SQL内功笔记 · 第7篇:CTE&临时表&递归

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)展开、社交网络关系链追溯、菜单权限树遍历等。

三、使用注意事项

  1. 性能考量:虽然 CTE 能提升可读性,但数据库优化器对其处理方式各异。对于复杂或大数据量的 CTE,尤其是被多次引用时,建议结合执行计划评估性能,必要时可考虑使用临时表进行优化。
  2. 递归深度与终止 :使用递归 CTE 时必须明确定义递归终止条件(通常通过 JOIN 或 WHERE 子句实现),避免无限循环。同时需注意数据库对递归深度的默认限制(如 MySQL 的 cte_max_recursion_depth),并根据业务需要调整。
  3. 作用域限制:CTE 仅在定义它的单条 SQL 语句(SELECT、INSERT、UPDATE、DELETE)执行期间有效,不能跨查询复用,这与临时表或视图不同。
  4. 语法差异:不同数据库(如 MySQL, PostgreSQL, SQL Server)对 CTE 的支持细节和递归语法可能存在细微差别,在实际开发中需查阅对应数据库的文档。

掌握 CTE,意味着你掌握了将复杂 SQL 逻辑化繁为简、并优雅处理层次化数据的两把钥匙。建议在涉及多步骤查询或树形数据的场景中积极尝试,使其成为你 SQL 工具箱中的常备利器。

相关推荐
XiYang-DING1 小时前
【Spring】日志
java·数据库·spring
我是唐青枫1 小时前
MySQL EXISTS 详解:存在性判断、NOT EXISTS 与实战示例
数据库·mysql
Reisentyan1 小时前
[Pro]GoLang Learn Data Day 5
开发语言·后端·golang
05候补工程师1 小时前
【英语学习笔记】基于“底层逻辑转换”与“去动词化”的英汉互译核心方法论及写作高分公式
经验分享·笔记·学习·考研
weixin_468466851 小时前
Airtable 零基础快速上手与实战指南
数据库·人工智能·python·深度学习·ai·大模型
凯瑟琳.奥古斯特1 小时前
10道数据库原理精选题
开发语言·数据库·职场和发展·数据库开发
稚枭天卓2 小时前
mac 安装mysql
mysql·macos
大明者省2 小时前
CentOS 与 Ubuntu Python 部署差异
笔记·python·ubuntu·centos