子查询:把一条 SELECT 查询,嵌套在另一条 SQL 里面(括号包起来),内层查询叫子查询,外层叫父查询。
子查询可以写在 WHERE、FROM、SELECT、EXISTS 后面,返回单行、多行、一张表。
核心口诀:括号包起来,先跑内层,结果交给外层用
1. 分类(按返回结果)
① 标量子查询(返回单个值:1 行 1 列)
内层只返回一个数值,可直接用 =、>、< 比较。
sql
-- 查询工资高于平均工资的员工
SELECT name, salary
FROM emp
WHERE salary > (SELECT AVG(salary) FROM emp);
执行顺序:
先执行括号内:算出平均工资(比如 8000)
外层变成:
WHERE salary > 8000
⚠️注意:子查询返回多行时,不能用
=,会报错。
② 列子查询(返回一列多行)
返回一堆值,要用 IN / ANY / ALL
sql
-- 查询在销售部的所有员工
SELECT name FROM emp
WHERE dept_id IN (SELECT id FROM dept WHERE dept_name='销售部');
IN:等于子查询结果里任意一个> ANY:大于子查询里任意一个(大于最小值)> ALL:大于子查询全部(大于最大值)
③ 表子查询(返回完整一张表,多行多列)
写在 FROM 后面,又叫派生表 / 内联视图,必须给子查询起别名。
sql
-- 先统计每个部门平均工资,再对这个结果过滤
SELECT *
FROM (
SELECT dept_id, AVG(salary) avg_sal
FROM emp
GROUP BY dept_id
) t -- 必须写别名t
WHERE avg_sal > 9000;
关键点:FROM 后面的子查询,一定要别名,否则语法报错。
④ EXISTS 相关子查询
普通子查询:内层独立执行,不依赖外层。
相关子查询:子查询引用外层表的字段,内层每一行都要跑一次。
EXISTS:判断子查询有没有返回行,有就 true,没有 false。
sql
-- 查询存在员工的部门
SELECT d.dept_name
FROM dept d
WHERE EXISTS (
SELECT 1 FROM emp e WHERE e.dept_id = d.id
);
逻辑:遍历外层 dept 每一行,传给内层,看这个部门下有没有员工。
推荐写
SELECT 1,不需要查真实字段,只判断存在,性能更好。
2. 子查询可以放的 4 个位置
WHERE 后面:过滤条件(最常用)
FROM 后面:派生表,当做一张临时表使用
SELECT 后面:标量子查询,作为字段输出(性能差,少用)
sql
SELECT
name,
(SELECT dept_name FROM dept WHERE id=emp.dept_id) dept_name
FROM emp;
HAVING 后面:分组后的条件过滤
3. 子查询 vs JOIN(重点面试考点)
很多子查询逻辑,都可以改写为 JOIN。
示例:子查询 IN 写法
sql
SELECT name FROM emp WHERE dept_id IN (SELECT id FROM dept where dept_name='销售部');
等价 JOIN 写法
sql
SELECT e.name
FROM emp e
JOIN dept d ON e.dept_id=d.id
WHERE d.dept_name='销售部';
怎么选?
IN + 子查询:数据量大容易性能差;MySQL5.5 以前会有子查询优化坑。EXISTS:适合外层表小、内层表大,找到匹配就停止,效率高。JOIN:数据库优化器可以选择索引,绝大多数场景优先 JOIN。相关子查询:外层每一行执行一次子查询,大表要谨慎,容易慢。
4. 常见坑
FROM 后的子查询忘记写别名 →语法报错
返回多行的子查询用
=,而不是 IN →报错subquery returns more than 1 rowSELECT 后面写相关子查询,千万级大表性能爆炸
NOT IN 子查询结果包含 NULL,查询结果直接为空!
NOT IN(1,2,NULL)结果永远 false,尽量改用NOT EXISTS
sql
-- 不推荐,子查询有null会出问题
SELECT * FROM emp WHERE id NOT IN (SELECT emp_id FROM other);
-- 推荐写法
SELECT * FROM emp e WHERE NOT EXISTS (SELECT 1 FROM other o WHERE o.emp_id = e.id);
5. 快速区分:相关子查询 / 非相关子查询
✅非相关子查询:子查询完全独立,可以单独拿出来执行一遍。
WHERE salary > (select avg(salary) from emp)✅相关子查询:子查询里面用到外层表的列,不能单独运行。EXISTS 例子就是典型。
简单总结
子查询就是括号包裹的 select,先执行内层;
返回单个值用
=;返回多值用IN/ANY/ALL;FROM 后子查询必须别名;
EXISTS 适合判断存在,NOT IN 小心 NULL 坑;
能 JOIN 优先 JOIN,大表慎用相关子查询。