子查询(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 的分数是否等于该最高分,等于则保留。

四、实战优化:子查询的注意事项

  1. 性能问题:相关子查询因为外层每一行都要执行一次子查询,数据量大时性能差,可替换为 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;
    
  2. 空值处理:子查询返回空时,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 = '三班');
    
  3. EXISTS 子查询:只判断「是否存在满足条件的记录」,返回布尔值(true/false),效率高(找到第一条就停止):

    -- 需求:查询有学生的班级
    SELECT class_name FROM class c
    WHERE EXISTS (SELECT 1 FROM student s WHERE s.class_id = c.id);
    

总结

  1. 核心定义:子查询是嵌套在其他 SQL 中的查询,按返回结果可分为标量、列、行、表子查询,其中标量和列子查询最常用;
  2. 关键用法:WHERE 条件中用 IN/ANY/ALL,SELECT 中作为字段,FROM 中作为临时表(需别名);
  3. 性能优化:相关子查询性能差时优先用 JOIN 替代,判断存在性用 EXISTS 比 IN 更稳定。

通过以上步骤,你可以从「会用简单子查询」到「理解子查询类型并优化」,核心是先拆解需求,再选择合适的子查询类型,同时关注性能问题。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐