【LeetCode】高频 SQL 50题 题解
目录
查询
可回收且低脂的产品
寻找用户推荐人
大的国家
文章浏览 I
无效的推文
连接
使用唯一标识码替换员工ID
产品销售分析 I
进店却未进行过交易的顾客
上升的温度
每台机器的进程平均运行时间
员工奖金
学生们参加各科测试的次数
至少有5名直接下属的经理
确认率
聚合函数
有趣的电影
平均售价
项目员工 I
各赛事的用户注册率
查询结果的质量和占比
每月交易 I
即时食物配送 II
游戏玩法分析 IV
排序和分组
每位教师所教授的科目种类的数量
查询近30天活跃用户数
销售分析 III
超过5名学生的课
求关注者的数量
只出现一次的最大数字
买下所有产品的客户
高级查询和连接
每位经理的下属员工数量
员工的直属部门
判断三角形
连续出现的数字
指定日期的产品价格
最后一个能进入巴士的人
按分类统计薪水
子查询
上级经理已离职的公司员工
换座位
电影评分
餐馆营业额变化增长
好友申请 II:谁有最多的好友
2016年的投资
部门工资前三高的所有员工
高级字符串函数
修复表中的名字
患某种疾病的患者
按日期分组销售产品
列出指定时间段内的所有的下单产品
正则表达式
查找拥有有效邮箱的用户
子句
删除重复的电子邮箱
第二高的薪水
查询
可回收且低脂的产品
1757. 可回收且低脂的产品 - 力扣(LeetCode)
简单查询
select product_id
from products
where low_fats='Y'and recyclable='Y';
寻找用户推荐人
584. 寻找用户推荐人 - 力扣(LeetCode)
注意和null值比较需要用is null和is not null
select name
from customer
where referee_id !=2 or referee_id is null;
大的国家
595. 大的国家 - 力扣(LeetCode)
简单查询
select name,population,area
from world
where area>=3e6or population>=25e6;
文章浏览 I
1148. 文章浏览 I - 力扣(LeetCode)
distinct消除重复,order 默认升序
selectdistinct author_id as id
fromviews
whereauthor_id=viewer_id
order byid;
无效的推文
1683. 无效的推文 - 力扣(LeetCode)
char_length返回字符数,length返回字节数
selecttweet_id
fromtweets
wherechar_length(content)>15;
连接
使用唯一标识码替换员工ID
1378. 使用唯一标识码替换员工ID - 力扣(LeetCode)
左外连接,左表全数据
selectunique_id,name
fromemployees e left join employeeUNI i
one.id=i.id;
using当两个表使用相同名称的列进行连接时
selectunique_id,name
fromemployees left join employeeUNI using(id);
产品销售分析 I
1068. 产品销售分析 I - 力扣(LeetCode)
左连接
select product_name,year,price
fromsales left join product using(product_id);
进店却未进行过交易的顾客
1581. 进店却未进行过交易的顾客 - 力扣(LeetCode)
左连接,没有交易就是对应交易id为null,按用户id分组统计次数
selectcustomer_id, count(visit_id)as count_no_trans
from visits left join transactions using(visit_id)
wheretransaction_id is null
group bycustomer_id;
上升的温度
197. 上升的温度 - 力扣(LeetCode)
自连接,匹配条件是日期相差一天,筛温度更高的
select w1.id
fromweather w1 join weather w2
ondatediff(w1.recordDate,w2.recordDate)=1
wherew1.temperature>w2.temperature;
每台机器的进程平均运行时间
1661. 每台机器的进程平均运行时间 - 力扣(LeetCode)
为什么这几道题都没有官方解答
小数格式用round,用if取值,如果是开始就减去,如果是结束就加上
selectmachine_id,round(avg(if(activity_type='start',-timestamp,timestamp))*2,3) as processing_time
fromactivity
group bymachine_id;
员工奖金
577. 员工奖金 - 力扣(LeetCode)
左连接,注意null不是0
selectname,bonus
fromemployee left join bonus using(empId)
where bonus<1000 or bonus is null;
学生们参加各科测试的次数
1280. 学生们参加各科测试的次数 - 力扣(LeetCode)
要查询出每个学生参加每一门科目测试的次数,即对象是每个学生和每个科目,所以需要学生和科目先做一个笛卡尔乘积,然后和考试表左连接,按学生科目分组统计考试表中的对应次数
selectstudent_id,student_name,subject_name,count(examinations.student_id)as attended_exams
fromstudents cross join subjects left join examinations using(student_id,subject_name)
group bystudent_id,subject_name
order bystudent_id,subject_name;
至少有5名直接下属的经理
570. 至少有5名直接下属的经理 - 力扣(LeetCode)
自连接,按员工id分组,having用于分组后筛选
select e1.name
from employee e1 join employee e2
one1.id=e2.managerId
group bye1.id
having count(*)>=5;
确认率
1934. 确认率 - 力扣(LeetCode)
左连接按user_id分组,用avg计算,如果确认的为真,那么就是1,否则就是0,没有的就是null变0
selects.user_id,round(ifnull(avg(c.action='confirmed'),0),2) as confirmation_rate
fromsignups s left join confirmations c
on s.user_id=c.user_id
group bys.user_id;
聚合函数
有趣的电影
620. 有趣的电影 - 力扣(LeetCode)
id&1判断奇数,<>不相等
select*
fromcinema
whereid&1 and description <> 'boring'
order byrating desc;
平均售价
1251. 平均售价 - 力扣(LeetCode)
左连接,匹配条件就是id相等并且日期对应,然后分组计算
selectp.product_id,round(ifnull(sum(p.price*u.units)/sum(u.units),0),2) as average_price
fromprices p left join unitsSold u
onp.product_id=u.product_id
andu.purchase_date between p.start_date and p.end_date
group byp.product_id;
项目员工 I
1075. 项目员工 I - 力扣(LeetCode)
按员工id左连接,按项目id分组取工作年限的平均值
selectproject_id,round(avg(experience_years),2) as average_years
fromproject left join employee using(employee_id)
group byproject_id;
各赛事的用户注册率
1633. 各赛事的用户注册率 - 力扣(LeetCode)
用不上连接,用子查询
selectcontest_id,round(count(*)*100/(select count(*)from users),2) percentage
fromregister
group bycontest_id
order bypercentage desc,contest_id;
查询结果的质量和占比
1211. 查询结果的质量和占比 - 力扣(LeetCode)
按query_name分组,统计质量的平均值,统计占比
selectquery_name,round(avg(rating/position),2) quality,round(avg(if(rating<3,1,0))*100,2) poor_query_percentage
fromqueries
group byquery_name;
每月交易 I
1193. 每月交易 I - 力扣(LeetCode)
date_format()函数,注意%Y是2025,而%y是25,%M是英文月份,%m是12
selectdate_format(trans_date,'%Y-%m') month,country,count(*) trans_count,sum(if(state='approved',1,0)) approved_count,sum(amount) trans_total_amount,sum(if(state='approved',amount,0)) approved_total_amount
from transactions
group bymonth,country;
即时食物配送 II
1174. 即时食物配送 II - 力扣(LeetCode)
子查询查找首发订单,然后在首发订单中计数即时订单,注意子查询要同时记录用户id,不然会误判其他用户的首发订单
selectround(avg(if(order_date=customer_pref_delivery_date,1,0))*100,2)as immediate_percentage
fromdelivery
where(customer_id,order_date) in (select customer_id,min(order_date)fromdeliverygroup bycustomer_id);
游戏玩法分析 IV
550. 游戏玩法分析 IV - 力扣(LeetCode)
用子查询找出有从首次登录连续登录2次的玩家个数,除以总玩家
selectround(count(*)/(selectcount(distinct player_id)fromactivity),2) fraction
fromactivity
where(player_id,date_sub(event_date,interval 1 day)) in (selectplayer_id,min(event_date)fromactivitygroup byplayer_id);
排序和分组
每位教师所教授的科目种类的数量
2356. 每位教师所教授的科目种类的数量 - 力扣(LeetCode)
按教师分组统计唯一科目id
selectteacher_id, count(distinct subject_id) cnt
fromteacher
group byteacher_id;
查询近30天活跃用户数
1141. 查询近30天活跃用户数 - 力扣(LeetCode)
按日期分组,保留合法日期,统计唯一用户数
selectactivity_date day,count(distinct user_id) active_users
fromactivity
group byactivity_date
havingdatediff('2019-07-27',activity_date) between 0 and 29;
销售分析 III
1084. 销售分析 III - 力扣(LeetCode)
销售表左连接产品表,按产品分组,保留合法日期的
selectproduct_id,product_name
fromsales left join product using(product_id)
group byproduct_id
havingmin(sale_date)>='2019-01-01' and max(sale_date)<='2019-03-31';
超过5名学生的课
596. 超过 5 名学生的课 - 力扣(LeetCode)
按课分组,保留行数大于5的
selectclass
fromcourses
group byclass
havingcount(*)>=5;
求关注者的数量
1729. 求关注者的数量 - 力扣(LeetCode)
按用户分组,统计行数,按用户升序
selectuser_id ,count(*) followers_count
fromfollowers
group byuser_id
order byuser_id;
只出现一次的最大数字
619. 只出现一次的最大数字 - 力扣(LeetCode)
先用子查询找出只出现一次的数字,然后找最大值,注意from后面使用子查询需要给子表别名
selectmax(num) num
from(selectnumfrommyNumbersgroup bynumhavingcount(*)=1
) as t;
买下所有产品的客户
1045. 买下所有产品的客户 - 力扣(LeetCode)
按客户分组统计购买的产品数量是否等于所有的产品种类,注意用户可能重复购买
selectcustomer_id
fromcustomer
group bycustomer_id
havingcount(distinct product_key)=(select count(*) from product);
高级查询和连接
每位经理的下属员工数量
1731. 每位经理的下属员工数量 - 力扣(LeetCode)
自己按上级id和员工id内连接,然后按上级id分组,统计行数就是员工数量
selectm.employee_id,m.name,count(*) reports_count,round(avg(e.age)) average_age
fromemployees e join employees m
one.reports_to=m.employee_id
group bye.reports_to
order bym.employee_id;
员工的直属部门
1789. 员工的直属部门 - 力扣(LeetCode)
是Y的或者只有一个的
selectemployee_id,department_id
fromemployee
whereprimary_flag='Y' or employee_id in(selectemployee_idfromemployeegroup byemployee_idhavingcount(*)=1);
判断三角形
610. 判断三角形 - 力扣(LeetCode)
最小的两边之和大于最大边
selectx,y,z,if(x+y+z>2*greatest(x,y,z),'Yes','No') triangle
fromtriangle;
连续出现的数字
180. 连续出现的数字 - 力扣(LeetCode)
官解很无趣没有通用性,下面是deepseek给出的通用解法
如果数字是连续出现的,那么数字的行号和和出现顺序的差值是固定的,按数字和计算的差值分组统计次数
SELECT DISTINCT num AS ConsecutiveNums
FROM (SELECT num,id - ROW_NUMBER() OVER (PARTITION BY num ORDER BY id) AS grpFROM Logs
) t
GROUP BY num, grp
HAVING COUNT(*) >= 3;
指定日期的产品价格
1164. 指定日期的产品价格 - 力扣(LeetCode)
先查出每个产品在指定日期之前的最大日期
如果最大日期为空那么就是10,否则就取对应的价格
selectp1.product_id,ifnull(p2.new_price,10) price
from(selectdistinct product_idfrom products
) as p1 left join (selectproduct_id,new_pricefromproductswhere(product_id,change_date) in (selectproduct_id,max(change_date)from productswherechange_date<='2019-08-16'group byproduct_id)
) as p2
using(product_id);
最后一个能进入巴士的人
1204. 最后一个能进入巴士的人 - 力扣(LeetCode)
统计每一个人前面人的总重,保留合适的重量,再按顺序逆序取第一个
selecta.person_name
fromqueue a,queue b
wherea.turn>=b.turn
group bya.person_id
havingsum(b.weight)<=1000
order bya.turn desc
limit 1;
按分类统计薪水
1907. 按分类统计薪水 - 力扣(LeetCode)
用sum统计范围内的个数,再用union合并起来
select'Low Salary' category,sum(income<20000) accounts_count
fromaccounts
union
select'Average Salary' category,sum(income between 20000 and 50000) accounts_count
fromaccounts
union
select'High Salary' category,sum(income>50000) accounts_count
fromaccounts;
子查询
上级经理已离职的公司员工
1978. 上级经理已离职的公司员工 - 力扣(LeetCode)
挺直接的
selectemployee_id
fromemployees
wheresalary<30000 and manager_id not in(selectemployee_idfromemployees)
order byemployee_id;
换座位
626. 换座位 - 力扣(LeetCode)
窗口函数lead往后取,lag往前取,需要over定义窗口的范围
lead往后取没有了就默认本值
selectid,if(id%2,lead(student,1,student)over(order by id),lag(student,1)over(order by id)) student
fromseat;
电影评分
1341. 电影评分 - 力扣(LeetCode)
这算两道题,注意用union all连接,如果用union会去掉相同的结果
三张表,都是用来两两连接查询名字的
第一个按用户分组保留数量最多的,第二先筛日期然后按电影分组保留平均分最高的
(selectname results
frommovieRating left join users using(user_id)
group byuser_id
order bycount(*) desc, name
limit 1)
union all
(select title results
frommovieRating left join movies using(movie_id)
wherecreated_at between '2020-02-01' and '2020-02-29'
group bymovie_id
order byavg(rating) desc,title
limit 1);
餐馆营业额变化增长
1321. 餐馆营业额变化增长 - 力扣(LeetCode)
用窗口函数按日期排序每七个算一次总和,最后从第七天开始挑
selectdistinct visited_on,sums amount,round(sums/7,2) average_amount
from(selectvisited_on,sum(amount)over(order by visited_on range interval 6 day preceding) sumsfrom customer
) t
wherevisited_on>= (select min(visited_on) from customer)+6;
好友申请 II:谁有最多的好友
602. 好友申请 II :谁有最多的好友 - 力扣(LeetCode)
将发送和接收合并在一起,统计最多的
selectid,count(*) num
from(selectrequester_id id, accepter_id a_idfromrequestAcceptedunion allselectaccepter_id id, requester_id a_idfromrequestAccepted
) t
group byid
order bynum desc
limit 1;
2016年的投资
585. 2016年的投资 - 力扣(LeetCode)
顾名思义,两个子查询筛选条件
子查询如果参与from需要加上别名
selectround(sum(tiv_2016),2) tiv_2016
from(select tiv_2016 from insurance iwhere tiv_2015 in(select tiv_2015 from insurance where pid!=i.pid)and (lat,lon) not in(select lat,lon from insurance where pid!=i.pid)
)t;
部门工资前三高的所有员工
185. 部门工资前三高的所有员工 - 力扣(LeetCode)
查出该部门前三高的工资,保留工资在里面的员工
注意这个版本不能在in后面的子查询用limit,所以要套一层
selectd.name department,e.name employee, salary
fromemployee e left join department d
one.departmentId=d.id
wheresalary
in( select * from(select distinct salaryfrom employeewhere departmentId=e.departmentIdorder by salary desc limit 3)t
);
高级字符串函数
修复表中的名字
1667. 修复表中的名字 - 力扣(LeetCode)
调用函数
concat连接字符串
upper变大lower变小
substring提取子串
selectuser_id,concat(upper(substring(name,1,1)),lower(substring(name,2))) name
fromusers
order byuser_id;
患某种疾病的患者
1527. 患某种疾病的患者 - 力扣(LeetCode)
字符串匹配
select*
frompatients
whereconditions like 'diab1%' or conditions like '% diab1%';
按日期分组销售产品
1484. 按日期分组销售产品 - 力扣(LeetCode)
group_concat可以将分组后的数据拼接成字符串
selectsell_date,count(distinct product) num_sold,group_concat(distinct product order by product separator ',') products
fromactivities
group bysell_date
order bysell_date;
列出指定时间段内的所有的下单产品
1327. 列出指定时间段内所有的下单产品 - 力扣(LeetCode)
连接两张表,筛选时间,用产品分组挑总数超过100的
selectproduct_name,sum(unit) unit
fromorders left join products using(product_id)
whereorder_date between '2020-02-01' and '2020-02-29'
group byproduct_id
having unit >=100;
正则表达式
^:表示一个字符串或行的开头
[a-z]:表示一个字符范围,匹配从 a 到 z 的任何字符
[0-9]:表示一个字符范围,匹配从 0 到 9 的任何字符
[a-zA-Z]:这个变量匹配从 a 到 z 或 A 到 Z 的任何字符。请注意,你可以在方括号内指定的字符范围的数量没有限制,您可以添加想要匹配的其他字符或范围
[^a-z]:这个变量匹配不在 a 到 z 范围内的任何字符。请注意,字符 ^ 用来否定字符范围,它在方括号内的含义与它的方括号外表示开始的含义不同
[a-z]*:表示一个字符范围,匹配从 a 到 z 的任何字符 0 次或多次
[a-z]+:表示一个字符范围,匹配从 a 到 z 的任何字符 1 次或多次
\w
:匹配一个字母数字字符(包括下划线)
.:匹配任意一个字符
\.:表示句点字符。请注意,反斜杠用于转义句点字符,因为句点字符在正则表达式中具有特殊含义。还要注意,在许多语言中,你需要转义反斜杠本身,因此需要使用\\.
$:表示一个字符串或行的结尾
查找拥有有效邮箱的用户
1517. 查找拥有有效邮箱的用户 - 力扣(LeetCode)
正则表达式regexp,\\用于表示\
select*
fromusers
wheremail regexp '^[a-zA-Z][\\w.-]*@leetcode\\.com$';
子句
删除重复的电子邮箱
196. 删除重复的电子邮箱 - 力扣(LeetCode)
注意要从数据库中删除,不允许在DELETE语句的子查询中直接引用正在被删除的表,防止在修改表的同时又查询它可能导致的不确定行为
所以要套一层select*from做临时表
delete fromperson
where id not in(select * from(selectmin(id)fromperson pgroup byemail ) temp);
第二高的薪水
176. 第二高的薪水 - 力扣(LeetCode)
要注意没有需要返回null,因此外面再套一层select
select(select distinctsalaryfromemployeeorder bysalary desclimit 1 offset 1) secondHighestSalary;