找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4953

积分

0

好友

687

主题
发表于 2 小时前 | 查看: 3| 回复: 0

子查询,也被称为嵌套查询或内层查询,它本质上就是嵌套在另一个 SQL 查询内部的查询语句。它就像编程中的函数调用,是 SQL 中实现逻辑复用的重要模块,能将复杂的业务问题分解为清晰的步骤。

子查询可以出现在 SELECTFROMWHEREHAVINGEXISTS 等子句中,用于提供数据、过滤条件或充当临时数据源。

一、子查询的基本语法

-- 主查询(外部查询)
SELECT column_list
FROM table1 t1
WHERE condition
  AND column1 operator (
    -- 子查询(内部查询)
    SELECT column_a
    FROM table2 t2
    WHERE t2.col = t1.col  -- 可选:关联条件(相关子查询)
  );
关键字 说明
SELECT ... FROM ... WHERE 外部主查询
(SELECT ...) 子查询,必须用括号包围
operator 比较运算符:=, >, <, IN, EXISTS, ANY, ALL
t2.col = t1.col 若存在此引用,则为“相关子查询”(Correlated Subquery)

核心要点:子查询的生命周期依附于外层查询,通常不能独立执行。当存在多层嵌套时,数据库的执行顺序遵循“由内到外”的原则。

二、子查询的两种分类维度

SQL 子查询可以从两个交叉维度进行分类:

分类方式 类型
按结果形式(结果形态)分类 ① 标量子查询
② 行子查询
③ 列子查询
④ 表子查询
⑤ EXISTS子查询
按执行依赖关系(执行模式)分类 相关(关联)子查询(Correlated Subquery)
非相关子查询(Non-Correlated Subquery)

一个子查询可以同时属于两种分类,例如,它可能既是“标量子查询”又是“相关子查询”:

SELECT name, (SELECT AVG(salary) FROM emp WHERE dept = e.dept) FROM emp e;

三、标量子查询(Scalar Subquery)

标量子查询返回单个值(一行一列),通常用于与单值进行比较或作为计算表达式的一部分。

1. 语法与要点

SELECT 
  col1,
  (SELECT expr FROM table2 WHERE condition) AS computed_value
FROM table1 t1
WHERE col2 > (SELECT AVG(col3) FROM table3);
  • 结果唯一性:必须返回 0 或 1 行,否则数据库会报错(如 MySQL 的 Subquery returns more than 1 row)。
  • 使用位置:可用于 SELECTWHEREHAVING 子句。
  • 运算符:支持 =><>=<=<>

2. 示例:查询工资高于公司平均工资的员工

SELECT 
  name, salary
FROM employees
WHERE salary > (
  SELECT AVG(salary) 
  FROM employees  -- 这是一个非相关子查询
);

执行过程

  1. 执行子查询 SELECT AVG(salary) FROM employees,得到平均值(假设为 75000)。
  2. 执行主查询 SELECT name, salary FROM employees WHERE salary > 75000
  3. 返回最终结果。

3. 优化与替代方案

优化方法 说明
改写为 JOIN 将标量子查询转为 JOIN + WHERE,避免重复执行
添加索引 在子查询的 WHERE 条件列(如 salary)上建立索引
使用窗口函数替代 AVG(salary) OVER(),可避免子查询,性能更优

优化示例(使用窗口函数):

SELECT name, salary
FROM (
  SELECT name, salary, AVG(salary) OVER() AS avg_sal
  FROM employees
) t
WHERE salary > avg_sal;

四、行子查询(Row Subquery)

行子查询返回一行多列的数据,用于多列同时匹配的场景。

1. 语法与要点

SELECT *
FROM table1
WHERE (col1, col2) = (
  SELECT col_a, col_b
  FROM table2
  WHERE condition
);
  • 列数一致:左右两边的列数必须相等。
  • 支持运算符=, <>, IN, NOT IN
  • 注意:像 >< 这类比较符在大部分数据库中不支持行间比较,除非使用特定数据库的扩展功能(如 PostgreSQL),为避免兼容性问题,建议慎用。

2. 示例:查找与 Alice 工资和部门相同的员工

SELECT name, salary, dept
FROM employees
WHERE (salary, dept) = (
  SELECT salary, dept
  FROM employees
  WHERE name = 'Alice'
);

优化建议:可改写为 JOIN,性能通常更好,也便于数据库优化器生成更佳的执行计划。

SELECT e1.name, e1.salary, e1.dept
FROM employees e1
JOIN employees e2 ON e1.salary = e2.salary AND e1.dept = e2.dept
WHERE e2.name = 'Alice';

五、列子查询(Column Subquery)

列子查询返回一列多行的数据,主要用于集合成员判断。

1. 语法与要点

SELECT *
FROM table1
WHERE col1 IN (
  SELECT col_a
  FROM table2
  WHERE condition
);
  • 支持运算符IN, NOT IN, ANY, ALL, SOME
    • = ANY 等价于 IN
    • <> ALL 等价于 NOT IN
  • 陷阱NOT INNULL 值非常敏感。如果子查询结果集中包含 NULL,则整个 NOT IN 表达式的结果将为 UNKNOWN,导致查询无结果。因此,强烈建议用 NOT EXISTS 替代 NOT IN

2. 示例:查找财务部和销售部的员工

SELECT name, dept
FROM employees
WHERE dept IN (
  SELECT dept_name
  FROM departments
  WHERE dept_type IN ('Finance', 'Sales')
);

优化建议:对于大数据集,使用 JOIN 替代 IN 往往能获得更好的性能。

SELECT e.name, e.dept
FROM employees e
JOIN departments d ON e.dept = d.dept_name
WHERE d.dept_type IN ('Finance', 'Sales');

六、表子查询(Table Subquery / Derived Table)

表子查询返回多行多列的结果集,作为临时表使用,必须使用别名

1. 语法与要点

SELECT t.col1, t.col2
FROM (
  SELECT col_a, col_b
  FROM table1
  WHERE condition
) AS t  -- 别名是必须的
WHERE t.col1 > value;
  • 别名必需:必须使用 AS alias 为子查询结果命名,否则语法错误。
  • 使用位置:通常位于 FROM 子句中。
  • 非关联性:它通常是非相关子查询,无法直接引用外部查询的列。
  • 复杂度控制:虽然可以多层嵌套,但应控制深度以保证可读性。

2. 示例:统计各部门平均工资并筛选出高于60000的部门

SELECT dept, avg_salary
FROM (
  SELECT dept, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY dept
) AS dept_avg
WHERE avg_salary > 60000;

优化与可读性建议:对于复杂的多层查询,使用 CTE (公共表表达式) 可以极大地提升代码清晰度和可维护性。

WITH dept_avg AS (
  SELECT dept, AVG(salary) AS avg_salary
  FROM employees 
  GROUP BY dept
)
SELECT * FROM dept_avg WHERE avg_salary > 60000;

七、EXISTS 子查询(Existential Subquery)

EXISTS 子查询用于判断是否存在满足条件的行,只返回布尔值 TRUEFALSE

1. 语法与要点

SELECT *
FROM table1 t1
WHERE EXISTS (
  SELECT 1  -- 推荐使用 SELECT 1 或 SELECT NULL,无需查询具体列
  FROM table2 t2
  WHERE t2.ref = t1.id  -- 通常是关联条件
);
  • 只关心存在性:子查询中 SELECT 什么并不重要,SELECT 1 是高效且清晰的写法。
  • 通常为关联子查询:几乎总是通过条件与外部查询关联。
  • 短路求值:一旦找到一行匹配数据,立即返回 TRUE,停止搜索,这对性能有利。
  • NOT EXISTS 的妙用:常用于查找“没有...”的集合差集,例如“没有订单的客户”。

2. 示例:查找有订单的客户

SELECT customer_name
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.customer_id
);

性能选择

  • 外表小,内表大EXISTS 配合内表索引,性能通常优于 IN
  • 内表小,外表大IN(子查询结果可被缓存)可能更快。
  • 在涉及高并发与复杂系统设计的查询中,理解 EXISTSJOIN 的执行计划差异至关重要。

八、相关子查询(Correlated Subquery)深度解析

相关子查询是指子查询的执行依赖于外部查询的当前行。它会为外部查询的每一行都执行一次,因此性能开销较大(复杂度可达 O(n×m)),但其逻辑表达能力非常强。

1. 示例与执行过程

-- 为每个员工计算其所在部门的平均工资
SELECT 
  name,
  salary,
  (SELECT AVG(salary) FROM employees e2 WHERE e2.dept = e1.dept) AS dept_avg
FROM employees e1;

执行过程

  1. 主查询读取 employees 表第一行(如:Alice, HR, 70000)。
  2. 执行子查询:SELECT AVG(salary) FROM employees WHERE dept = ‘HR’,得到结果 60000。
  3. 输出该行,并附上 dept_avg = 60000
  4. 移动到下一行(如:Bob, Engineering, 80000),子查询变为 WHERE dept = ‘Engineering’,重复此过程。

2. 各类型子查询的关联性分析

类型 是否可能是关联子查询? 说明
标量子查询 常见于 SELECTWHERE 中,依赖外部列进行计算。
行子查询 用于 (col1, col2) = (...) 的多列匹配,条件可依赖外部。
列子查询 INANYALL 中使用,条件可依赖外部。
表子查询 通常不是 位于 FROM 子句,必须先独立执行生成临时表,一般不能引用外部列。
EXISTS 子查询 几乎总是 这是最典型的关联子查询应用场景。

结论:除了 FROM 子句中的表子查询外,其他所有类型的子查询(标量、行、列、EXISTS)都可能是关联子查询。

九、性能优化核心建议

什么时候该用子查询?又该如何选择类型呢?下图提供了一个清晰的决策路径:

SQL子查询类型选择与使用场景决策流程图

此外,遵循以下优化原则可以大幅提升查询效率:

场景 推荐写法 原因
简单关联过滤 JOIN 优化器更容易处理,执行计划更优。
存在性判断 EXISTS 利用短路求值,尤其当子查询表大时性能好。
集合成员判断 IN(集合小)或 JOIN(集合大) 避免 NOT INNULL 陷阱,JOIN 更可控。
聚合计算 窗口函数 OVER() 一次扫描完成计算,彻底避免低效的相关子查询。
复杂多步查询 CTE(公用表表达式) 提升可读性、可维护性,便于分步调试。
结果被多次引用 物化视图/临时表 减少重复计算的开销。

核心准则:凡能用 JOINCTE 清晰表达的,尽量不使用子查询。现代数据库优化器虽然能对简单子查询进行“解嵌套”优化,但对于复杂或关联子查询,手动重构为 JOIN/CTE 通常能带来更稳定、更高效的执行。

通用性能排序(仅供参考,实际取决于数据和索引):JOINCTE > EXISTS > IN > 相关子查询 > 多层嵌套子查询。

掌握子查询的分类、特性与优化技巧,是编写高效、清晰 SQL 语句的关键。希望本文的梳理能帮助你在实际开发中游刃有余。更多数据库实战技巧与深度讨论,欢迎访问云栈社区与广大开发者交流。




上一篇:Zilliz CLI工具与官方Agent Skill发布:一句话搞定向量数据库开发与运维
下一篇:从加班疲劳到复利式创造:关于开发者如何把握关键机会的几点杂谈
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-10 09:16 , Processed in 1.038907 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表