递归 CTE:处理层级数据
什么是递归 CTE
递归 CTE 引用自身,适合处理层级数据(组织结构、目录树、图遍历)。
基本结构:
sql
WITH RECURSIVE cte_name AS (
-- 基础查询(锚点)
SELECT ... WHERE base_condition
UNION ALL
-- 递归查询(引用 cte_name)
SELECT ... FROM cte_name WHERE recursive_condition
)
SELECT * FROM cte_name;第一个递归 CTE:生成数列
sql
WITH RECURSIVE numbers AS (
-- 锚点:起始值
SELECT 1 AS n
UNION ALL
-- 递归:n + 1
SELECT n + 1 FROM numbers WHERE n < 10
)
SELECT * FROM numbers;结果:
text
n
----
1
2
3
4
5
6
7
8
9
10工作原理:
- 执行锚点查询:
SELECT 1→ 结果{1} - 执行递归查询:
SELECT 1 + 1→ 结果{2} - 继续递归:
SELECT 2 + 1→ 结果{3} - ...直到条件不满足(
n < 10)
组织结构示例
员工表(manager_id 指向上级):
sql
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT
);
INSERT INTO employees VALUES
(1, 'Alice', NULL), -- CEO
(2, 'Bob', 1),
(3, 'Charlie', 1),
(4, 'Diana', 2),
(5, 'Eve', 2),
(6, 'Frank', 3);查询所有下属
sql
WITH RECURSIVE subordinates AS (
-- 锚点:从 Alice 开始
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE id = 1
UNION ALL
-- 递归:查找下级
SELECT e.id, e.name, e.manager_id, s.level + 1
FROM employees e
JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;结果:
text
id | name | manager_id | level
----+---------+------------+-------
1 | Alice | | 1
2 | Bob | 1 | 2
3 | Charlie | 1 | 2
4 | Diana | 2 | 3
5 | Eve | 2 | 3
6 | Frank | 3 | 3查询完整路径
sql
WITH RECURSIVE org_path AS (
SELECT id, name, manager_id, name AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, op.path || ' > ' || e.name
FROM employees e
JOIN org_path op ON e.manager_id = op.id
)
SELECT name, path FROM org_path;结果:
text
name | path
---------+-------------------------
Alice | Alice
Bob | Alice > Bob
Charlie | Alice > Charlie
Diana | Alice > Bob > Diana
Eve | Alice > Bob > Eve
Frank | Alice > Charlie > Frank实际应用
应用 1:目录树
sql
WITH RECURSIVE folder_tree AS (
SELECT id, name, parent_id, name AS path, 0 AS depth
FROM folders
WHERE parent_id IS NULL
UNION ALL
SELECT f.id, f.name, f.parent_id, ft.path || '/' || f.name, ft.depth + 1
FROM folders f
JOIN folder_tree ft ON f.parent_id = ft.id
)
SELECT REPEAT(' ', depth) || name AS indented_name, path
FROM folder_tree
ORDER BY path;应用 2:图遍历(查找连接)
sql
WITH RECURSIVE connections AS (
-- 起点
SELECT user_id, friend_id, 1 AS distance
FROM friendships
WHERE user_id = 1
UNION
-- 朋友的朋友(去重用 UNION 而非 UNION ALL)
SELECT c.user_id, f.friend_id, c.distance + 1
FROM connections c
JOIN friendships f ON c.friend_id = f.user_id
WHERE c.distance < 3 -- 限制深度
)
SELECT DISTINCT friend_id, MIN(distance) AS degrees_of_separation
FROM connections
GROUP BY friend_id;应用 3:账户余额追踪
sql
WITH RECURSIVE balance AS (
-- 初始余额
SELECT 0 AS transaction_num, 0 AS balance
UNION ALL
SELECT
t.id,
b.balance + t.amount
FROM transactions t
JOIN balance b ON t.id = b.transaction_num + 1
)
SELECT * FROM balance;终止条件
递归必须有终止条件,否则无限循环。
方式 1:WHERE 条件
sql
SELECT n + 1 FROM numbers WHERE n < 10 -- n >= 10 时停止方式 2:没有更多行
sql
SELECT e.id FROM employees e
JOIN subordinates s ON e.manager_id = s.id
-- 当没有下级时自动停止方式 3:深度限制
sql
SELECT ..., s.level + 1
FROM ... JOIN subordinates s ...
WHERE s.level < 5 -- 限制深度循环检测
如果数据有循环引用(如 A → B → A),递归会无限循环。
防止循环:
sql
WITH RECURSIVE hierarchy AS (
SELECT id, name, manager_id, ARRAY[id] AS path
FROM employees
WHERE id = 1
UNION ALL
SELECT e.id, e.name, e.manager_id, h.path || e.id
FROM employees e
JOIN hierarchy h ON e.manager_id = h.id
WHERE NOT (e.id = ANY(h.path)) -- 检测循环
)
SELECT * FROM hierarchy;性能考虑
- 递归 CTE 可能很慢(大数据集或深层次)
- 使用
UNION(去重)代替UNION ALL避免重复行 - 限制递归深度
- 考虑添加索引到递归连接的列
PostgreSQL vs MySQL
PostgreSQL:完整支持递归 CTE
MySQL 8.0+:支持递归 CTE,语法相同,但有一些限制:
- 不支持
CYCLE子句(需要手动检测循环) - 递归深度限制为 1000(可配置)
MySQL < 8.0:不支持递归 CTE,需要用存储过程或应用层处理。
练习
生成序列:生成 1 到 100 的数列。
组织结构:查询某个员工的所有下属(所有层级)。
路径追踪:显示从 CEO 到每个员工的完整路径。
查找上级:反向递归,查询某个员工的所有上级。
深度限制:只查询 3 层以内的下属。
计数统计:计算每个经理的直接和间接下属总数。
循环检测:在有潜在循环的数据中使用路径数组检测循环。
实际场景:构建产品类别树,显示缩进的层级结构。
完成练习后,打开本章答案页核对。
本章小结
第四章学习了 SQL 高级查询:
- 窗口函数:OVER、PARTITION BY、ORDER BY
- 排名函数:ROW_NUMBER、RANK、DENSE_RANK
- 窗口聚合:移动平均、累计求和、LAG/LEAD
- CTE:WITH 子句,提高可读性
- 递归 CTE:处理层级数据和图遍历
这些是高级数据分析的核心工具。第五章将学习事务和并发控制。