【力扣 SQL 50】子查询篇

目录

1.上级经理已离职的公司员工

2.换座位

3.电影评分

4.餐馆营业额变化增长(窗口+子查询)

[5.好友申请 II :谁有最多的好友(union+窗口函数+子查询)](#5.好友申请 II :谁有最多的好友(union+窗口函数+子查询))

6.2016年的投资(子查询)

7.部门工资前三高的所有员工(左连接)


题目来源:高频 SQL 50 题(基础版) - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

1.上级经理已离职的公司员工

题目:

查找这些员工的id,他们的薪水严格少于$30000 并且他们的上级经理已离职。当一个经理离开公司时,他们的信息需要从员工表中删除掉,但是表中的员工的manager_id 这一列还是设置的离职经理的id 。

返回的结果按照employee_id 从小到大排序。

查询结果如下所示:

思路:

这道题要求薪资小于30000的且上司已经离职的员工

第一个条件很简单,第二个条件如果正向找已经离职的员工会麻烦点,我们使用not in语句来反向寻找目前还未离职的员工,最后排个序即可。

代码:

sql 复制代码
# 子查询+排序
select employee_id
    from Employees 
    where salary<30000
    and manager_id not in (
        select employee_id
            from Employees
    )
    order by employee_id

2.换座位

题目:

编写解决方案来交换每两个连续的学生的座位号。如果学生的数量是奇数,则最后一个学生的id不交换。

id 升序 返回结果表。

查询结果格式如下所示。

思路:

这题要求的是将相邻的两行的id进行位置交换,最后跟据id进行排序。

这题的难点在于如何将两行的id进行交换,其他字段不变,可以使用窗口函数实现,完整流程:

  1. FROM seat:从seat表读取数据

  2. 计算IF表达式:对每行的id进行奇偶判断和转换

  3. 窗口函数排序:按照转换后的值排序

  4. RANK()分配排名:为排序后的行分配连续排名(1,2,3...)(因为排名从1开始,需重新分配)

  5. SELECT输出:显示新的id列和原student列

转换过程示例:

原ID 学生 转换值 新ID
1 Alice 1 1
2 Bob 0 2
3 Charlie 3 3
4 David 2 4
5 Eve 5 5

结合流程和表进行理解

代码:

sql 复制代码
# 窗口函数+排序
select rank() over (order by if(id%2=0,id-2,id)) id, student
    from Seat

3.电影评分

题目:

请你编写一个解决方案:

  • 查找评论电影数量最多的用户名。如果出现平局,返回字典序较小的用户名。
  • 查找在 February 2020平均评分最高 的电影名称。如果出现平局,返回字典序较小的电影名称。

字典序 ,即按字母在字典中出现顺序对字符串排序,字典序较小则意味着排序靠前。

返回结果格式如下例所示。

思路:

这题本质就是两道题,第一个要求的是评论电影最多的人,第二个要求的是2020年2月内的电影平均分最高的电影,两个毫不相干的问题,最后都塞进同一个字段,只能使用写两个select然后使用union拼接起来。

之所以用union all而不是union,是因为有一个例子出现了电影名跟人名同名的情况,直接union会把其中一个覆盖掉,必须使用union all

代码:

sql 复制代码
# union
(select u.name results
    from MovieRating mr 
    left join Users u on mr.user_id=u.user_id
    group by mr.user_id
    order by count(*) desc, name
    limit 1
    )
union all
(select m.title results
    from MovieRating mr
    left join Movies m on mr.movie_id=m.movie_id
    where date_format(created_at,'%Y%m')='202002' 
    group by mr.movie_id
    order by avg(rating) desc, m.title         
    limit 1    
    )  

4.餐馆营业额变化增长(窗口+子查询)

题目:

你是餐馆的老板,现在你想分析一下可能的营业额变化增长(每天至少有一位顾客)。

计算以 7 天(某日期 + 该日期前的 6 天)为一个时间段的顾客消费平均值。average_amount保留两位小数。

结果按 visited_on 升序排序

返回结果格式的例子如下。

思路:

这道题要求对每个日期的前七天的营业额的平均值,且该日期的前七天要存在,不能少一天,这就是为什么输出结果从7号开始。

因为原表出现重复的日期(如10号),思路是先对原来的表进行重新去重查询,同时使用窗口函数计算每个日期的前七天营业总额,这样就得到一个新表temp。这里要注意的是,对于去重distinct和窗口函数的优先级是先对每条数据执行完所有窗口函数的代码,然后才进行去重,也就是说,就算有两个10号,也会先把所有10号的营业额算进去,这两个10号的营业总额都是一样的,然后再去重多余的10号

最后最外层的查询的where语句要筛选出从第7天开始 的数据,使用 datediff(date1, date2):计算两个日期的天数差,这个date2可以使用子查询 (select min(visited_on) from Customer):找到最早的访问日期(也就是1号),这个日期差>=6即可满足条件

最后再回到最外层的查询字段,对于第三个字段,只要使用round((amount/7),2)求平均值然后保留2位小数即可,注意不能使用avg()

代码:

sql 复制代码
#窗口函数+子查询
select visited_on, amount, round((amount/7),2) average_amount 
    from  (
        select distinct visited_on, 
        sum(amount) over(order by visited_on range interval 6 day preceding) amount
            from Customer
    ) temp
    where datediff(visited_on, (
        select min(visited_on)
            from Customer
    )) >=6

5.好友申请 II :谁有最多的好友(union+窗口函数+子查询)

题目:

编写解决方案,找出拥有最多的好友的人和他拥有的好友数目。

生成的测试用例保证拥有最多好友数目的只有 1 个人。

查询结果格式如下例所示。

思路:

这道题主要是求拥有好友数量最多的人,也就是第一列出现的id加上第二列出现的id的值最大的用户,因为一个是添加别人,一个是被别人添加,都是属于好友数+1

因为在一行数据中,每个id只要出现一次,就算这个id的好友数+1,因此根本目的就是找出出现次数最多的id。

那么我们只要统计两列的id数然后相加再排个序即可,但是这样直接算会比较麻烦,我们可以使用union all语句进行优化,第一个select,先求出第一列的所有id,第二个select求出第二列的所有id,然后合并(注意union会去除重复行, 而union all会保留所有行),这样我们就做到了将两列的id全部加起来了,之所以要保留所有行,就是防止第二列的id漏算。然后就是按照id进行分组排序取第一即可。

代码:

sql 复制代码
# union
select id, count(*) num
    from (
        select requester_id id from RequestAccepted
        union all
        select accepter_id id from RequestAccepted
    ) temp
    group by id
    order by num desc
    limit 1

进阶:

使用窗口函数

sql 复制代码
# union+窗口函数+子查询
WITH friend_counts AS (
    SELECT id, COUNT(*) as num
    FROM (
        SELECT requester_id as id FROM RequestAccepted
        UNION ALL
        SELECT accepter_id as id FROM RequestAccepted
    ) temp
    GROUP BY id
)
SELECT id, num
FROM friend_counts
WHERE num = (SELECT MAX(num) FROM friend_counts)
ORDER BY num DESC;

6.2016年的投资(子查询)

题目:

编写解决方案报告 2016 年 (tiv_2016) 所有满足下述条件的投保人的投保金额之和:

  • 他在 2015 年的投保额 (tiv_2015) 至少跟一个其他投保人在 2015 年的投保额相同。
  • 他所在的城市必须与其他投保人都不同(也就是说 (lat, lon) 不能跟其他任何一个投保人完全相同)。

tiv_2016 四舍五入的 两位小数

查询结果格式如下例所示。

思路:

这道题求的是满足条件的所有投保人的2016年的投保金额总额,也就是要先筛选出满足上面说的两个条件的人再求和

我们可以使用从正面进行求解,先使用一个where求出满足第一个条件的人,子查询的内容是先按tiv_2015分组,然后having找出行数>1的组,意思就是有至少两个人的2015的投保额一样,使用in确定这个范围即可;然后使用and求出满足第二个条件的人,子查询的内容是同时对lat和lon进行分组,然后找出行数>1的组,意思是这个组的经纬度一样,一定不满足第二个条件,然后使用not in排除掉这组数据即可;

代码:

sql 复制代码
# 子查询
select round(sum(tiv_2016),2) tiv_2016
    from Insurance
    where tiv_2015 in (
        select tiv_2015
            from Insurance
            group by tiv_2015
            having count(*)>1
    )
    and (lat, lon) not in (
        select lat, lon
            from Insurance
            group by lat, lon
            having count(*)>1
    )

也可以使用窗口函数:

思路就是先对原表的每一行数据后面都开两个窗,统计当前id的2015投资额有多少组和经纬度有多少组

sql 复制代码
#窗口函数
select round(sum(tiv_2016),2) tiv_2016
    from (
        select *, 
            count(*) over (partition by tiv_2015) cnt_tiv_2015, 
            count(*) over (partition by lat, lon) cnt_lat_lon
            from Insurance
    ) temp
    where cnt_tiv_2015>1
    and cnt_lat_lon =1

窗口查询结果示例:

7.部门工资前三高的所有员工(左连接)

题目:

公司的主管们感兴趣的是公司每个部门中谁赚的钱最多。一个部门的 高收入者 是指一个员工的工资在该部门的 不同 工资中 排名前三

编写解决方案,找出每个部门中 收入高的员工

任意顺序 返回结果表。

返回结果格式如下所示。

思路:

这题主要求每个部门的工资排名前三的员工(包括重复/并排的),比较麻烦的是如何将重复的也找出来

我们可以先直接使用两次左连接来找出排名前三的员工,第一个左连接先Employee自己跟自己链接,要求是departmentId必须相等且工资必须比左边的高,第二个左连接是跟Department链接,要求是departmentId相等,代码如下:

sql 复制代码
select *
    from employee e1
    left join employee e2 on e1.departmentId = e2.departmentId and e1.salary < e2.salary
    left join department d on e1.departmentId = d.id

查询结果如下:

可以看出在IT组Will就是第三名,则可以排除Janet,我们可以继续按e1.id进行分组,条件是count(distinct e2.salary) <=2,即先对e2的工资进行去重然后再统计行数,行数即代表实际e1.id的排名+1,Joe和Randy并排第二

sql 复制代码
    group by e1.id
    having count(distinct e2.salary) <=2

最后可以再进行一个排序即可,排序是对e1.departmentId, e1.salary进行排序,因为返回的数据没有要求,所以排不排都无所谓,我这里选择排一下(不排更快一点)

代码:

sql 复制代码
#左连接
select d.name Department, e1.name Employee, e1.salary Salary 
    from employee e1
    left join employee e2 on e1.departmentId = e2.departmentId and e1.salary < e2.salary
    left join department d on e1.departmentId = d.id
    group by e1.id
    having count(distinct e2.salary) <=2
    order by e1.departmentId, e1.salary desc

本篇文章到此结束,如果对你有帮助可以点个赞吗~

个人主页有很多个人总结的 Java、MySQL 等相关的知识,欢迎关注~

相关推荐
玖日大大36 分钟前
OceanBase SeekDB:AI 原生数据库的技术革命与实践指南
数据库·人工智能·oceanbase
AlenTech1 小时前
207. 课程表 - 力扣(LeetCode)
算法·leetcode·职场和发展
高溪流2 小时前
3.数据库表的基本操作
数据库·mysql
练习时长一年2 小时前
LeetCode热题100(杨辉三角)
算法·leetcode·职场和发展
alonewolf_992 小时前
深入剖析MySQL锁机制与MVCC原理:高并发场景下的数据库核心优化
数据库·mysql
栈与堆2 小时前
LeetCode 19 - 删除链表的倒数第N个节点
java·开发语言·数据结构·python·算法·leetcode·链表
一 乐2 小时前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
Codeking__3 小时前
Redis初识——什么是Redis
数据库·redis·mybatis
k***1953 小时前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring
程序员黄老师3 小时前
主流向量数据库全面解析
数据库·大模型·向量·rag