MySQL 8.0+ 使用 WITH RECURSIVE 实现树形查询,需定义锚点(根节点)和递归成员(自连接CTE自身),通过 UNION ALL 合并,连接条件确保收敛,否则触发迭代上限报错。

MySQL 8.0+ 怎么用 WITH RECURSIVE 做树形查询
标准 SQL 的 JOIN 本身不支持递归,硬用多层 LEFT JOIN 拼接父子关系只适用于固定层级(比如最多3级),且一旦层级变深就难维护、易出错。真要查完整树形结构,必须用递归 CTE —— MySQL 8.0+、PostgreSQL、SQL Server 都支持,但语法细节不同。
以常见组织架构表为例:employees(id, name, manager_id),其中 manager_id 指向上级的 id:
WITH RECURSIVE org AS ( SELECT id, name, manager_id, 0 AS level FROM employees WHERE manager_id IS NULL -- 根节点(CEO) UNION ALL SELECT e.id, e.name, e.manager_id, o.level + 1 FROM employees e INNER JOIN org o ON e.manager_id = o.id ) SELECT * FROM org ORDER BY level;
注意:递归部分(UNION ALL 右侧)的 JOIN 必须引用 CTE 自身(这里是 org),且连接条件要确保能收敛(比如 e.manager_id = o.id),否则会报 Recursive query aborted after 1000 iterations 错误。
PostgreSQL 中递归查询的陷阱:循环引用怎么破
如果数据存在脏数据(比如 A 管 B、B 管 C、C 又管 A),PostgreSQL 会直接报错 infinite recursion detected。它不像 MySQL 那样默认截断,必须显式加循环检测。
- 用
ARRAY[emp_id]记录已遍历路径,在递归时用@> ARRAY[e.id]判断是否重复 -
SELECT ... ARRAY[e.id] || path构建路径数组,初始值为ARRAY[]::int[] - WHERE 条件里加
NOT e.id = ANY(path)阻止回环
否则哪怕只有一条错误的自环记录(manager_id = id),整个查询就崩。
SQL Server 的 OPTION (MAXRECURSION n) 是干啥的
SQL Server 默认只允许 100 层递归,超过就报错 Maximum recursion 100 has been exhausted。这不是性能限制,而是安全机制,防止意外死循环拖垮实例。
- 加
OPTION (MAXRECURSION 0)表示不限制(慎用!) - 设成具体数字如
OPTION (MAXRECURSION 500)更稳妥 - 这个 hint 必须写在最终
SELECT语句末尾,不能放在 CTE 内部
如果树真实深度可能超 100,又不想设 0,就得先用 SELECT MAX(level) 估算最大深度再动态设值。
没有递归支持的老版本 MySQL 怎么办
MySQL 5.7 或更早版本不支持 WITH RECURSIVE,硬要用 JOIN 模拟,只能按预估最大层级展开。比如最多4级,则写4次 LEFT JOIN:
SELECT t1.name AS lvl1, t2.name AS lvl2, t3.name AS lvl3, t4.name AS lvl4 FROM employees t1 LEFT JOIN employees t2 ON t2.manager_id = t1.id LEFT JOIN employees t3 ON t3.manager_id = t2.id LEFT JOIN employees t4 ON t4.manager_id = t3.id WHERE t1.manager_id IS NULL;
这种写法的问题很实在:层级一变就得改 SQL;NULL 值多(叶子节点后面全是 NULL);无法统一排序或统计深度;一旦某条路径超过预设层级,信息就直接丢弃。真要长期用,不如在应用层做迭代查询,或者升级数据库。
递归查询真正的难点不在语法,而在数据质量 —— manager_id 指向不存在的 id、自环、跨环、空值混用,这些都会让 CTE 失效或结果错乱。动手前先 SELECT COUNT(*) FROM employees WHERE manager_id NOT IN (SELECT id FROM employees) OR manager_id = id 扫一遍脏数据。

















