CTE
CTE(Common Table Expression,公用表表达式) 是 SQL 中一种临时命名的结果集,它存在于单个语句(SELECT、INSERT、UPDATE、DELETE)的执行范围内。MySQL 8.0 或更高版本支持。
你可以把它理解为一个临时的、只在你这一条 SQL 执行期间有效的 "视图",或者更形象地说,是 SQL 语句中的命名子查询。
| 优点 | 说明 |
|---|---|
| 提高可读性 | 把复杂查询拆解成有名字的步骤,逻辑更清晰 |
| 可复用 | 在同一条 SQL 中,可以多次引用同一个 CTE |
| 支持递归 | 可以编写递归 CTE 处理树形或图形数据(如组织结构、菜单树) |
| 替代子查询 | 避免多层嵌套的子查询,使代码扁平化 |
CTE(WITH)不是"语法糖",它解决的是两件事:
- 非递归 CTE:把复杂查询拆成可命名的阶段(可读性 + 可复用)
- 递归 CTE:表达递归/分层(层级数据、逐层展开)
非递归 CTE
基本形态
mysql
WITH t1 AS (
SELECT ...
),
t2 AS (
SELECT ... FROM t1
)
SELECT * FROM t2;
-- 等价于"多层子查询",但:每一层有名字(像变量)可以复用(同一个 CTE 可被多次引用)
复杂逻辑拆解
mysql
WITH candidates AS (
-- 多来源候选
SELECT 1 AS level, code, ft_xs FROM ...
UNION ALL
SELECT 2, code, ft_xs FROM ...
),
ranked AS (
SELECT c.*,
ROW_NUMBER() OVER (
PARTITION BY code ORDER BY level
) AS rn
FROM candidates c
)
SELECT * FROM ranked WHERE rn = 1;
中间结果复用
mysql
WITH base AS (
SELECT * FROM big_table WHERE ...
)
SELECT * FROM base WHERE ...
UNION ALL
SELECT * FROM base WHERE ...
-- 避免重复扫描大表(有些数据库会优化成一次执行)
逐步聚合
mysql
WITH flow AS (
SELECT item_id, qty_in, qty_out FROM ...
),
agg AS (
SELECT item_id, SUM(qty_in - qty_out) AS total
FROM flow
GROUP BY item_id
)
SELECT * FROM agg WHERE total > 0;
-- 先算流水 → 再聚合 → 再过滤
递归 CTE
这是重点:处理树/层级/链路。递归要控制深度。
核心结构
mysql
WITH RECURSIVE t AS (
-- ① 起点(种子数据)
SELECT ...
UNION ALL
-- ② 递归部分(不断往下找)
SELECT ...
FROM 表
JOIN t ON 条件
)
SELECT * FROM t;
执行机制
1. 先执行"起点查询"(root)
2. 再用结果去跑"递归查询"
3. 不断重复,直到查不出新数据
注意 :递归查询必须包含终止条件(例如 JOIN ... ON 条件),否则可能陷入无限循环。
典型场景
树结构(组织 / 分类 / BOM)
mysql.
-- 表结构:
id | parent_id
查整棵树(从根开始)
mysql
WITH RECURSIVE tree AS (
-- 根节点
SELECT id, parent_id, 1 AS level
FROM dept
WHERE parent_id IS NULL
UNION ALL
-- 子节点
SELECT d.id, d.parent_id, t.level + 1
FROM dept d
JOIN tree t ON d.parent_id = t.id
)
SELECT * FROM tree;
说明 :level 字段用于记录节点在树中的深度,根节点为 1。
查某个节点的所有子节点
mysql
WITH RECURSIVE sub_tree AS (
SELECT id, parent_id
FROM dept
WHERE id = '目标节点'
UNION ALL
SELECT d.id, d.parent_id
FROM dept d
JOIN sub_tree t ON d.parent_id = t.id
)
SELECT * FROM sub_tree;
反向查(找所有父节点)
mysql
WITH RECURSIVE parent_tree AS (
SELECT id, parent_id
FROM dept
WHERE id = '当前节点'
UNION ALL
SELECT d.id, d.parent_id
FROM dept d
JOIN parent_tree t ON d.id = t.parent_id
)
SELECT * FROM parent_tree;
注意:此查询用于查找从当前节点到根节点的所有祖先节点。
总结
CTE(公用表表达式)是 SQL 中提升代码可读性、复用性和表达能力的强大工具。通过本文的梳理,我们可以从三个维度来把握其核心价值:
一、核心价值
- 逻辑清晰化:将复杂查询拆解为有名字的步骤,让 SQL 的编写和阅读更符合人类思维。
- 代码扁平化:用命名的 CTE 替代多层嵌套子查询,减少括号嵌套,降低维护成本。
- 能力扩展:递归 CTE 为 SQL 引入了处理树形、层级、图状数据的能力,填补了传统 SQL 在递归查询上的空白。
二、适用场景
- 非递归 CTE:适用于复杂业务逻辑的分步计算、中间结果复用、多步骤数据清洗与转换,以及需要多次引用同一子查询的场景。
- 递归 CTE:专为处理具有自引用关系的数据而设计,典型应用包括:组织架构树查询、产品分类导航、物料清单(BOM)展开、社交网络关系链追溯、菜单权限树遍历等。
三、使用注意事项
- 性能考量:虽然 CTE 能提升可读性,但数据库优化器对其处理方式各异。对于复杂或大数据量的 CTE,尤其是被多次引用时,建议结合执行计划评估性能,必要时可考虑使用临时表进行优化。
- 递归深度与终止 :使用递归 CTE 时必须明确定义递归终止条件(通常通过 JOIN 或 WHERE 子句实现),避免无限循环。同时需注意数据库对递归深度的默认限制(如 MySQL 的
cte_max_recursion_depth),并根据业务需要调整。 - 作用域限制:CTE 仅在定义它的单条 SQL 语句(SELECT、INSERT、UPDATE、DELETE)执行期间有效,不能跨查询复用,这与临时表或视图不同。
- 语法差异:不同数据库(如 MySQL, PostgreSQL, SQL Server)对 CTE 的支持细节和递归语法可能存在细微差别,在实际开发中需查阅对应数据库的文档。
掌握 CTE,意味着你掌握了将复杂 SQL 逻辑化繁为简、并优雅处理层次化数据的两把钥匙。建议在涉及多步骤查询或树形数据的场景中积极尝试,使其成为你 SQL 工具箱中的常备利器。