LeetCode SQL 50 题题解
该教程参考 LeetCode 官方精选的高频 SQL 50 题(基础版)进行整理。这些题目涵盖了从基础查询到进阶连接、子查询及窗口函数的各类核心考点,是应对数据库面试的极佳练习材料。
为什么刷 SQL 题很重要?
在实际工作中,数据查询是后端开发、数据分析、算法工程师等岗位的日常需求。无论是业务数据统计、用户行为分析,还是系统性能监控,都离不开 SQL。面试中考察 SQL,主要看以下几点:
- 基础语法掌握程度:能否熟练使用
SELECT、WHERE、GROUP BY、HAVING等基础语句 - 多表关联能力:理解各种
JOIN的区别,能根据业务场景选择合适的连接方式 - 复杂查询思维:能否用子查询、窗口函数解决复杂的统计问题
- 代码规范性:可读性、性能考量、边界情况处理
SQL 知识体系概览
核心知识点
SQL 的知识体系可以分为八个核心模块,它们从基础到高级层层递进:
难度分布
| 难度级别 | 题目数量 | 核心考点 |
|---|---|---|
| 简单 | 约 20 题 | 基础查询、简单 JOIN、聚合函数 |
| 中等 | 约 25 题 | 多表连接、子查询、窗口函数 |
| 困难 | 约 5 题 | 复杂窗口函数、多层嵌套子查询 |
SQL 执行顺序
理解 SQL 的执行顺序对于编写复杂查询至关重要。与书写顺序不同,SQL 引擎按以下顺序处理查询:
这个顺序解释了为什么在 WHERE 中不能使用 SELECT 中定义的别名(因为 WHERE 在 SELECT 之前执行),也解释了为什么聚合函数的过滤必须用 HAVING 而非 WHERE(因为 HAVING 在 GROUP BY 之后执行)。
示例理解执行顺序:
SELECT department_id, AVG(salary) AS avg_salary -- 7. 选择列(这里可以使用别名)
FROM employees -- 1. 确定数据来源
WHERE status = 'active' -- 4. 过滤行(这里不能用 avg_salary)
GROUP BY department_id -- 5. 分组
HAVING AVG(salary) > 10000 -- 6. 分组后过滤(这里可以用聚合函数)
ORDER BY avg_salary DESC -- 9. 排序(这里可以用别名)
LIMIT 5; -- 10. 限制结果
学习路径建议
第一阶段:夯实基础
先掌握最基本的查询语法,这是所有复杂查询的基石:
- SELECT 语句:理解列选择、别名、去重
- WHERE 条件:熟练使用各种比较和逻辑运算符
- ORDER BY 排序:单字段、多字段排序
- 聚合函数:
COUNT、SUM、AVG、MAX、MIN
练习重点:select.md 和 aggregate-functions.md 中的题目
第二阶段:掌握连接
JOIN 是 SQL 的灵魂,理解连接的本质是理解"表间关系":
- INNER JOIN:只返回两表都有匹配的行
- LEFT JOIN:左表全保留,右表无匹配则为 NULL
- 自连接:同一表自己连接自己,常用于层级数据
- 连接条件:理解
ON子句的作用
关键理解:LEFT JOIN ... WHERE right.id IS NULL 是找出"在左表但不在右表"的经典模式
练习重点:joins.md 中的题目
第三阶段:分组与聚合
分组是数据统计的核心:
- GROUP BY 原理:将数据分成多个"桶",每个桶进行聚合计算
- HAVING vs WHERE:
WHERE在分组前过滤(行级过滤)HAVING在分组后过滤(组级过滤)
- 条件聚合:
SUM(IF(condition, value, 0))或SUM(condition)(MySQL 特有)
练习重点:sorting-and-grouping.md 和 aggregate-functions.md
第四阶段:子查询与窗口函数
这是从"会写 SQL"到"精通 SQL"的分水岭:
-
子查询位置:
SELECT子句:标量子查询FROM子句:派生表WHERE子句:IN、EXISTS、比较运算
-
窗口函数(面试高频):
ROW_NUMBER():连续编号,无并列RANK():并列跳号DENSE_RANK():并列不跳号LAG()/LEAD():获取前后行的值
窗口函数核心语法:
函数名() OVER (
PARTITION BY 分组列 -- 可选,类似 GROUP BY
ORDER BY 排序列 -- 排序规则
ROWS BETWEEN ... -- 可选,窗口范围
)
练习重点:subqueries.md 和 advanced-select-and-joins.md
常见易错点
1. NULL 值处理
-- 错误:使用 = 判断 NULL
WHERE bonus = NULL -- 永远不成立
-- 正确:使用 IS NULL
WHERE bonus IS NULL
-- NULL 参与运算结果也是 NULL
SELECT NULL + 1; -- 结果是 NULL
-- 使用 IFNULL 或 COALESCE 处理
SELECT IFNULL(bonus, 0);
SELECT COALESCE(phone, mobile, 'N/A');
2. WHERE vs HAVING
-- 错误:在 WHERE 中使用聚合函数
SELECT department, COUNT(*)
FROM employees
WHERE COUNT(*) > 5 -- 报错!
GROUP BY department;
-- 正确:使用 HAVING
SELECT department, COUNT(*)
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;
3. JOIN 条件遗漏
-- 错误:忘记连接条件,产生笛卡尔积
SELECT * FROM orders, customers; -- 结果行数 = orders × customers
-- 正确:添加连接条件
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id;
4. 排名函数混淆
| 函数 | 相同值 | 示例结果 |
|---|---|---|
ROW_NUMBER() | 不同编号 | 1, 2, 3, 4, 5 |
RANK() | 相同编号,跳号 | 1, 2, 2, 4, 5 |
DENSE_RANK() | 相同编号,不跳号 | 1, 2, 2, 3, 4 |
选择建议:
- 需要唯一编号:用
ROW_NUMBER() - 需要真实排名(有空缺):用
RANK() - 需要连续排名(无空缺):用
DENSE_RANK()
学习建议
- 先理解再动手:每道题先思考解题思路,再写代码
- 多解法对比:同一道题尝试用子查询、JOIN、窗口函数等不同方式实现
- 关注性能:了解索引、避免全表扫描
- 总结归纳:按题型整理解题模板
教程目录
1. 基础查询 (Select)
- 查询 - [1757, 584, 595, 1148, 1683]
2. 连接查询 (Joins)
- 连接 - [1378, 1068, 1581, 197, 1661, 577, 1280, 570, 1934]
3. 聚合与分组 (Aggregate Functions)
- 聚合函数 - [620, 1251, 1075, 1633, 1211, 1193, 1174, 550]
- 排序和分组 - [2356, 1141, 1084, 596, 1729, 619, 1045]
4. 进阶查询
- 高级查询和连接 - [1731, 1789, 610, 180, 1164, 1204, 1907]
- 子查询 - [1978, 626, 1341, 1321, 602, 585, 601] (注:部分题目包含 部门工资前三高)
5. 窗口函数(面试高频)
- 窗口函数详解 - 排名函数、偏移函数、聚合窗口、窗口帧详解
6. 综合技巧
- 高级字符串函数 / 正则表达式 / 子句 - [1667, 1527, 196, 176, 1484, 1327, 1517]
学习建议
- 掌握基础语法:理解
WHEREvsHAVING,JOIN的各种类型(INNER, LEFT, RIGHT, FULL)。 - 窗口函数:大部分中等及以上难度的题目(如“部门工资前三高”)可以使用窗口函数
RANK(),DENSE_RANK(),ROW_NUMBER()极大地简化逻辑。 - 分逻辑处理:复杂的 SQL 建议先写子查询理清中间逻辑,再进行外层组装。