MySQL 子查询详解
·
子查询(Subquery)简单来说就是嵌套在另一个 SQL 语句中的查询语句,也叫嵌套查询。它可以把一个复杂的查询拆解成多个简单的查询,让逻辑更清晰。下面从基础到进阶,一步步讲解子查询的用法。
一、基础认知:什么是子查询?
先看一个最直观的例子,帮你建立基本概念:
假设我们有一张 student 表:
| id | name | score | class_id |
|---|---|---|---|
| 1 | 张三 | 90 | 1 |
| 2 | 李四 | 85 | 2 |
| 3 | 王五 | 95 | 1 |
| 4 | 赵六 | 80 | 2 |
1.1 最简单的子查询(标量子查询)
需求:查询分数高于平均分的学生。
第一步:先查平均分(单独执行)
SELECT AVG(score) FROM student; -- 结果:87.5
第二步:把上面的查询嵌套进去,作为条件
SELECT name, score
FROM student
WHERE score > (SELECT AVG(score) FROM student);
结果:
| name | score |
|---|---|
| 张三 | 90 |
| 王五 | 95 |
核心说明:
- 子查询需要用
()包裹,这是语法要求; - 这个子查询返回单个值(87.5),称为「标量子查询」,是最简单的子查询类型;
- 执行顺序:先执行括号内的子查询,再执行外层查询。
二、基础用法:子查询的常见场景
2.1 子查询作为 WHERE 条件(最常用)
场景 1:IN 子查询(子查询返回多个值)
需求:查询 1 班的所有学生(先查 1 班的 id,再查对应学生)
-- 子查询返回单个值时用 =,返回多个值时用 IN
SELECT name, score
FROM student
WHERE class_id IN (SELECT id FROM class WHERE class_name = '一班');
场景 2:NOT IN 子查询
需求:查询不是 1 班的学生
SELECT name, score
FROM student
WHERE class_id NOT IN (SELECT id FROM class WHERE class_name = '一班');
2.2 子查询作为字段(SELECT 子句中)
需求:查询每个学生的姓名、分数,以及班级名称(假设有 class 表:id、class_name)
SELECT
name,
score,
(SELECT class_name FROM class WHERE class.id = student.class_id) AS class_name
FROM student;
结果:
| name | score | class_name |
|---|---|---|
| 张三 | 90 | 一班 |
| 李四 | 85 | 二班 |
| 王五 | 95 | 一班 |
| 赵六 | 80 | 二班 |
说明:
- 这种子查询会为每一行外层查询结果匹配一个值,也叫「相关子查询」(子查询依赖外层的字段);
- 如果子查询返回多个值,会报错,必须保证返回单个值。
三、进阶用法:子查询的类型与高级场景
3.1 按返回结果分类(核心分类)
| 子查询类型 | 特点 | 常用运算符 | 例子 |
|---|---|---|---|
| 标量子查询 | 返回单个值(一行一列) | =、>、<、>=、<= | WHERE score > (SELECT AVG(score)...) |
| 列子查询 | 返回一列多行 | IN、NOT IN、ANY、ALL | WHERE class_id IN (SELECT id...) |
| 行子查询 | 返回一行多列 | =、IN | WHERE (id, score) = (SELECT id, MAX(score)...) |
| 表子查询 | 返回多行多列 | 作为临时表使用 | FROM (SELECT ...) AS temp |
3.2 关键运算符:ANY / ALL(列子查询进阶)
先补充数据:class 表新增 avg_score 字段(班级平均分):
| id | class_name | avg_score |
|---|---|---|
| 1 | 一班 | 92 |
| 2 | 二班 | 82 |
ANY:满足任意一个条件即可
需求:查询分数高于任意一个班级平均分的学生
SELECT name, score
FROM student
WHERE score > ANY (SELECT avg_score FROM class);
分析:班级平均分是 92 和 82,只要分数 > 82 就满足,结果是张三(90)、李四(85)、王五(95)。
ALL:满足所有条件
需求:查询分数高于所有班级平均分的学生
SELECT name, score
FROM student
WHERE score > ALL (SELECT avg_score FROM class);
分析:需要分数 > 92,只有王五(95)满足。
3.3 表子查询(子查询作为临时表)
需求:先筛选出分数≥85 的学生,再统计每个班级的高分人数
-- 子查询作为临时表,必须给别名(如 temp)
SELECT
class_id,
COUNT(*) AS high_score_count
FROM (SELECT id, name, score, class_id FROM student WHERE score >= 85) AS temp
GROUP BY class_id;
结果:
表格
| class_id | high_score_count |
|---|---|
| 1 | 2 |
| 2 | 1 |
说明:
- 表子查询返回的是一张临时表,外层查询可以像操作普通表一样操作它;
- 临时表必须指定别名(如上例的
temp),否则 MySQL 会报错。
3.4 相关子查询 vs 非相关子查询(核心区别)
非相关子查询:子查询不依赖外层,先执行子查询,结果供外层使用
-- 非相关子查询:先算平均分,再查高于平均分的学生
SELECT name, score FROM student WHERE score > (SELECT AVG(score) FROM student);
相关子查询:子查询依赖外层字段,外层执行一行,子查询就执行一次
-- 需求:查询每个班级分数最高的学生
SELECT s1.name, s1.score, s1.class_id
FROM student s1
WHERE score = (SELECT MAX(score) FROM student s2 WHERE s2.class_id = s1.class_id);
分析:
- 外层遍历每个学生(s1),子查询根据 s1 的 class_id,查该班级的最高分;
- 对比 s1 的分数是否等于该最高分,等于则保留。
四、实战优化:子查询的注意事项
-
性能问题:相关子查询因为外层每一行都要执行一次子查询,数据量大时性能差,可替换为 JOIN:
-- 原相关子查询(查各班最高分学生) SELECT s1.name, s1.score, s1.class_id FROM student s1 WHERE score = (SELECT MAX(score) FROM student s2 WHERE s2.class_id = s1.class_id); -- 替换为JOIN(性能更好) SELECT s1.name, s1.score, s1.class_id FROM student s1 JOIN (SELECT class_id, MAX(score) AS max_score FROM student GROUP BY class_id) s2 ON s1.class_id = s2.class_id AND s1.score = s2.max_score; -
空值处理:子查询返回空时,IN / NOT IN 可能出问题,建议用 EXISTS 替代:
-- 不推荐:子查询返回空时,NOT IN 结果为空 SELECT name FROM student WHERE class_id NOT IN (SELECT id FROM class WHERE class_name = '三班'); -- 推荐:EXISTS 更稳定 SELECT name FROM student s WHERE NOT EXISTS (SELECT 1 FROM class c WHERE c.id = s.class_id AND c.class_name = '三班'); -
EXISTS 子查询:只判断「是否存在满足条件的记录」,返回布尔值(true/false),效率高(找到第一条就停止):
-- 需求:查询有学生的班级 SELECT class_name FROM class c WHERE EXISTS (SELECT 1 FROM student s WHERE s.class_id = c.id);
总结
- 核心定义:子查询是嵌套在其他 SQL 中的查询,按返回结果可分为标量、列、行、表子查询,其中标量和列子查询最常用;
- 关键用法:WHERE 条件中用 IN/ANY/ALL,SELECT 中作为字段,FROM 中作为临时表(需别名);
- 性能优化:相关子查询性能差时优先用 JOIN 替代,判断存在性用 EXISTS 比 IN 更稳定。
通过以上步骤,你可以从「会用简单子查询」到「理解子查询类型并优化」,核心是先拆解需求,再选择合适的子查询类型,同时关注性能问题。
更多推荐

所有评论(0)