sql server递归cte必须用无recursive关键字的with+union all结构,with须为首词,锚与递归成员列数类型顺序一致,并需显式引用自身、加option(maxrecursion n)防自环。

SQL Server 里不能用 WITH RECURSIVE,抄 MySQL/PostgreSQL 写法直接报错;必须用无 RECURSIVE 关键字的 WITH + UNION ALL 结构,且锚和递归部分顺序、引用关系不能错。
SQL Server 的递归 CTE 必须满足两个硬性条件
它不认 WITH RECURSIVE,只认 WITH 开头、UNION ALL 连接、递归支中必须显式引用自身 CTE 名——三者缺一不可。
-
UNION写成UNION ALL:漏掉ALL会导致去重逻辑干扰树形结构,子节点重复时数据丢失 - 递归支里没写
JOIN Tree ON ...或写成JOIN Employees ON ...:必须出现Tree(你定义的 CTE 名),否则 SQL Server 不认为这是递归 - 锚查询没加
WHERE parent_id IS NULL或WHERE id = @rootId:会从所有根节点开始展开,生成多棵树混在一起的结果 -
WITH没放在批处理最前面:比如套在IF块里却前面有注释或空行,会报 “Incorrect syntax near 'WITH'”
层级字段和路径拼接是树形展示的基础
光查出父子关系不够,前端或报表要缩进、排序、识别深度,得靠 Level 和 Path 字段。这两个必须在 CTE 里算好,别指望外层再加工。
-
Level从 1 开始,每次递归+ 1;避免用0起始,否则和OPTION (MAXRECURSION n)的默认行为容易混淆 -
Path推荐用CONCAT('/', id)起步,递归支用CONCAT(t.Path, '/', e.id),保证可排序、可截断、可做LIKE查询 - 如果表主键是字符串(如部门编码
'DEPT-001'),Path字段必须用定长字符类型(如VARCHAR(500)),否则超长被截断后路径失效 - 别在 CTE 里用
REPLICATE('─', Level * 2)拼前缀——那是展示层逻辑,CTE 只管结构,留到最终SELECT再算
存储过程中调用递归 CTE 的常见陷阱
CTE 不是变量,也不是函数,它没有作用域嵌套概念,但有严格的语句位置约束。在存储过程里写错位置,比在 SSMS 里还容易翻车。
-
WITH必须是IF分支里的第一条可执行语句:下面这样会失败:IF @rootId IS NOT NULL BEGIN -- 注释或空行在这里就违规 WITH Tree AS (...) SELECT * FROM Tree; END - 动态根节点别拼 SQL:用参数驱动锚查询,例如
WHERE id = @rootId,而不是EXEC('WITH Tree AS (...) SELECT * FROM Tree WHERE id = ' + @rootId)—— 动态 SQL 无法复用执行计划,且易注入 - 深度控制要外显:默认
MAXRECURSION 100很容易不够,尤其组织架构超 6 层或评论嵌套深;必须在最终SELECT后加OPTION (MAXRECURSION 500),且不能写在 CTE 定义里 - 别在外层再套一层
SELECT *包装 CTE:除非真要ORDER BY Path或分页,否则纯增解析开销,SQL Server 不会优化掉这层包装
查出来之后怎么安全聚合子树数据?
递归 CTE 本身不能 GROUP BY 或用 SUM(),所有聚合必须移至外层。否则要么报错,要么结果爆炸式翻倍——因为每条递归路径都算一次。
- 先让 CTE 展开完整树形(含
id,parent_id,Level,Path),再JOIN业务表(如员工表、订单表) - 若要“某节点及其所有子节点”的汇总值,
JOIN条件用ON t.id = orders.dept_id(假设 CTE 输出的是每个节点的所有后代) - 若要“某节点的所有上级部门”汇总,则 CTE 应向上展开祖先链,
JOIN改为ON t.ancestor_id = orders.dept_id -
orders.dept_id字段必须建索引,否则外层JOIN在深树下可能触发全表扫描,性能断崖下跌
最常被忽略的一点:CTE 输出的是一张扁平表,不是真正的树对象。路径截断、层级过滤、兄弟排序这些操作,都得靠外层 WHERE、ORDER BY、STRING_SPLIT 或 CHARINDEX 配合完成——CTE 本身不提供树 API。










