SQL 高频面试题 Top 20

这里是为你精心整理的 SQL 高频面试题 Top 20。

这些题目由浅入深,覆盖了从基础查询到高级分析的绝大部分核心考点。


基础与筛选 (1-5)

1. WHERE条件查询

  • 1. 思路

这是 SQL 的基本功。就像在 Excel 中使用筛选功能一样,WHERE 子句用于从表中提取满足特定条件的记录。核心是理解各种比较运算符(=, >, <)、逻辑运算符(AND, OR, NOT)的用法。

  • 2. 题目

在 employees 员工表中,查询所有部门为 'Sales' 且年龄大于 30 岁的员工姓名和年龄。

  • 3. 示例与记忆点
SELECT name, age
FROM employees
WHERE department = 'Sales' AND age > 30;

记忆点:WHERE 是“先行官”,在数据被分组或聚合之前,就先把不符合条件的行过滤掉了。


2. LIKE模糊查询

  • 1. 思路

当需要查找部分匹配的文本时,LIKE 就派上用场了。它一般配合两个通配符使用:%(代表任意多个字符)和 _(代表单个字符)。

  • 2. 题目

在 products 产品表中,查询所有产品名称以 'Pro' 开头的产品。

  • 3. 示例与记忆点
SELECT product_name
FROM products
WHERE product_name LIKE 'Pro%';

记忆点:% 像一个可以伸缩的“万能口袋”,能装下任意字符;_ 像一个“占位符”,只能站一个位置。


3. ORDER BY排序

  • 1. 思路

查询结果默认是无序的。ORDER BY 用于对结果集进行排序。ASC 是升序(默认,可省略),DESC 是降序。可以按多列排序,前面是主排序,后面是次排序。

  • 2. 题目

查询所有员工信息,并按工资从高到低排序,如果工资一样,则按入职日期从早到晚排序。

  • 3. 示例与记忆点
SELECT *
FROM employees
ORDER BY salary DESC, hire_date ASC;

记忆点:ORDER BY 是“总指挥”,在所有查询完成后,对最终结果进行“排队”。


4. LIMIT/ TOP分页

  • 1. 思路

当查询结果有成千上万条时,我们一般只需要看前几条。LIMIT (MySQL/PostgreSQL) 或 TOP (SQL Server) 用于限制返回的行数。常用于分页查询。

  • 2. 题目

查询工资最高的前 3 名员工的姓名和工资。

  • 3. 示例与记忆点
-- MySQL/PostgreSQL 写法
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
-- SQL Server 写法
SELECT TOP 3 name, salary
FROM employees
ORDER BY salary DESC;

记忆点:LIMIT 是“门卫”,只让指定数量的记录“出门”。


5. NULL值处理

  • 1. 思路

NULL 表明“未知”或“没有值”,它不等于 0、空字符串或任何其他值。判断一个值是否为 NULL,必须用 IS NULL 或 IS NOT NULL,不能用 =。COALESCE 函数可以用来将 NULL 转换为一个默认值。

  • 2. 题目

查询所有没有分配部门的员工姓名,并将他们的奖金(bonus,可能为 NULL)显示为 0。

  • 3. 示例与记忆点
SELECT name, COALESCE(bonus, 0) AS bonus
FROM employees
WHERE department_id IS NULL;

记忆点:NULL 是个“薛定谔的猫”,你不能说它“是”什么,只能说它“是”或“不是” NULL。COALESCE 是“替补队员”,主力(NULL)不上场它就顶上。


聚合与分组 (6-9)

6. GROUP BY分组统计

  • 1. 思路

GROUP BY 是数据分析的核心。它把具有一样值的行分成一组,然后对每一组使用聚合函数(如 COUNT, SUM, AVG)进行计算。可以想象成把一堆杂乱的卡片,按花色(红桃、黑桃等)分成几摞。

  • 2. 题目

在 orders 订单表中,统计每个客户的总订单金额。

  • 3. 示例与记忆点
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id;

记忆点:GROUP BY 是“班长”,把同学(行)按小组(列值)分好,然后聚合函数(SUM等)来统计每个小组的成绩。


7. HAVING分组后过滤

  • 1. 思路

WHERE 过滤行,HAVING 过滤组。HAVING 在 GROUP BY 之后执行,用于对聚合后的结果进行筛选。列如,找出“平均成绩大于 60 分”的班级。

  • 2. 题目

找出总订单金额超过 10000 的客户。

  • 3. 示例与记忆点
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 10000;

记忆点:WHERE 是“进门安检”,没资格的行直接被拦住。HAVING 是“分组评比”,小组成立后,不达标的小组被淘汰。


8. COUNT的几种用法

  • 1. 思路

COUNT(*)、COUNT(1) 和 COUNT(列名) 有细微差别。

    • COUNT(*) 和 COUNT(1):统计所有行的数量,包括 NULL 行。性能上现代数据库优化器几乎没差别。
    • COUNT(列名):统计指定列中非 NULL 值的数量。
  • 2. 题目

假设 students 表有一列 email(可能为空)。查询学生总数,以及有邮箱的学生数量。

  • 3. 示例与记忆点
SELECT
  COUNT(*) AS total_students,      -- 所有学生
  COUNT(email) AS students_with_email -- 有邮箱的学生
FROM students;

记忆点:COUNT(*) 是“数人头”,来了一个就数一个。COUNT(列名) 是“点名”,只有答“到”(非NULL)的才数。


连接查询 (9-12)

9. INNER JOIN内连接

  • 1. 思路

获取两个表中能匹配上的行的交集。就像两个集合的交集,只有同时在两个集合里的元素才会被选中。

  • 2. 题目

查询所有下了订单的客户的姓名和他们的订单金额。(客户表 customers 和订单表 orders)

  • 3. 示例与记忆点
SELECT c.name, o.amount
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;

记忆点:INNER JOIN 是“双向奔赴”,只有两边都有对应关系,才能在一起。


10. LEFT JOIN左连接

  • 1. 思路

获取左表的所有行,以及右表中能匹配上的行。如果右表没有匹配,则右表的列显示为 NULL。这是面试中的绝对高频考点!

  • 2. 题目

查询所有客户的姓名及其订单信息,包括那些没有下过订单的客户。

  • 3. 示例与记忆点
SELECT c.name, o.amount
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;

记忆点:LEFT JOIN 是“霸道总裁”,左表(customers)的人我全都要,右表(orders)有匹配的就带上,没有的就留空(NULL)。


11. RIGHT JOIN与 FULL OUTER JOIN

  • 1. 思路
    • RIGHT JOIN:和 LEFT JOIN 相反,以右表为基准,获取右表所有行及左表匹配行。
    • FULL OUTER JOIN:获取两个表的并集,无论是否匹配,所有行都显示,没有匹配的部分用 NULL 填充。
  • 2. 题目

查询所有员工和所有部门,即使员工没有部门或部门没有员工。

  • 3. 示例与记忆点
SELECT e.name, d.department_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;

记忆点:RIGHT JOIN 是 LEFT JOIN 的镜像。FULL OUTER JOIN 是“海纳百川”,两边的所有记录都保留,像把 LEFT JOIN 和 RIGHT JOIN 的结果加起来去重。


12. 自连接

  • 1. 思路

一张表和它自己进行连接。一般用于处理表内存在层级关系的数据,如员工-经理、学生-班主任、评论-回复评论。

  • 2. 题目

在 employees 表中,manager_id 是其经理的 id。查询所有员工姓名及其对应的经理姓名。

  • 3. 示例与记忆点
SELECT
    e.name AS employee_name,
    m.name AS manager_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

记忆点:自连接就像“照镜子”,把一张表复制成两份(一份当员工表 e,一份当经理表 m),然后按关系连接。


子查询与 CTE (13-15)

13. 子查询

  • 1. 思路

一个查询嵌套在另一个查询内部。子查询可以出目前 WHERE、FROM、SELECT 子句中。WHERE 子句中的子查询常与 IN 或 EXISTS 结合使用。

  • 2. 题目

查询工资高于公司平均工资的员工姓名。

  • 3. 示例与记忆点
SELECT name
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

记忆点:子查询是“套娃”,先执行内层的查询,得到一个结果,再把这个结果用于外层的查询。


14. EXISTSvs IN

  • 1. 思路
    • IN:用于检查某个值是否存在于子查询返回的列表中。先执行子查询,生成一个临时列表。
    • EXISTS:用于检查子查询是否返回任何行。它不关心返回什么,只关心有没有返回。一般性能更好,特别是当子查询结果集很大时。
  • 2. 题目

查询所有下过订单的客户姓名。

  • 3. 示例与记忆点
-- 使用 IN
SELECT name FROM customers
WHERE id IN (SELECT DISTINCT customer_id FROM orders);
-- 使用 EXISTS (更推荐)
SELECT c.name FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

记忆点:IN 是“点名”,先拿到全班名单(子查询结果),再一个个看你在不在。EXISTS 是“按学号找人”,拿着你的学号(c.id)去订单表里找,找到一个就立刻返回 true,效率高。


15. CTE (公用表表达式)

  • 1. 思路

使用 WITH 关键字创建一个临时的、命名的结果集,它只在当前查询中存在。CTE 让复杂的查询逻辑更清晰、更易读,是子查询的现取代代品。

  • 2. 题目

同样是查询工资高于公司平均工资的员工姓名,用 CTE 实现。

  • 3. 示例与记忆点
WITH AvgSalary AS (
  SELECT AVG(salary) AS avg_sal FROM employees
)
SELECT name
FROM employees, AvgSalary
WHERE employees.salary > AvgSalary.avg_sal;

记忆点:CTE 是“给临时结果起个名字”。把复杂的子查询先做好,放在 WITH 后面,像定义一个变量一样,后面直接引用这个“变量名”,代码更整洁。


高级应用 (16-20)

16. 窗口函数 – ROW_NUMBER()

  • 1. 思路

窗口函数可以在不改变原有行数的情况下,对数据进行分组、排序和计算。ROW_NUMBER() 为每行分配一个唯一的连续序号。常用于分组排名(Top-N)。

  • 2. 题目

在每个部门内,根据工资高低对员工进行排名。

  • 3. 示例与记忆点
SELECT
    name,
    department,
    salary,
    ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary DESC) AS rank_in_dept
FROM employees;

记忆点:窗口函数是“开个窗户看数据”。PARTITION BY 是“分组”,ORDER BY 是“组内排序”,ROW_NUMBER() 是“发编号”。


17. 窗口函数 – RANK()/ DENSE_RANK()

  • 1. 思路

与 ROW_NUMBER() 类似,但处理并列名次的方式不同。

    • RANK():并列后,会占用后续名次。如:1, 2, 2, 4。
    • DENSE_RANK():并列后,不占用后续名次。如:1, 2, 2, 3。
  • 2. 题目

对学生成绩进行排名,要求一样分数有一样名次。

  • 3. 示例与记忆点
SELECT
    student_name,
    score,
    RANK() OVER(ORDER BY score DESC) AS rank,
    DENSE_RANK() OVER(ORDER BY score DESC) AS dense_rank
FROM scores;

记忆点:RANK() 是“体育比赛排名”,两人并列第二,下一个就是第四。DENSE_RANK() 是“班级排名”,两人并列第二,下一个还是第三。


18. 窗口函数 – LAG()/ LEAD()

  • 1. 思路

LAG() 可以获取当前行某一行的值,LEAD() 可以获取当前行某一行的值。超级适合计算环比、同比等。

  • 2. 题目

查询每个月的销售额,以及与前一个月销售额的对比(增长率)。

  • 3. 示例与记忆点
SELECT
    month,
    sales,
    LAG(sales, 1) OVER(ORDER BY month) AS prev_month_sales,
    (sales - LAG(sales, 1) OVER(ORDER BY month)) / LAG(sales, 1) OVER(ORDER BY month) AS growth_rate
FROM monthly_sales;

记忆点:LAG() 是“回头看”,LEAD() 是“向前看”。


19. UNIONvs UNION ALL

  • 1. 思路

都用于合并两个或多个 SELECT 语句的结果集。

    • UNION:合并后会去重
    • UNION ALL:合并后不去重,只是简单地把结果集堆在一起。性能更好。
  • 2. 题目

假设有两个表:old_employees 和 new_employees,结构一样。查询所有员工,不重复。

  • 3. 示例与记忆点
SELECT name, department FROM old_employees
UNION
SELECT name, department FROM new_employees;

记忆点:UNION 是“合并同类项”,会做去重这个额外工作。UNION ALL 是“直接堆叠”,速度更快。如果确定没有重复或需要保留重复,就用 UNION ALL。


20. 删除重复数据

  • 1. 思路

这是一个经典操作题。思路是先找到哪些行是重复的,然后保留其中一行,删除其他行。常用 ROW_NUMBER() 窗口函数来标记重复行。

  • 2. 题目

删除 logs 表中重复的日志记录(假设 log_date 和 message 一样即为重复),只保留最早的一条。

  • 3. 示例与记忆点
-- 使用 CTE 和 ROW_NUMBER()
WITH CTE_Duplicates AS (
  SELECT
    *,
    ROW_NUMBER() OVER(PARTITION BY log_date, message ORDER BY id) AS rn
  FROM logs
)
DELETE FROM CTE_Duplicates WHERE rn > 1;

记忆点:给重复数据分组(PARTITION BY),然后按某个规则排序(ORDER BY),只保留第一名(rn = 1),删掉其他名次(rn > 1)。


希望这份整理能协助你轻松应对 SQL 面试!核心是理解每个操作背后的业务场景和逻辑,而不是死记硬背语法。祝你面试顺利!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共1条

请登录后发表评论