【数据库】SQL面试基础15题,测试/开发必背速查
SQL 面试基础题占面试八成以上很多候选人不是不会是答不全、答不顺。这篇把板块 1 的 15 道基础必问整理成可背的速查附执行顺序、易错点与答案框架建议收藏。本文目录一、SELECT 基本查询与执行顺序二、WHERE 与 HAVING 的区别必考三、JOIN 家族四、GROUP BY / ORDER BY / LIMIT 分页五、聚合函数与条件过滤六、自连接员工找领导七、板块1 易错点总结一、SELECT 基本查询与执行顺序语法SELECT 列 FROM 表 WHERE 条件 ORDER BY 列 LIMIT执行顺序高频考点务必背FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMITflowchart TD A[FROM 确定数据来源表] -- B[WHERE 过滤原始行] B -- C[GROUP BY 分组] C -- D[HAVING 过滤分组] D -- E[SELECT 投影列] E -- F[ORDER BY 排序] F -- G[LIMIT 限制条数]下面给出一个可直接运行的完整示例演示 SELECT 的完整执行流程-- 准备测试数据 CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept VARCHAR(20), salary INT ); INSERT INTO emp VALUES (1, 张三, 研发部, 8000), (2, 李四, 研发部, 6000), (3, 王五, 市场部, 5000), (4, 赵六, 市场部, 4000); -- 查询找出平均工资超过 5500 的部门按平均工资降序排列 SELECT dept, AVG(salary) AS avg_salary -- 第 5 步SELECT 投影 FROM emp -- 第 1 步FROM 确定表 WHERE salary 4500 -- 第 2 步WHERE 过滤行 GROUP BY dept -- 第 3 步GROUP BY 分组 HAVING AVG(salary) 5500 -- 第 4 步HAVING 过滤组 ORDER BY avg_salary DESC -- 第 6 步ORDER BY 排序 LIMIT 1; -- 第 7 步LIMIT 限制条数 -- 结果研发部 | 7000二、WHERE 与 HAVING 的区别必考对比维度WHEREHAVING执行时机分组前过滤行分组后过滤组能否使用聚合函数不能如 COUNT、SUM、AVG能如 COUNT(*) 1作用对象原始数据行分组后的结果集与 GROUP BY 的关系在 GROUP BY 之前执行在 GROUP BY 之后执行是否必须搭配 GROUP BY否可单独使用通常搭配 GROUP BY 使用典型场景筛选 salary 5000 的行筛选人数大于 1 的部门下面通过一个可运行的对比示例直观展示两者的区别-- 准备测试数据 CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept VARCHAR(20), salary INT ); INSERT INTO emp VALUES (1, 张三, 研发部, 8000), (2, 李四, 研发部, 6000), (3, 王五, 市场部, 5000), (4, 赵六, 市场部, 4000); -- 场景 1WHERE 在分组前过滤行先筛掉低薪员工再分组 SELECT dept, COUNT(*) AS cnt FROM emp WHERE salary 5000 -- WHERE 先过滤只保留 8000、6000、5000 GROUP BY dept; -- 结果研发部 2 人市场部 1 人 -- 场景 2HAVING 在分组后过滤组先分组再筛掉人数少的组 SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept HAVING COUNT(*) 1; -- HAVING 后过滤只保留人数大于 1 的组 -- 结果研发部 2 人市场部 2 人 -- 场景 3WHERE 和 HAVING 组合使用先过滤行再过滤组 SELECT dept, COUNT(*) AS cnt FROM emp WHERE salary 4500 -- 先过滤去掉 4000 GROUP BY dept HAVING COUNT(*) 1; -- 再过滤只保留人数大于 1 的组 -- 结果研发部 2 人市场部 1 人三、JOIN 家族连接类型保留行范围无匹配时结果MySQL 是否原生支持典型使用场景INNER JOIN只保留两表匹配的行不保留无匹配行是取两表交集数据如订单与客户匹配LEFT JOIN保留左表全部行右表只取匹配行右表列填 NULL是以左表为主查右表补充信息如员工及其部门RIGHT JOIN保留右表全部行左表只取匹配行左表列填 NULL是以右表为主查左表补充信息FULL JOIN保留两表全部行无匹配一侧填 NULL否用 UNION 模拟合并两表全量数据找出两侧不匹配的记录下面通过一个完整的实战示例直观对比四种连接的区别。先准备两张表员工表 emp 和部门表 dept。-- 建表语句 CREATE TABLE dept ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50) ); CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT ); -- 示例数据 INSERT INTO dept VALUES (1, 研发部), (2, 市场部), (3, 财务部); INSERT INTO emp VALUES (101, 张三, 1), (102, 李四, 1), (103, 王五, 2), (104, 赵六, NULL);1. INNER JOIN 内连接只返回两表匹配的行赵六无部门和财务部无员工都不会出现。SELECT e.emp_name, d.dept_name FROM emp e INNER JOIN dept d ON e.dept_id d.dept_id; -- 结果张三-研发部、李四-研发部、王五-市场部2. LEFT JOIN 左连接保留左表 emp 全部行赵六没有匹配部门dept_name 显示为 NULL。SELECT e.emp_name, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id; -- 结果张三-研发部、李四-研发部、王五-市场部、赵六-NULL3. RIGHT JOIN 右连接保留右表 dept 全部行财务部没有匹配员工emp_name 显示为 NULL。SELECT e.emp_name, d.dept_name FROM emp e RIGHT JOIN dept d ON e.dept_id d.dept_id; -- 结果张三-研发部、李四-研发部、王五-市场部、NULL-财务部4. FULL JOIN 全连接MySQL 不原生支持用 LEFT JOIN 和 RIGHT JOIN 的 UNION 模拟保留两表全部行无匹配一侧为 NULL。SELECT e.emp_name, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id UNION SELECT e.emp_name, d.dept_name FROM emp e RIGHT JOIN dept d ON e.dept_id d.dept_id; -- 结果张三-研发部、李四-研发部、王五-市场部、赵六-NULL、NULL-财务部四、GROUP BY / ORDER BY / LIMIT 分页GROUP BY 分组统计ORDER BY 排序DESC 降序LIMIT 分页LIMIT 10 OFFSET 20 取第 21-30 条。下面给出一个可运行的完整示例演示分组、排序和分页的组合使用-- 准备测试数据 CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept VARCHAR(20), salary INT ); INSERT INTO emp VALUES (1, 张三, 研发部, 8000), (2, 李四, 研发部, 6000), (3, 王五, 市场部, 5000), (4, 赵六, 市场部, 4000), (5, 孙七, 财务部, 7000), (6, 周八, 财务部, 6500); -- 示例 1GROUP BY 分组统计各部门人数和平均工资 SELECT dept, COUNT(*) AS emp_cnt, -- 统计每组人数 AVG(salary) AS avg_salary -- 计算每组平均工资 FROM emp GROUP BY dept; -- 按部门分组 -- 结果研发部 2 人 7000市场部 2 人 4500财务部 2 人 6750 -- 示例 2ORDER BY 按平均工资降序排序 SELECT dept, AVG(salary) AS avg_salary FROM emp GROUP BY dept ORDER BY avg_salary DESC; -- DESC 降序排列 -- 结果研发部 7000 → 财务部 6750 → 市场部 4500 -- 示例 3LIMIT 分页查询每页 2 条取第 2 页 SELECT emp_id, emp_name, salary FROM emp ORDER BY salary DESC -- 先按工资排序 LIMIT 2 OFFSET 2; -- 跳过前 2 条取 2 条第 3、4 名 -- 结果孙七 7000、李四 6000五、聚合函数与条件过滤COUNT、SUM、AVG、MAX、MINLIKE 模糊张%、%三%、王_IN / BETWEEN / IS NULL 范围与空值。下面给出一个可运行的完整示例演示聚合函数和条件过滤的组合使用-- 准备测试数据 CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept VARCHAR(20), salary INT ); INSERT INTO emp VALUES (1, 张三, 研发部, 8000), (2, 李四, 研发部, 6000), (3, 王五, 市场部, 5000), (4, 赵六, 市场部, 4000), (5, 孙七, 财务部, 7000), (6, 周八, 财务部, NULL); -- 周八工资未知 -- 示例 1聚合函数统计 SELECT COUNT(*) AS total_cnt, -- 总行数6 COUNT(salary) AS salary_cnt,-- 非空工资数5NULL 不计入 SUM(salary) AS total_salary,-- 工资总和30000 AVG(salary) AS avg_salary, -- 平均工资6000只算非空 MAX(salary) AS max_salary, -- 最高工资8000 MIN(salary) AS min_salary -- 最低工资4000 FROM emp; -- 示例 2LIKE 模糊匹配 SELECT emp_name FROM emp WHERE emp_name LIKE 张%; -- 以张开头张三 -- 结果张三 SELECT emp_name FROM emp WHERE emp_name LIKE %三%; -- 包含三张三 -- 结果张三 SELECT emp_name FROM emp WHERE emp_name LIKE 王_; -- 王后跟一个字符王五 -- 结果王五 -- 示例 3IN / BETWEEN / IS NULL 条件过滤 SELECT emp_name, salary FROM emp WHERE dept IN (研发部, 财务部); -- 指定多个值 -- 结果张三、李四、孙七、周八 SELECT emp_name, salary FROM emp WHERE salary BETWEEN 5000 AND 7000; -- 范围 5000-7000含边界 -- 结果李四 6000、王五 5000、孙七 7000 SELECT emp_name FROM emp WHERE salary IS NULL; -- 判断空值不能用 NULL -- 结果周八六、自连接员工找领导自连接是把一张表当作两张表来连接常用于层级关系查询。下面给出一个可运行的完整示例-- 准备测试数据员工表leader_id 指向本表的 emp_id CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), leader_id INT -- 领导的 emp_idNULL 表示没有领导 ); INSERT INTO emp VALUES (1, 张三, NULL), -- 张三没有领导老板 (2, 李四, 1), -- 李四的领导是张三 (3, 王五, 1), -- 王五的领导是张三 (4, 赵六, 2); -- 赵六的领导是李四 -- 自连接查询每个员工及其领导 SELECT e.emp_name AS employee, -- 员工姓名 m.emp_name AS leader -- 领导姓名 FROM emp e -- 员工表别名 e LEFT JOIN emp m ON e.leader_id m.emp_id; -- 领导表别名 m用 leader_id 关联 -- 结果张三-NULL、李四-张三、王五-张三、赵六-李四 -- 注意LEFT JOIN 保证张三无领导也会出现在结果中七、板块1 易错点总结1、执行顺序记反最常见翻车点。2、WHERE 误用聚合函数。3、LEFT JOIN 后误加 WHERE 右表条件导致变内连接。4、HAVING 与 WHERE 混淆。5、NULL 判断用 IS NULL 而非 NULL。下面通过一个可运行的对比示例直观展示误加 WHERE 条件后结果集如何变化-- 准备测试数据员工表 emp 和部门表 dept CREATE TABLE dept ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50) ); CREATE TABLE emp ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT ); INSERT INTO dept VALUES (1, 研发部), (2, 市场部), (3, 财务部); INSERT INTO emp VALUES (101, 张三, 1), (102, 李四, 1), (103, 王五, 2), (104, 赵六, NULL); -- 正确写法LEFT JOIN 保留左表全部行赵六无部门也会出现 SELECT e.emp_name, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id; -- 结果张三-研发部、李四-研发部、王五-市场部、赵六-NULL -- 错误写法在 WHERE 中误加右表条件赵六被过滤掉LEFT JOIN 退化为 INNER JOIN SELECT e.emp_name, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id WHERE d.dept_name 研发部; -- 错误右表条件放 WHERENULL 不满足条件被剔除 -- 结果张三-研发部、李四-研发部赵六-NULL 被过滤王五-市场部也被过滤 -- 原因WHERE 在 JOIN 之后执行右表无匹配时 dept_name 为 NULL -- NULL 与 研发部 比较结果为 UNKNOWN该行被过滤LEFT JOIN 效果消失。 -- 正确做法若想过滤右表条件且保留左表全部行应把条件放进 ON 子句 SELECT e.emp_name, d.dept_name FROM emp e LEFT JOIN dept d ON e.dept_id d.dept_id AND d.dept_name 研发部; -- 结果张三-研发部、李四-研发部、王五-NULL、赵六-NULL板块1是地基板块2窗口函数/索引/事务与板块3真实场景题后续更新关注不迷路。