SQL Server存储过程中的递归CTE必须用WITH+UNION ALL结构,WITH须为语句首词,锚成员与递归成员列数类型顺序一致,需加OPTION(MAXRECURSION n)且防自环。

SQL Server 存储过程中不能用 WITH RECURSIVE,直接写会报错 Incorrect syntax near the keyword 'RECURSIVE'。必须用无关键字的 WITH + UNION ALL 结构,且 WITH 必须是语句第一个词。
WITH 必须是存储过程里某条语句的第一个词
常见错误是把 WITH 套在 IF 或 BEGIN...END 里,但前面还有别的语句(比如 SELECT、SET 或空行)。SQL Server 要求它必须是该分支内第一条可执行语句。
- 错:
IF @rootId IS NOT NULL BEGIN SELECT 1; WITH Tree AS (...) SELECT * FROM Tree END→ 报错Incorrect syntax near 'WITH' - 对:
IF @rootId IS NOT NULL BEGIN WITH Tree AS (...) SELECT * FROM Tree OPTION (MAXRECURSION 500); END - 如果需要多条件分支(比如按部门类型走不同树),别硬塞进一个 CTE,拆成多个独立
WITH块更稳
UNION ALL 是硬性要求,不是风格选择
递归 CTE 的锚成员和递归成员之间必须用 UNION ALL 连接。用 UNION 看似能跑,但会去重——同一层两个同名节点会被删掉一个,整棵树就断了。
- 列数、类型、顺序必须完全一致,否则报错
All queries combined using a UNION... - 漏掉锚成员(比如只写下属查找逻辑)→ 结果为空或编译失败
- 递归成员里多加一列(如
GETDATE())→ 类型不匹配直接报错 - 执行计划里若出现
Sort算子在递归分支里,大概率是误用了UNION
OPTION(MAXRECURSION n) 必须加在最终语句末尾
默认上限 100 层,查组织架构或 BOM 时极易触发 The maximum recursion 100 has been exhausted。这不是超时,是 SQL Server 主动终止,且不返回部分结果。
-
OPTION (MAXRECURSION n)必须写在SELECT/INSERT语句末尾,不能放在 CTE 定义里 -
MAXRECURSION 0表示不限制,但生产环境禁用——数据脏或自环会导致会话卡死、内存耗尽 - 建议把
@maxRecursion设为输入参数,由调用方控制,比如“最多展开 5 级部门”就传5 - 该选项只对当前语句生效,不能跨语句继承
防自环比设深度更重要
最隐蔽的坑不是层数超限,而是数据脏导致的无限循环:比如 ManagerID = EmployeeID,或 parent_id 为空没过滤,递归就原地打转。
- 锚成员必须严格限定顶层节点,例如
WHERE ManagerID IS NULL或WHERE parent_id = 0 - 递归成员 JOIN 条件要能推进层级,典型写法是
ON e.ManagerID = cte.EmployeeID,不是反向 - 兜底建议:在递归成员里加
WHERE e.EmployeeID != cte.EmployeeID - 调试时可在 CTE 中加
LEVEL INT列并用LEVEL + 1计数,方便观察是否卡在某一层
CTE 生命周期只限于紧随其后的那一条语句,想复用结果得先 INSERT INTO #temp 落到临时表——这点容易被忽略,尤其当你要先统计再拉明细时。

















