递归CTE必须先定义锚点再写递归成员,顺序不可颠倒;需用UNION ALL连接,显式处理空值拼接路径,WHERE仅限锚点或最终结果过滤,防环需依赖数据库特性或手动实现。
递归 CTE 必须定义锚点和递归成员,且顺序不能颠倒
SQL Server、PostgreSQL(14+)、Oracle 都支持递归 CTE,但语法细节有差异。核心是两部分必须显式分开:WITH RECURSIVE(PostgreSQL)或 WITH(SQL Server),然后先写锚点查询(即最顶层节点),再用 UNION ALL 连接递归查询。如果把递归部分写在前面,或用了 UNION(去重),会直接报错或结果异常。
常见错误现象:Maximum recursion exceeded(SQL Server)、recursive reference must be in FROM clause(PostgreSQL)。这是因为递归调用没正确引用自身别名,或没限制层级深度。
- 锚点部分只能查出根节点(例如
parent_id IS NULL或level = 1) - 递归部分的
FROM必须包含 CTE 自身的别名(如FROM tree t JOIN cte ON t.parent_id = cte.id) - SQL Server 默认递归上限 100 层,超限需加
OPTION (MAXRECURSION n),设为 0 表示无限制(慎用)
拼接全路径字符串要用字符串聚合,注意空值和分隔符位置
层级路径本质是把祖先节点名按从顶到底顺序连起来,比如 'A/B/C'。不能依赖递归中的简单 + 或 || 拼接,因为初始锚点没有父路径,必须显式处理空值;否则会出现 NULL/B/C 或整个字段变 NULL。
不同数据库处理方式不同:
- SQL Server:用
ISNULL(cte.path, '') + '/' + t.name,锚点中 path 初始化为t.name - PostgreSQL:用
COALESCE(cte.path || '/', '') || t.name,更安全;锚点中 path 设为t.name::TEXT - Oracle:用
COALESCE(cte.path || '/', '') || t.name,但需确保字段类型一致(避免隐式转换失败)
如果路径含斜杠、反斜杠等特殊字符,建议提前 REPLACE 转义,否则后续解析困难。