整体总结
SQL 在数据库内部的逻辑处理顺序是这样的:
**FROM :**确定数据来源(表、子查询、视图)。
**JOIN / ON :**如果有连接,先根据 ON 条件生成笛卡尔积,再过滤掉不符合条件的行。
**WHERE :**过滤行(对原始表数据过滤,还没分组)。
**GROUP BY :**按指定字段进行分组,开始"形成小集合"。
**HAVING :**对分组后的结果再进行过滤(和 WHERE 的区别是它作用于分组之后)。
**SELECT :**选择要返回的列,执行列表达式、聚合函数。
DISTINCT :(如果有)去重。
**ORDER BY :**对结果排序。
**LIMIT / OFFSET :**返回最终结果的行数限制。
1.条件过滤(WHERE子句)
范围查询 :BETWEEN...AND...(闭区间)
sql
SELECT * FROM students WHERE age BETWEEN 18 AND 25;
**集合查询:**IN
sql
SELECT * FROM students WHERE university IN ('北京大学', '浙江大学');
模糊查询:(LIKE和LIKE CONCAT)
LIKE直接写死匹配模板,固定字符串+通配符
sql
SELECT * FROM users WHERE name LIKE '史%'
CONCAT + LIKE是一个字符串拼接函数,可以避免SQL注入风险。
sql
SELECT * FROM users WHERE city LIKE CONCAT('%', @keyword, '%');
2.聚合函数(GROUP BY & HAVING)
聚合函数:COUNT() SUM() AVG() MAX() MIN()等,使用例子如下:
聚合函数用于计算具体值,GROUP BY用于判断根据哪个键进行分组,HAVING用于过滤不符合条件的组 (与WHERE的区别是,WHERE过滤的是不符合条件的行)
sql
SELECT
s.product_id,
p.product_name
FROM Sales s
JOIN Product p ON s.product_id = p.product_id
GROUP BY p.product_id, p.product_name
HAVING
MIN(s.sale_date) >= '2019-01-01'
AND MAX(s.sale_date) <= '2019-03-31'
3.多表查询(JOIN与UNION)
-
**内连接(INNER JOIN / JOIN):**返回两表交集数据。
-
**左连接(LEFT JOIN):**保留左表所有记录,以及右表匹配的数据,无匹配则补NULL。
-
**右连接(RIGHT JOIN):**保留右表所有记录 ,以及左表匹配的数据,无匹配则补NULL。
-
**全连接(LEFT JOIN + RIGHT JOIN + UNION):**保留两个表的完整数据。
-
UNION与UNION ALL用于合并两个表的数据,区别是UNION会去重,而UNION ALL 不会。
4.窗口函数 (Window Functions)
常用于处理"排名"、"连续出现"、"前 N 名"类问题。
格式: 函数名() OVER (PARTITION BY 分组列 ORDER BY 排序列)
-
ROW_NUMBER(): 依次排序,即使数值相同排名也不同 (1, 2, 3, 4)。 -
RANK(): 数值相同排名相同,但会跳号 (1, 2, 2, 4)。 -
DENSE_RANK(): 数值相同排名相同,且不跳号 (1, 2, 2, 3)。常用于"部门工资前三高"。 -
LEAD()/LAG(): 获取当前行之后/之前的某行数据。LAG(num, 1) OVER (ORDER BY id) AS prev1。
5.结果排序与限制(ORDER &LIMIT)
可设置降序或者升序
sql
SELECT * FROM students ORDER BY gpa DESC, age ASC;
可通过OFFSET跳过前几行数据,如果没有10行会返回空
sql
SELECT * FROM students LIMIT 5 OFFSET 10;
6.常用函数
-
CASE WHEN ... THEN ... ELSE ... END: SQL 中的 If-Else。 -
IFNULL(val, default): 如果值为 NULL 则返回默认值。 -
COALESCE(v1, v2, ...): 返回第一个非空值。 -
ROUND(xx,2): 保留几位小数,四舍五入,FLOOR(xx,2)下取整,CEIL上取整
7. 时间与字符串处理
-
CHAR_LENGTH(content) > 15:计算字符串长度
-
DATE_FORMAT(trans_date, "%Y-%m"):将时间转化为指定格式
-
DATEDIFF(d1, d2): 计算日期差。 -
LEFT(str, len)/SUBSTRING(): 截取字符串。 -
REGEXP: 正则表达式匹配。
LeetCode 高频50 SQL
简单讲解了以下LeetCode高频50SQL的逻辑,部分包含特殊语句或者复杂逻辑的代码已经给出,剩余简单的题目只给出了解题思路,适合代码复习。
一、 查询 (Basic Queries)
1. 可回收且低脂的产品
-
题目描述:找出所有既是低脂(low_fats = 'Y')又是可回收(recyclable = 'Y')的产品 ID。
-
解题思路 :使用
WHERE子句同时过滤两个布尔/字符条件,基础语法可直接跳过。
2. 寻找用户推荐人
-
题目描述:找出所有推荐人 ID(referee_id)不等于 2 的顾客姓名。
-
解题思路 :注意处理
NULL值。在 SQL 中,!= 2不包含NULL,必须显式判定。 -
代码 :
WHERE referee_id != 2 OR referee_id IS NULL。
3. 大的国家
-
题目描述:找出面积(area)至少为 300 万或人口(population)至少为 2500 万的国家。
-
解题思路 :使用
OR连接两个面积和人口的过滤条件。
4. 文章浏览 I
-
题目描述:找出所有至少浏览过一次自己文章的作者 ID。
-
解题思路:当作者 ID 等于读者 ID 时,表示自读。需去重并按 ID 升序。
-
特殊语句 :
DISTINCT,ORDER BY id ASC。
5. 无效的推文
-
题目描述:找出内容长度严格大于 15 个字符的推文 ID。
-
解题思路 :筛选内容长度大于 15 的记录,
CHAR_LENGTH()(获取字符数)。 -
代码:SELECT tweet_id FROM tweets WHERE CHAR_LENGTH(content) > 15
二、 连接 (Basic Joins)
6. 使用唯一标识码替换员工 ID
-
题目描述:展示每位用户的唯一标识码(unique_id);如果没有,则展示 null。
-
解题思路:以员工表为主表,左外连接(LEFT JOIN)唯一码表,防止丢失没有唯一码的员工。
-
代码 :
LEFT JOIN ... ON。
7. 产品销售分析 I
-
题目描述:获取销售表(Sales)中所有订单对应的产品名称(product_name)、年份和价格。
-
解题思路:内连接销售表和产品表,获取产品名称。
8. 进店却未进行过交易的顾客
-
题目描述:找出那些进店浏览了但没有进行任何交易的顾客 ID 及其进店次数。
-
解题思路 :左连接交易表后,通过
WHERE transaction_id IS NULL找到未交易记录并计数。 -
特殊语句 :
IS NULL。
9. 上升的温度
-
题目描述:找出与其前一天相比温度更高的所有日期 ID。
-
解题思路 :自连接同一个表,比较"今天"和"昨天"的温度,使用日期函数处理时间差,
DATEDIFF(date1, date2) = 1 -
代码:
sqlSELECT w1.id FROM Weather as w1 JOIN Weather as w2 ON DATEDIFF(w1.recordDate, w2.recordDate) = 1 WHERE w1.temperature > w2.temperature;
10. 每台机器的进程平均运行时间
-
题目描述:计算每台机器完成一个进程所需的平均耗时(End 时间 - Start 时间)。
-
解题思路 :计算每个进程的
(end - start),再对机器 ID 进行分组求平均。
11. 员工奖金
-
题目描述:查询奖金少于 1000 的员工姓名及其奖金数额。
-
解题思路 :左连接奖金表,筛选奖金小于 1000 或奖金为
NULL的记录。
12. 学生们参加各科测试的次数
-
题目描述:统计每个学生参加每一门科目测试的次数,即使没参加过也要显示 0。
-
解题思路 :使用
CROSS JOIN生成学生和科目的全组合,再LEFT JOIN考试记录进行计数。 -
特殊语句 :
CROSS JOIN。
13. 至少有 5 名直接下属的经理
-
题目描述:找出至少有五个直接下属的经理姓名。
-
解题思路 :先对
managerId分组统计数量,通过HAVING过滤后连接查询名字。 -
特殊语句 :
GROUP BY ... HAVING COUNT(*) >= 5。
14. 确认率
-
题目描述:计算每个用户的"确认率"(确认消息数 / 总请求数)。
-
解题思路 :左连接确认表,利用
AVG(IF(action='confirmed', 1, 0))计算比例。 -
特殊语句 :
ROUND(..., 2),IFNULL(..., 0)。
三、 聚合函数 (Aggregate Functions)
15. 有趣的电影
-
题目描述:找出 ID 为奇数且描述不是 "boring" 的电影,按评分降序排列。
-
解题思路:WHERE过滤 ID 奇偶性和描述内容,按评分降序。
16. 平均售价
-
题目描述:计算每种产品在对应日期范围内的平均售价。
-
解题思路 :连接销售表和价格表(条件包含日期区间),
SUM(价格*数量)/SUM(数量)。
17. 项目员工 I
-
题目描述:计算每个项目下员工的平均工作年限。
-
解题思路:按项目 ID 分组计算平均值。
18. 各赛事的用户注册率
-
题目描述:计算每个赛事中注册用户的百分比。
-
解题思路:按赛事分组计数,除以用户表总数。
19. 查询结果的质量和占比
-
题目描述:计算每个查询名的质量(评分/位置的均值)和劣质查询占比(评分 < 3 的百分比)。
-
解题思路:使用聚合函数直接计算均值和比例。
20. 每月交易 I
-
题目描述:按月和国家统计交易数、批准数、总金额和批准金额。
-
解题思路:按月份(格式化日期)和国家分组统计。
-
特殊语句 :
DATE_FORMAT(trans_date, '%Y-%m')。
21. 即时食物配送 II
-
题目描述:找所有用户的"首次订单"中,即时订单(下单日期=配送日期)所占的百分比。
-
解题思路:子查询找到每个用户的首单日期,再判断首单是否为即时单。
-
特殊语句 :
(customer_id, order_date) IN (SELECT ... MIN(order_date) ...)。
22. 游戏玩法分析 IV
-
题目描述:计算首日登录后,第二天紧接着也登录的玩家比例。
-
解题思路:找到每个玩家的首登日期,左连接表看"首登后一天"是否有记录。
-
特殊语句 :
DATE_ADD(min_date, INTERVAL 1 DAY)。 -
代码:
sql# Write your MySQL query statement below WITH First_login AS( SELECT player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) AS next_day FROM Activity GROUP BY player_id ), ConsecutivePlayers AS ( SELECT DISTINCT a.player_id FROM Activity a JOIN First_login f ON a.player_id = f.player_id AND a.event_date = f.next_day ) SELECT IFNULL( ROUND( (SELECT COUNT(*) FROM ConsecutivePlayers) / (SELECT COUNT(DISTINCT player_id) FROM Activity) , 2) , 0) as fraction;
四、 排序和分组 (Sorting and Grouping)
23. 每位教师所教授的科目种类的数量
-
题目描述:统计每位老师教授了多少种不同的科目。
-
解题思路 :按老师 ID 分组,对科目 ID 去重计数。
COUNT(DISTINCT subject_id)。
24. 查询近 30 天活跃用户数
-
题目描述:统计截至 2019-07-27(含)前 30 天内的每日活跃用户数。
-
解题思路:过滤日期范围,分组统计去重后的用户。
-
代码:
sqlSELECT activity_date AS day, COUNT(DISTINCT user_id) AS active_users FROM Activity WHERE activity_date BETWEEN DATE_SUB('2019-07-27', INTERVAL 29 DAY) AND '2019-07-27' GROUP BY activity_date;
25. 销售分析 III
-
题目描述:找出仅在 2019 年第一季度出售过的产品。
-
解题思路 :分组后,
HAVING判定最小和最大销售日期均在指定范围内。
26. 超过 5 名学生的课
-
题目描述:列出所有至少有 5 名学生的课程。
-
解题思路 :分组并使用
HAVING过滤计数。
27. 求关注者的数量
-
题目描述:统计每个被关注者的关注者总数。
-
解题思路:按被关注者分组统计。
28. 只出现一次的最大数字
-
题目描述:找出只出现过一次的数字中最大的那个。
-
解题思路 :子查询找出
COUNT = 1的数字,外部嵌套MAX()。 -
代码:
sqlSELECT MAX(num) AS num FROM( SELECT num FROM MyNumbers GROUP BY num HAVING COUNT(*) = 1 ) T;
29. 买下所有产品的客户
-
题目描述:找出购买了产品表中所有种类产品的客户。
-
解题思路:按顾客分组,统计其购买的不同产品数是否等于产品表总行数。
-
代码:
sqlSELECT customer_id FROM Customer GROUP BY customer_id HAVING COUNT(DISTINCT product_key) = (SELECT COUNT(*) FROM Product);
五、 高级查询和连接 (Advanced Select and Joins)
30. 每位经理的下属员工数量
-
题目描述:统计经理的下属人数及下属的平均年龄。
-
解题思路:自连接。
-
代码:
sqlSELECT e.employee_id, e.name, COUNT(*) AS reports_count, ROUND(AVG(t.age)) AS average_age FROM Employees e JOIN Employees t ON e.employee_id = t.reports_to GROUP BY e.employee_id ORDER BY e.employee_id;
31. 员工的直属部门
-
题目描述:找出员工的直属部门。若只属于一个部门则选该部门,若属于多个则选标记为 'Y' 的。
-
解题思路 :使用
UNION结合单部门员工和标记为 Y 的员工。
32. 判断三角形
-
题目描述:给定三条边长,判断是否能组成三角形。
-
解题思路:判定任意两边之和大于第三边。
-
特殊语句 :
CASE WHEN x+y>z AND x+z>y AND y+z>x THEN 'Yes' ELSE 'No' END。 -
代码:
sql# Write your MySQL query statement below SELECT x, y, z, CASE WHEN x + y > z AND x + z > y AND y + z > x THEN 'Yes' ELSE 'No' END AS triangle FROM Triangle;
33. 连续出现的数字
-
题目描述:找出连续出现至少三次的数字。
-
解题思路 :判断某个数字是否在连续的 ID 中重复。
LEAD()或LAG()。 -
代码:
sql# Write your MySQL query statement below SELECT DISTINCT num AS ConsecutiveNums FROM (SELECT num, LAG(num, 1) OVER (ORDER BY id) AS prev1, LAG(num, 2) OVER (ORDER BY id) AS prev2 FROM LOGS) T WHERE num = prev1 AND num = prev2;
34. 指定日期的产品价格
-
题目描述:找出所有产品在 2019-08-16 时的价格(无调价记录则默认为 10)。
-
解题思路:取日期前的最后一次调价记录,再并集未调价的产品。
-
特殊语句 :
RANK() OVER(PARTITION BY product_id ORDER BY change_date DESC)。
35. 最后一个能进入巴士的人
-
题目描述:按重量限制(1000kg)和上车顺序,找出最后一个能上车的人。
-
解题思路 :按顺序累加重量,
SUM(weight) OVER(ORDER BY turn)。 -
代码:
sql# Write your MySQL query statement below SELECT person_name FROM ( SELECT person_name, SUM(weight) OVER(ORDER BY turn) AS total_weight FROM Queue ) T WHERE total_weight <= 1000 ORDER BY total_weight DESC LIMIT 1;
36. 按分类统计薪水
-
题目描述:统计 Low Salary, Average Salary, High Salary 三个等级的账户数量。
-
解题思路 :分别计算三个分类的数量并用
UNION合并。
六、 子查询 (Subqueries)
37. 上级经理已离职的公司员工
-
题目描述:找出薪水低于 3000 且其上级经理已不在公司(员工表)的员工。
-
解题思路 :使用
NOT IN子查询。
38. 换座位
-
题目描述:交换相邻两个学生的座位。
-
解题思路:奇数 ID + 1,偶数 ID - 1,考虑最后一个奇数 ID 不变。
-
代码:
sql# Write your MySQL query statement below SELECT ( CASE WHEN MOD(id, 2) = 1 AND id = (SELECT COUNT(*) FROM Seat) THEN id WHEN MOD(id, 2) = 1 THEN id + 1 ELSE id - 1 END) AS id, student FROM Seat ORDER BY id;
39. 电影评分
-
题目描述:找出评论电影最多的用户名,以及 2020 年 2 月平均分最高的电影名。
-
解题思路 :分两部分查询并用
UNION ALL。
40. 餐馆营业额变化增长
-
题目描述:计算每一天及其前 6 天(共 7 天)的营业额总和及平均值。
-
解题思路 :利用窗口函数定义滚动窗口范围。
ROWS 6 PRECEDING。 -
代码:
sqlWITH daily_amount AS ( SELECT visited_on, SUM(amount) AS amount FROM Customer GROUP BY visited_on ), window_metrics AS ( SELECT visited_on, SUM(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS amount, ROUND(AVG(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND CURRENT ROW), 2) AS average_amount, ROW_NUMBER() OVER(ORDER BY visited_on) AS rn FROM daily_amount ORDER BY visited_on ) SELECT visited_on, amount, average_amount FROM window_metrics WHERE rn >= 7
41. 好友申请 II :谁有最多的好友
-
题目描述:找出拥有好友数量最多的人。
-
解题思路:统计 ID 在申请方和接受方出现的总数。
42. 2016 年的投资
-
题目描述:统计满足条件的投资总额:1. 2015年投资额相同;2. 经纬度位置唯一。
-
解题思路:窗口函数计算出tiv_count和loc_count然后用where过滤袋掉不符合条件的
43. 部门工资前三高的所有员工
-
题目描述:找出每个部门中工资排名前三的员工。
-
解题思路 :使用密集排名,
DENSE_RANK(),不跳过。 -
代码:
sql# Write your MySQL query statement below nvb WITH employee_rank AS ( SELECT d.name AS Department, e.name AS Employee, e.salary AS salary, DENSE_RANK() OVER(PARTITION BY e.departmentId ORDER BY e.salary DESC) AS sale_rank FROM Employee e JOIN Department d ON e.departmentId = d.id ) SELECT Department, Employee, salary FROM employee_rank WHERE sale_rank <= 3;
七、 高级字符串函数 / 正则表达式 / 子句 (String & Regex)
44. 修复表中的名字
-
题目描述:将名字修复为首字母大写,其余小写。
-
解题思路 :字符串截取并转换大小写后拼接。
CONCAT,UPPER,LOWER,SUBSTR。 -
代码:
sql# Write your MySQL query statement below SELECT user_id, CONCAT ( UPPER(LEFT(name, 1)), LOWER(SUBSTR(name, 2)) ) AS name FROM Users ORDER BY user_id;
45. 患某种疾病的患者
-
题目描述:找出患有以 "DIAB1" 开头的疾病的患者。
-
解题思路:注意 "DIAB1" 可能是字符串开头,也可能是某个单词的开头。
-
特殊语句 :
LIKE 'DIAB1%' OR LIKE '% DIAB1%'。
46. 删除重复的电子邮箱
-
题目描述:删除重复的邮箱记录,仅保留 ID 最小的那一条。
-
解题思路:自连接删除 ID 较大的重复项。
-
代码:
sqlDELETE p1 FROM Person p1 JOIN Person p2 ON p1.email = p2.email WHERE p1.id > p2.id;
47. 第二高的薪水
-
题目描述:找出第二高的薪水,如果没有则返回 null。
-
解题思路 :排序、去重并偏移取值,
LIMIT 1 OFFSET 1。 -
代码:
sql# Write your MySQL query statement below SELECT ( SELECT DISTINCT salary FROM Employee ORDER BY salary DESC LIMIT 1 OFFSET 1 ) AS SecondHighestSalary;
48. 按日期分组销售产品
-
题目描述:找出每个日期销售的产品种类数及其名称(按字典序排列并逗号分隔)。
-
解题思路 :分组并进行组内字符串聚合,
GROUP_CONCAT。 -
代码:
sql# Write your MySQL query statement below SELECT sell_date, COUNT(DISTINCT product) AS num_sold, GROUP_CONCAT(DISTINCT product ORDER BY product SEPARATOR ',') AS products FROM Activities GROUP BY sell_date ORDER BY sell_date;
49. 列出指定时间段内所有的下单产品
-
题目描述:找出 2020 年 2 月下单总量不少于 100 的产品名称。
-
解题思路:日期过滤,分组求和。
50. 查找拥有有效邮箱的用户
-
题目描述:找出邮箱前缀以字母开头,后缀为 @leetcode.com 的有效邮箱。
-
解题思路:使用正则表达式匹配复杂模式。
-
代码:
sqlSELECT user_id, name, mail FROM Users WHERE REGEXP_LIKE(mail, '^[a-zA-Z][a-zA-Z0-9_.-]*@leetcode\\.com$', 'c');