SQL 子查询
子查询是在一条 SQL 中嵌入另一条查询,用于把中间结果作为条件、标量值或临时结果集继续参与外层查询。
#type / concept
#status / growing
#tech / dev / backend
#resource / sql
#resource / database
[!info] related notes
- 所属 MOC: 数据库 MOC
- 前置概念: SQL, 查询
- 并列概念: SQL JOIN、GROUP BY 与聚合查询, SQL HAVING 和 WHERE 的区别
- 相关概念: SQL 查询执行流程, 数据库索引
SQL 子查询
一句话定义
子查询就是把一条查询写在另一条查询里面,让内层查询的结果继续参与外层查询。
它到底解决什么问题
很多业务题不是一步就能直接写出来,而是要先得到一个中间结果,再拿这个中间结果继续判断。
典型问题包括:
- 先找出“平均工资以上”的员工
- 先找出“下过单的用户 id”,再查用户信息
- 先把某个统计结果算出来,再在外层继续筛选或排序
子查询本质上是在表达:
我先查一小步,再把这一步的结果喂给外层。
常见形式
1. 标量子查询
子查询返回一个值。
例如:查工资高于平均工资的员工。
SELECT name, salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
);
内层先算出平均工资,外层再逐行比较。
2. IN 子查询
子查询返回一组值,外层判断某值是否落在这组结果里。
SELECT id, name
FROM users
WHERE id IN (
SELECT user_id
FROM orders
);
这题的意思是:查出下过单的用户。
3. EXISTS 子查询
EXISTS 不关心内层具体返回什么值,只关心“有没有匹配记录”。
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
它特别适合表达:
- 是否存在关联记录
- 半连接语义
4. FROM 子查询
把子查询当成一个临时结果集,再由外层继续查询。
SELECT t.user_id, t.order_count
FROM (
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
) t
WHERE t.order_count > 3;
这类写法常见于:
- 先做聚合
- 再对聚合结果继续过滤、排序、联表
相关子查询和非相关子查询
非相关子查询
内层查询可以独立执行一次,不依赖外层行。
例如前面的平均工资例子。
相关子查询
内层查询依赖外层当前行,常和 EXISTS 一起出现。
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
这里内层 o.user_id = u.id 依赖外层的 u.id,所以叫相关子查询。
子查询和 JOIN 怎么选
先抓语义,不要先背性能传说。
更适合子查询的场景
- 题目本来就是“先算一个中间结果,再拿它做条件”
- 需要表达“是否存在”
- 写成子查询后语义更直观
更适合 JOIN 的场景
- 你最终就是要把多张表的列一起拿出来
- 需要在同一个结果里展示关联字段
- 后续还要继续分组和聚合
所以不是“子查询不好,JOIN 才高级”,而是看哪种表达更贴合问题本身。
最容易踩的坑
1. IN 和 EXISTS 不分场景乱用
两者都能解决“有没有关联”,但语义不完全一样:
IN更像“值是否在一个集合中”EXISTS更像“是否存在匹配记录”
面试里先把语义说清,再补一句“具体性能还要看数据库优化器和索引情况”,会比绝对化结论更稳。
2. 子查询一多就看不清层级
写复杂 SQL 时要先口头拆成两步:
- 内层先得到什么中间结果
- 外层再基于它做什么
如果这两步说不清,SQL 大概率也会写乱。
3. 相关子查询在大表上可能代价不低
尤其是没有合适索引时,相关子查询可能变慢。所以写完以后还要有性能意识:
- 关联字段有没有索引
- 是否能改写成 JOIN
- 是否要用
EXPLAIN看执行计划
最短记忆方式
- 子查询 = 查询里面再套查询
- 标量子查询 = 返回一个值
IN= 看值是否在集合里EXISTS= 看是否存在匹配记录