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 的区别(必考)
| 对比维度 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组前过滤行 | 分组后过滤组 |
| 能否使用聚合函数 | 不能(如 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); -- 场景 1:WHERE 在分组前过滤行(先筛掉低薪员工再分组) SELECT dept, COUNT(*) AS cnt FROM emp WHERE salary > 5000 -- WHERE 先过滤:只保留 8000、6000、5000 GROUP BY dept; -- 结果:研发部 2 人,市场部 1 人 -- 场景 2:HAVING 在分组后过滤组(先分组再筛掉人数少的组) SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept HAVING COUNT(*) > 1; -- HAVING 后过滤:只保留人数大于 1 的组 -- 结果:研发部 2 人,市场部 2 人 -- 场景 3:WHERE 和 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); -- 示例 1:GROUP BY 分组统计各部门人数和平均工资 SELECT dept, COUNT(*) AS emp_cnt, -- 统计每组人数 AVG(salary) AS avg_salary -- 计算每组平均工资 FROM emp GROUP BY dept; -- 按部门分组 -- 结果:研发部 2 人 7000,市场部 2 人 4500,财务部 2 人 6750 -- 示例 2:ORDER BY 按平均工资降序排序 SELECT dept, AVG(salary) AS avg_salary FROM emp GROUP BY dept ORDER BY avg_salary DESC; -- DESC 降序排列 -- 结果:研发部 7000 → 财务部 6750 → 市场部 4500 -- 示例 3:LIMIT 分页查询(每页 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、MIN;LIKE 模糊(张%、%三%、王_);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,-- 非空工资数:5(NULL 不计入) 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; -- 示例 2:LIKE 模糊匹配 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 '王_'; -- "王"后跟一个字符:王五 -- 结果:王五 -- 示例 3:IN / 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_id,NULL 表示没有领导 ); 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 = '研发部'; -- 错误:右表条件放 WHERE,NULL 不满足条件被剔除 -- 结果:张三-研发部、李四-研发部(赵六-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(真实场景题)后续更新,关注不迷路。