标准JOIN无法遍历任意深度树,因其静态特性要求预设连接层数,而树深度未知时易遗漏节点、性能差且难维护;WITH RECURSIVE通过种子+迭代逐层展开,天然适配未知深度,是现代数据库遍历树的正解。
SQL 的 JOIN 是静态的,每次只能连接固定数量的表或自连接次数。树的深度未知时,你没法提前写 5 层 LEFT JOIN 去适配可能有 10 层深的组织架构。更关键的是,标准 SQL-92 不支持递归;哪怕你硬写 10 层自连接,遇到第 11 层节点就漏掉,且查询会极慢、难以维护。
现代主流数据库(PostgreSQL、SQL Server、SQLite 3.8.3+、MySQL 8.0+)都支持递归 CTE(Common Table Expression),这才是解决深层树遍历的正解。它用“种子 + 迭代”方式逐层展开,天然适配未知深度。
假设表 orgs 有字段 id、name、parent_id,要查某部门及其所有下级(含子孙):
WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 0 AS level FROM orgs WHERE id = 123 -- 起始节点(根) UNION ALL SELECT o.id, o.name, o.parent_id, t.level + 1 FROM orgs o INNER JOIN tree t ON o.parent_id = t.id)SELECT * FROM tree ORDER BY level;
注意点:
UNION ALL 必须,UNION 会去重但破坏层级逻辑,还拖慢性能infinite recursion
SET SESSION cte_max_recursion_depth = 5000;
SEARCH DEPTH FIRST BY id SET ordercol 控制遍历顺序如果业务确定最多只有 3 级(比如:省→市→区),且对性能极度敏感(如高频查询)、又不想依赖递归 CTE,才考虑手写自连接。但它本质是“硬编码深度”,不是通用解。
示例(查某省及其下辖市、区):
SELECT p.name AS province, c.name AS city, d.name AS districtFROM areas pLEFT JOIN areas c ON c.parent_id = p.id AND p.level = 1LEFT JOIN areas d ON d.parent_id = c.id AND c.level = 2WHERE p.id = 440000;
风险点:
JOIN
LEFT JOIN 条件里必须加 AND xxx.level = N,否则会跨级错误关联(比如把区直接连到省)CASE WHEN 处理有些老项目用循环查数据库(先查子,再对每个子查孙…),再在 Java/Python 里拼结果。这看似“绕过 SQL 限制”,实则灾难:
too many connections
INDEX(parent_id),但多次单查无法复用执行计划真正需要关注的,是递归 CTE 的终止条件是否严谨、是否有环(parent_id 指向自己或形成闭环),以及深度超限时的 fallback 策略——这些比纠结“用不用 JOIN”重要得多。