SQL Server递归CTE必须将WITH置于批处理最前端且不可加RECURSIVE,需用UNION ALL连接锚点与递归成员、递归部分必须引用CTE名,深度超100须在最终SELECT后加OPTION (MAXRECURSION n),缩进推荐REPLICATE而非自关联。

SQL Server存储过程里写递归CTE必须把WITH放最前面
SQL Server不认WITH RECURSIVE,抄PostgreSQL或MySQL 8.0+的写法会直接报错“Incorrect syntax near the keyword 'RECURSIVE'”。更关键的是,WITH必须是批处理中第一个可执行语句——不能套在IF、BEGIN或DECLARE后面。
常见错误现象:
把WITH Tree AS (...)写在IF @rootId IS NOT NULL BEGIN之后 → 报错“Incorrect syntax near 'WITH'”
- 正确做法:把整个
WITH ... SELECT块放在IF分支内部,且确保它是该分支第一条语句 - 锚查询务必加限定条件,比如
WHERE id = @rootId或WHERE parent_id IS NULL,否则可能生成多棵树 - 别用字符串拼接动态SQL来换根节点,参数化更安全,也避免SQL注入
递归CTE必须用UNION ALL,且递归成员要引用自身CTE名
UNION和UNION ALL在这里不是可选项:用UNION会导致去重,树节点重复时结果漏数据;递归成员里没写FROM Tree(即CTE自身名)→ 查询只跑锚点,不递归。
常见错误现象:
递归部分写成FROM department d INNER JOIN department dt ON d.parent_id = dt.id → 永远只查一层,因为没引用CTE名Tree
- 锚成员和递归成员之间必须用
UNION ALL连接 - 递归成员中至少一处要出现CTE名(如
FROM Tree或JOIN Tree),否则SQL Server不识别为递归 - 层级字段(如
depth)建议从0开始,方便后续按深度排序或截断
递归深度超100要显式加OPTION (MAXRECURSION n)
SQL Server默认最多递归100层,查5级以上的组织架构或评论嵌套很容易触发“The statement terminated. The maximum recursion 100 has been exhausted”。这个限制不能在CTE定义里设,只能加在最终SELECT语句末尾。
常见错误现象:
加了OPTION (MAXRECURSION 0) → 生产环境可能栈溢出或耗尽内存
把OPTION写在WITH块里 → 语法错误,提示“Incorrect syntax near 'OPTION'”
-
OPTION (MAXRECURSION 500)比0更稳妥,尤其当树深不确定时 - 如果存储过程被不同业务调用(如前端分页查3层,后台导出要查全部),建议把
@maxRecursion设为输入参数 - 该选项只对当前语句生效,不影响其他查询,也不需要
SET全局配置
查完树形结果后按层级缩进,用REPLICATE比自关联更可靠
前端要显示“├─ 部门A”、“│├─ 小组B”这类视觉缩进,别用LEFT JOIN自关联模拟层级——容易因数据稀疏漏行或重复。用REPLICATE拼空格是轻量、确定性高的方案。
常见错误现象:
写LEFT JOIN Tree t2 ON t1.depth = t2.depth - 1 → 某层缺失时整条路径断裂,缩进错乱
- 示例:
REPLICATE('│ ', t.depth) + '├─ ' + t.name,其中防HTML合并空格 -
depth字段必须来自CTE计算,不能靠外部计数器或临时表模拟 - 如果需按“先序遍历”排序(父节点总在子节点前),加
ORDER BY sort_path比ORDER BY depth更准,但需在CTE里构造路径字符串
递归CTE本身不难,但SQL Server对语法位置、连接方式、深度控制的约束很具体——漏掉任一环,要么报错,要么结果静默异常。真正麻烦的是混合场景:比如既要按深度分页,又要带祖先路径,还得兼容旧版SQL Server 2008(不支持CTE嵌套)。这时候得回退到临时表+循环的老办法,而不是硬套CTE。

















