MySQL 子查询指的是在一个完整的 SQL 查询语句中,嵌套另一个或多个完整的查询语句。被嵌套的查询会先得到结果,这个结果再作为外层查询的条件、数据来源或比较对象。子查询可以出现在 SELECT、FROM、WHERE、HAVING 等子句中,根据返回结果的不同,通常可以分为标量子查询、列子查询、行子查询和表子查询四类。理解这些返回结果的形态,是正确使用子查询的前提,因为不同位置和操作符对子查询返回的行数、列数有不同要求。

子查询的基本概念与执行逻辑
子查询的核心价值在于把复杂查询拆成多个清晰的步骤。很多业务问题并不能直接通过一层筛选完成,而是需要先得到一个中间结果,再基于这个中间结果继续判断。例如,先计算某个统计值,再筛选满足该统计值的记录;或者先找到一组符合条件的编号,再根据这组编号查询明细数据。子查询可以把这些步骤写在一条 SQL 语句中,让数据在数据库内部完成流转,减少应用层多次查询带来的重复逻辑。
从执行关系来看,子查询可以分为非相关子查询和相关子查询。非相关子查询不依赖外层查询,可以独立执行,执行一次后将结果交给外层查询使用。相关子查询则会引用外层查询中的列,因此它的执行往往和外层查询的数据行产生关联,可能需要对外层结果集中的每一行重复求值。相关子查询表达能力很强,但在数据量较大时更容易带来性能压力,使用时需要格外谨慎。
子查询出现的位置不同,对外层查询的作用也不同。放在 WHERE 子句中,通常用于行级筛选;放在 HAVING 子句中,可以用于分组后的条件判断;放在 FROM 子句中,会形成一个派生表,供外层查询继续连接或过滤;放在 SELECT 子句中,则通常要求返回单个值,作为结果列的一部分。写子查询时,必须先明确它最终返回的是单个值、一列值、一行值还是一个结果集,再选择合适的外层写法。
按返回结果划分的四类典型写法
按照返回结果划分子查询类型,是最贴近实际开发的分类方式。因为 SQL 语句是否合法,往往取决于子查询返回了几行几列,以及外层查询使用了什么操作符。标量子查询适合单值比较,列子查询适合集合判断,行子查询适合多字段同时匹配,表子查询适合把中间结果当作临时表继续使用。
标量子查询
标量子查询返回的结果是单个值,也就是一行一列。它经常出现在 WHERE 子句中,作为比较条件使用。典型场景是先通过聚合函数得到平均值、最大值、最小值或总数,再把这个值交给外层查询进行筛选。由于标量子查询要求结果唯一,通常会借助 AVG、MAX、MIN、COUNT 等聚合函数来保证返回单值。
-- 查询工资高于平均工资的员工信息
SELECT emp_id, emp_name, salary
FROM employee
WHERE salary > (
SELECT AVG(salary)
FROM employee
);
这类写法的关键是子查询必须只返回一个值。如果子查询返回多行,外层比较就无法成立,数据库会直接报错。因此在设计标量子查询时,要特别确认内部查询是否具备唯一性,或者是否通过聚合函数把多行结果压缩成了单个值。
列子查询
列子查询返回的是一列多行的数据,通常配合 IN、ANY、ALL 等操作符使用。它适合处理“某个字段是否属于一组值”的问题。例如,先查出销售部门对应的部门编号集合,再根据这些编号筛选员工表中的记录。这样可以把跨表条件集中在一条语句中表达。
-- 查询销售部门的员工信息
SELECT emp_id, emp_name, dept_id
FROM employee
WHERE dept_id IN (
SELECT dept_id
FROM department
WHERE dept_name = '销售部'
);
列子查询最常见的是与 IN 搭配,用于判断成员归属。如果使用 ANY 或 ALL,则还需要配合比较操作符,表达“满足其中任意一个值”或“满足全部值”的语义。无论使用哪种写法,都要注意子查询返回的列数必须与外层比较的列数匹配,否则语句无法执行。
行子查询
行子查询返回的是一行多列的数据,通常用于行比较场景。它适合多个字段需要同时匹配的情况。例如,需要查找与某个员工同部门且同入职时间的其他员工,就可以把这个员工的部门和入职时间作为一个整体条件,而不是分别写两个独立的判断条件。
-- 查询与张三同部门且同入职时间的员工
SELECT emp_id, emp_name, dept_id, hire_date
FROM employee
WHERE (dept_id, hire_date) = (
SELECT dept_id, hire_date
FROM employee
WHERE emp_name = '张三'
);
行子查询要求内部查询只能返回一行,并且返回的列数要与左侧行构造器保持一致。如果内部查询返回多行,行比较就无法确定唯一对象。行子查询的优势在于语义紧凑,能够把多个字段的匹配关系写成一个整体条件,适合用于重复记录排查、同条件记录查找等场景。
表子查询
表子查询返回的是多行多列的结果集,通常用在 FROM 子句中,作为临时表参与外层查询。它适合先完成一次统计、分组或筛选,再把结果当作新的数据源继续处理。例如,先按部门分组得到每个部门的最高工资,再把这个结果与员工表连接,最终找出对应员工。
-- 查询每个部门工资最高的员工
SELECT e.emp_id, e.emp_name, e.dept_id, e.salary
FROM employee AS e
INNER JOIN (
SELECT dept_id, MAX(salary) AS max_salary
FROM employee
GROUP BY dept_id
) AS t ON e.dept_id = t.dept_id AND e.salary = t.max_salary;
表子查询也常被称为派生表。由于它本质上是一个临时结果集,因此必须指定别名,否则外层查询无法引用。派生表可以参与连接、过滤、排序等后续操作,适合处理复杂报表、分组极值、阶段性汇总等需求。使用表子查询时,要关注它的结果集大小,如果结果集过大,可能会增加临时处理成本。
子查询的典型应用场景
子查询最常见的用途是处理复杂条件筛选。当外层查询的筛选条件不是固定值,而是来自另一段查询的计算结果时,子查询可以很自然地表达这种依赖关系。比如根据平均工资、最高分、最小库存、最大订单金额等动态阈值进行筛选,直接写死数值并不现实,而子查询可以让条件随着数据变化自动变化。
子查询也适合分步数据统计。有些查询需要先得到中间统计结果,再基于中间结果继续查询。如果把所有逻辑都压在一层语句中,表达式可能变得很难阅读。通过子查询,可以把中间统计过程封装在内部,让外层查询只关注最终筛选或展示逻辑。这样的写法在报表统计、排名分析、分组极值查询中比较常见。
表子查询还经常用于临时数据生成。当业务逻辑需要先对原始数据做一次预处理,比如分组、去重、求和、排序后再参与连接时,可以把预处理结果放在 FROM 子句中作为临时表。这样外层查询面对的是一个已经整理过的数据集,逻辑会更清晰,也更容易维护。
- 复杂条件筛选:筛选条件依赖另一段查询的计算结果,子查询可以避免手工传入中间值。
- 分步数据统计:先得到中间统计结果,再基于中间结果继续过滤或连接。
- 临时数据生成:通过派生表形成临时结果集,供外层查询进一步处理。
在实际使用中,子查询是否合适,不能只看语句是否简短,还要看它是否准确表达了业务含义,以及执行计划是否稳定。如果子查询能让逻辑更直观,同时数据量可控,那么它是很好的选择;如果子查询导致重复扫描大表,就需要考虑改写。
子查询的使用限制与优化建议
子查询虽然能简化表达,但并不是所有场景都适合无限制使用。最需要关注的是性能问题,尤其是相关子查询。由于相关子查询依赖外层查询的当前行,数据库可能需要对外层结果集的每一行都执行一次内部查询。当外层数据量较大时,这种重复执行会显著增加查询耗时。在这类场景中,通常可以考虑使用连接查询替代,让优化器以更高效的方式处理表与表之间的关系。
子查询对返回结果也有严格限制。标量子查询只能返回单个值,如果返回多行就会报错;列子查询返回的列数必须和外层比较表达式匹配;行子查询要求返回单行,并且列数一致;表子查询作为派生表时必须指定别名。除此之外,子查询嵌套层级过深会增加阅读和维护成本,也可能导致解析和执行效率下降,一般建议尽量控制嵌套层数,保持语句清晰。
- 性能限制:相关子查询可能被重复执行,大数据量下容易变慢。
- 结果限制:标量子查询必须返回单值,列子查询和行子查询必须满足列数匹配要求。
- 嵌套限制:嵌套层级过深会降低可读性,也可能影响执行效率。
- 语法限制:在部分场景中,
FROM子句中的子查询如果包含LIMIT,需要注意括号和别名的规范写法。
在子查询与连接查询之间做选择时,可以遵循一个实用原则:当同样的需求可以用连接查询清晰表达,并且连接条件能够有效利用索引时,优先考虑连接查询。MySQL 对连接查询的优化较为成熟,很多场景下执行效率更稳定。只有当子查询的结果集很小,或者子查询能让业务逻辑更直观时,才更适合保留子查询写法。
-- 使用JOIN改写销售部员工查询 SELECT e.emp_id, e.emp_name, e.dept_id FROM employee AS e INNER JOIN department AS d ON e.dept_id = d.dept_id WHERE d.dept_name = '销售部';
上面这个例子中,原来通过列子查询先查出部门编号,再筛选员工信息;改写为连接查询后,员工表和部门表直接通过部门编号关联,再由部门名称过滤。这样的写法在很多数据场景中更容易被优化,也便于利用索引减少扫描范围。当然,最终选择哪种写法,仍应结合表结构、数据量、索引情况和执行计划综合判断。
总体来说,子查询是 MySQL 中非常重要的表达能力。掌握标量子查询、列子查询、行子查询和表子查询的适用场景,理解不同位置对返回结果的要求,再结合性能限制进行合理改写,才能让 SQL 既保持清晰,又具备良好的执行效率。