解析 MySQL CTE:WITH 与 WITH RECURSIVE
在开发过程中,发现在 MYSQL 的 Mapper 文件中会用到 WITH 关键字,有一些疑问:为什么要用WITH 和 WITH RECURSIVE ?是什么?有何区别?

一、 CTE 的含义
MySQL 从 8.0 开始支持 WITH 语法,即:Common Table Expressions (CTE),公用表表达式。
CTE 是一个命名的临时结果集合,仅在单个 SQL 语句(select、insert、update 或 delete)的执行范围内存在。
允许用户在单个查询中定义临时的命名结果集,从而提升复杂查询的可读性和结构化程度。
CTE 的主要目的是将复杂的查询逻辑分解为多个简单、可读的步骤,从而提升 SQL 代码的结构化程度和可维护性。
CTE 可以分为两种类型:非递归 CTE 和递归 CTE。
二、 适用场景
为什么需要使用 WITH ?
假设场景:找出每个学科中,分数高于该科目平均分的学生。
1 | 插入8条数据: |
方式一:不使用 WITH(传统子查询)
1 | mysql> SELECT s1.* |
方式二:用 WITH
1 | -- 先算好每个科目的平均分 |
这样看来:
| 特性 | 方式一 | 方式二 |
|---|---|---|
| 逻辑结果 | 等价 | 等价 |
| 核心思想 | 对每一行数据,都重新计算一次关联值 | 先将所有关联值一次性算好,再进行匹配 |
| 性能 | 尤其在大数据量下,通常较差(0.01 sec) | 性能稳定且可预测,通常极佳(0.00 sec) |
| 形象比喻做菜 | 手忙脚乱,现用现配 | 专业从容,提前备料 |
CTE 可以分为两种类型
非递归
CTE的核心价值在于提升可读性和模块化。- 简化复杂查询: 将一个需要多步计算的复杂查询分解成多个
CTE,每一步逻辑清晰,易于理解和维护。 - 代码复用: 当一个查询中需要多次使用同一个子查询时,可以将其定义为
CTE,避免重复代码。 - 替代复杂的嵌套子查询: 用线性的
CTE链条代替深层嵌套的子查询,使代码结构更扁平。
- 简化复杂查询: 将一个需要多步计算的复杂查询分解成多个
递归
CTE是处理层级结构或图结构数据的“神器”。- 组织架构/员工层级查询: 查询部门及其所有下级部门(包括所有层级的下级部门)。
- 物料清单分解: 计算一个产品由哪些零件组成,以及每个零件的成本。
- 文件系统目录树遍历: 查询某个目录及其所有子目录下的所有文件。
- …
三、 非递归 CTE
非递归 CTE 是 CTE 的基础形式,本质上是一个可以复用的子查询。
3.1 语法
要指定公共表表达式,请使用 WITH 子句,该子句包含一个或多个以逗号分隔的子句。
每个子句都提供一个用于生成结果集的子查询,并为该子查询关联一个名称。
1 | WITH |
在使用了 WITH 子句的语句里,可以通过每个 CTE 的名字来查询它所生成的结果集。
3.2 列名定义规则
CTE 的列名可以通过两种方式定义:
- 显式指定列名:如果
CTE名称后跟一个带括号的名称列表,那么这些名称就是列名:
1 | WITH cte (col1, col2) AS |
- 从子查询中继承: 列名来源于 AS (subquery) 部分中第一个 SELECT 的选择列表:
1 | WITH cte AS |
3.3. 使用上下文
WITH 子句非常灵活,可以在多种 SQL 语句中使用:
- 在
SELECT、UPDATE和DELETE语句的开头:
1 | WITH ... SELECT ... |
示例: 更新所有上月活跃但本月不活跃的用户。
1 | WITH LastMonthActive AS ( |
- 在子查询(包括派生表子查询)的开头:
1 | SELECT ... WHERE id IN (WITH ... SELECT ...) ... |
- 紧跟在包含
SELECT语句的SELECT之前:
1 | INSERT ... WITH ... SELECT ... |
示例: 将聚合后的数据插入报表表。
1 | WITH DailySales AS ( |
四、 递归 CTE
递归 CTE 是一种特殊的 CTE,它的子查询会引用其自身的名称,从而实现循环遍历。
4.1 语法
如果一个 CTE 的子查询引用了其自身的名称,那么该 CTE 就是递归的。
如果 WITH 子句中的任何 CTE 是递归的,则必须包含 RECURSIVE 关键字。
简单来说:就是一个在它的子查询里引用了自己名字的 CTE。
1 | WITH RECURSIVE cte (n) AS |
1 | +------+ |
4.2 结构解析
一个递归 CTE 的子查询必须由两部分组成,用 UNION ALL 或 UNION [DISTINCT] 连接:
- 锚点成员: 执行一次,返回初始结果集。它是递归的“种子”。
- 递归成员: 反复执行,直到不再返回任何行。它的
WHERE子句是递归的终止条件,非常重要,否则会陷入无限循环。
五、WITH 与 WITH RECURSIVE 的区别:
| 特性 | WITH (非递归) | WITH RECURSIVE (递归) |
|---|---|---|
| 关键字 | WITH |
WITH RECURSIVE |
| 自引用 | 不允许,CTE 不能引用自身 | 允许,CTE 必须引用自身以实现递归 |
| 主要用途 | 查询逻辑的模块化、代码复用、简化复杂查询 | 遍历具有层级或父子关系的数据结构 |
| 结构 | 一个或多个独立的子查询 | 必须包含锚点成员和递归成员两部分 |
| 性能 | 通常等同于派生表(子查询),性能相近 | 可能非常消耗资源,必须有明确的终止条件 |
Title: 解析 MySQL CTE:WITH 与 WITH RECURSIVE
Author: Amber
Date: 2025-12-20
Last Update: 2025-12-20
Blog Link: https://wyiyi.github.io/amber/2025/12/20/CTE/
Copyright Declaration: Copyright © 2022 Amber.