SQL 子查询

子查询是在一条 SQL 中嵌入另一条查询,用于把中间结果作为条件、标量值或临时结果集继续参与外层查询。

#type / concept #status / growing #tech / dev / backend #resource / sql #resource / database

[!info] related notes

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. INEXISTS 不分场景乱用

两者都能解决“有没有关联”,但语义不完全一样:

  • IN 更像“值是否在一个集合中”
  • EXISTS 更像“是否存在匹配记录”

面试里先把语义说清,再补一句“具体性能还要看数据库优化器和索引情况”,会比绝对化结论更稳。

2. 子查询一多就看不清层级

写复杂 SQL 时要先口头拆成两步:

  1. 内层先得到什么中间结果
  2. 外层再基于它做什么

如果这两步说不清,SQL 大概率也会写乱。

3. 相关子查询在大表上可能代价不低

尤其是没有合适索引时,相关子查询可能变慢。所以写完以后还要有性能意识:

  • 关联字段有没有索引
  • 是否能改写成 JOIN
  • 是否要用 EXPLAIN 看执行计划

最短记忆方式

  • 子查询 = 查询里面再套查询
  • 标量子查询 = 返回一个值
  • IN = 看值是否在集合里
  • EXISTS = 看是否存在匹配记录
创建于 2026/5/7 更新于 2026/5/27