SQL 面试题
SQL 面试测试你是否能正确高效地表达数据查询。预计会现场编写查询语句,通常涉及连接、聚合和窗口函数。
SQL 面试涵盖内容
连接与过滤
内连接/左连接/右连接/全连接,WHERE 与 HAVING 的区别,以及自连接。
聚合
GROUP BY、聚合函数以及分组陷阱(NULL、重复)。
窗口函数
ROW_NUMBER、RANK、LAG/LEAD、累计总和以及每组前 N 名。
性能
索引、查询计划、可检索性以及查询慢的原因。
SQL 面试题示例
- 查找表中的第二高薪水。好回答应覆盖
- 使用子查询和 LIMIT/OFFSET
- 使用窗口函数 DENSE_RANK(处理并列)
- 避免的陷阱:没有考虑并列情况
查看范例答案
查找第二高薪水常见方法有子查询加 LIMIT/OFFSET 或窗口函数。子查询:SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1。但这在有并列时可能返回错误。更稳妥是用窗口函数 DENSE_RANK:SELECT salary FROM (SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk FROM employees) t WHERE rk = 2 LIMIT 1。注意要处理空值,如果不足两行返回 NULL。复杂度 O(n log n) 排序。
参考代码sql -- 方法1: LIMIT/OFFSET SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1; -- 方法2: 窗口函数(推荐) SELECT salary FROM ( SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk FROM employees ) t WHERE rk = 2 LIMIT 1; - 获取每组前 N 行(例如每个类别的前 3 个产品)。好回答应覆盖
- 窗口函数 ROW_NUMBER 分区排序
- 外层过滤前 N 行
- 效率与索引关系
查看范例答案
获取每组前 N 行常用窗口函数 ROW_NUMBER() 在分区内按指定列排序编号,然后外层选择编号 <= N 的行。例如:SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn FROM products) t WHERE rn <= 3。此查询需要全表扫描并排序,性能随数据量下降。若表很大且需要频繁查询,可考虑对 (category, sales) 建立复合索引以优化排序。时间复杂度 O(n log n) 每分组排序。
参考代码sql -- 每个类别销售额前三的产品 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn FROM products ) t WHERE rn <= 3; - 解释 WHERE 和 HAVING 的区别。好回答应覆盖
- WHERE 在分组前过滤行
- HAVING 在分组后过滤聚合结果
- 可与 GROUP BY 配合使用顺序
查看范例答案
WHERE 和 HAVING 都是过滤子句,但作用时机不同。WHERE 在数据分组前对行逐行过滤,不能使用聚合函数(如 SUM、AVG);HAVING 在分组后对分组结果过滤,可以使用聚合函数。执行顺序上,WHERE 先于 GROUP BY,HAVING 后于 GROUP BY。例如:查询销售额总和超过 1000 的部门,且部门所属区域为 'North'。WHERE region = 'North' 过滤行,GROUP BY department,HAVING SUM(sales) > 1000。常见陷阱:错误地在 WHERE 中使用聚合函数会导致语法错误。
- 编写查询找出重复行并仅保留一个。好回答应覆盖
- 使用 GROUP BY 和 HAVING COUNT(*) > 1 找出重复
- 使用窗口函数 ROW_NUMBER 删除其余行
- 事务中谨慎操作,备份数据
查看范例答案
查找重复行并保留一条的方法:先用 GROUP BY 找出有重复的组,再使用窗口函数 ROW_NUMBER() 为每组内的行编号,然后删除编号 > 1 的行。假设重复定义是某几列相同,例如 email。查询重复:SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1。删除仅保留一条:WITH cte AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users) DELETE FROM cte WHERE rn > 1。注意 ORDER BY 决定保留哪条(如最小 id)。执行前务必备份或先在事务中测试。
参考代码sql -- 查找重复行 SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1; -- 删除重复,保留id最小的那条 WITH cte AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users ) DELETE FROM cte WHERE rn > 1; - 计算每日收入的累计总和。好回答应覆盖
- 窗口函数 SUM() OVER (ORDER BY date) 实现累计和
- 处理日期缺失时的结果
- 性能与索引关系
查看范例答案
计算每日收入的累计总和,可使用窗口函数的累加和。基本语法:SELECT date, revenue, SUM(revenue) OVER (ORDER BY date) AS cumulative_revenue FROM daily_revenue。若要按其他分组累计,可加 PARTITION BY。注意:如果日期不连续,累计和依然正确累加已有天数。若需显示所有日期(包括无收入日),需用日期维度表左连接。性能上,ORDER BY date 需排序,在 date 列建索引可优化。复杂度 O(n log n) 排序。
参考代码sql SELECT date, revenue, SUM(revenue) OVER (ORDER BY date) AS cumulative_revenue FROM daily_revenue ORDER BY date; - 如何加速慢查询?查询计划告诉了你什么?好回答应覆盖
- EXPLAIN ANALYZE 查看执行计划
- 索引优化(扫描类型、索引选择)
- 常见慢查询原因:全表扫描、临时表、排序
查看范例答案
加速慢查询首先使用 EXPLAIN ANALYZE 获取执行计划,它显示查询步骤、每步耗时和行数。常见优化点:1)全表扫描 → 考虑加合适的索引(B+树索引支持等值、范围、排序)。2)排序操作(Using filesort)→ 利用索引预排序。3)临时表(Using temporary)→ 可能由于 GROUP BY 或 DISTINCT 没有索引,调整查询或加复合索引。4)查询计划中的 type 列:const > ref > range > index > ALL。5)连接查询中驱动表选择,用小表驱动大表。6)检查是否因星号(*)返回不必要列,改为需要列。实践:先看耗时最大节点针对性优化。
如何准备
- 练习手动编写查询——许多面试使用共享编辑器,没有自动补全。
- 掌握窗口函数;它们能解决大量的“每组前 N”和累计总和问题。
- 明确考虑 NULL 行为和重复值——它们是常见的陷阱。
- 准备好阅读查询计划并解释为什么索引有帮助。
常见问题
应该学习哪种 SQL 方言?
标准 SQL 涵盖大多数面试。注意窗口函数和 CTE 得到广泛支持;一些函数在 PostgreSQL、MySQL 和 SQL Server 之间有所不同。
SQL 面试中需要优化查询吗?
对于数据/后端岗位,是的——准备好讨论索引、查询计划以及查询慢的原因。
窗口函数常见吗?
非常常见。ROW_NUMBER/RANK 和累计总和经常出现,尤其是数据工程和分析岗位。
如何练习 SQL 面试?
针对真实 schema 解决查询问题并解释你的方法。Offersly 可以生成 SQL 相关题目并对你的推理评分。
练习 SQL 题目,即时获取 AI 反馈
上传简历,获得个性化模拟面试,并了解需要改进的地方——免费开始。