普通子查询无法解决树形结构遍历问题,因其不具备自我引用和迭代能力;真正有效的是递归cte(with recursive),它通过锚点+递归成员、union all连接、正确join方向(如on子.parent_id = 父.id)及显式深度限制实现逐层展开。

没用——至少不能单独靠普通子查询解决树形结构遍历问题。
它没法自我引用,也不能逐层展开父子关系。真正起作用的是递归 CTE(WITH RECURSIVE),不是传统意义上的子查询。
为什么普通子查询搞不定树形结构?
子查询本质是“一次求值”,执行完就结束,无法迭代。而树形结构需要从根出发,一层层找子节点,直到叶子为止——这要求查询能引用自身结果,普通子查询做不到。
-
IN、= ANY、相关子查询这些写法,最多查 1~2 层,遇到三级以上嵌套就漏数据或报错 - 想查“部门 A 下所有子孙部门”,用
(SELECT ... FROM dept WHERE parent_id IN (SELECT id FROM dept WHERE parent_id = ?))这种套两层的写法,硬编码层数,维护性差,深度一变就得重写 - 性能上,每多一层嵌套,就多一次全表扫描或索引查找,N 层 ≈ N 次 IO
WITH RECURSIVE 才是正解,不是子查询但常被误认
很多人把 WITH RECURSIVE 叫成“递归子查询”,其实它是独立语法特性,核心在 UNION ALL 和自引用。
- 锚点部分(非递归)必须明确指定起点,比如
WHERE parent_id IS NULL或WHERE id = 123 - 递归部分必须用
JOIN关联到 CTE 自身,且连接条件要体现父子关系,例如ON c.parent_id = t.id -
UNION ALL是强制要求(UNION会去重,破坏层级路径连续性) - MySQL 8.0+、PostgreSQL、SQL Server 都支持;Oracle 用的是
START WITH ... CONNECT BY PRIOR,语法不同但语义等价
容易踩的三个坑
写对语法只是第一步,运行出错或结果不对,大概率栽在这几个地方:
- 忘记加
RECURSIVE关键字(MySQL/PostgreSQL 必须显式声明,SQL Server 的WITH默认允许递归但建议写全) - 递归连接条件写反,比如写成
ON t.parent_id = c.id(父找子变成子找父),结果为空或死循环 - 没设终止条件,又没建好索引,碰到环状数据(比如 A→B→A)或超深树(>1000 层),直接触发
max_recursion_depth限制或超时
实际中,树形结构往往还带聚合需求(比如统计每个部门下总人数)。这时得先用 WITH RECURSIVE 展开全路径,再在外面套一层 GROUP BY 或窗口函数——不能试图在递归体内部做 SUM(),CTE 里不支持聚合下推。
递归 CTE 不是银弹,但它把原来要 5~10 次查询 + 应用层拼接的逻辑,压进一条 SQL。关键在写对锚点和连接逻辑,其余都是体力活。











