sql server递归cte必须用无关键字写法,with须为语句首词,用union all连接锚与递归成员,列数类型顺序一致,需加option(maxrecursion n),并防自环死循环。

SQL Server 存储过程中不能用 WITH RECURSIVE,这是 PostgreSQL/MySQL 8.0+ 的语法;直接照搬会报错 Incorrect syntax near the keyword 'RECURSIVE'。必须改用 SQL Server 原生的无关键字递归 CTE 写法,并严格满足语法规则。
SQL Server 存储过程里写递归 CTE 的位置限制
递归 CTE 必须是存储过程内某条独立语句的**第一个词**,不能嵌套在 IF、BEGIN...END 或变量赋值之后,也不能放在 DECLARE 后面但未进入执行流的位置。
- 错误写法:
IF @id IS NOT NULL BEGIN WITH Tree AS (...) SELECT * FROM Tree END→ 报错Incorrect syntax near 'WITH' - 正确写法:把整个
WITH ... SELECT块作为IF分支内的第一条可执行语句,且前面不能有空行或注释干扰 - 若需动态根节点,用参数驱动锚查询,例如
WHERE id = @rootId,避免拼接 SQL 字符串
递归 CTE 必须用 UNION ALL 连接锚与递归成员
SQL Server 不接受 UNION 或单独的 SELECT,必须显式用 UNION ALL 连接两部分,且列数、类型、顺序必须完全一致。
- 漏掉锚成员(如只写下属查找逻辑)→ 编译失败或结果为空
- 递归成员里多选一列(比如加了
GETDATE())→ 报错All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions - 用
UNION替代UNION ALL→ 可能意外去重,导致某层节点被删,递归提前终止
不设 MAXRECURSION 就会卡在 100 层
SQL Server 默认递归上限是 100 层,查组织架构、BOM 或评论嵌套时极易触发错误:The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
- 必须在最终
SELECT/INSERT语句末尾加OPTION (MAXRECURSION n),例如OPTION (MAXRECURSION 500) -
MAXRECURSION 0表示无限制,但生产环境禁用——失控递归会拖垮会话甚至实例内存 - 该选项只对当前语句生效,不能写在 CTE 定义里,也不能跨语句继承
- 若树深波动大,建议把
@maxRecursion设为存储过程输入参数,由调用方控制
防无限循环的关键细节:锚点过滤 + 递归推进 + 自环兜底
最隐蔽的坑是数据脏导致的自关联死循环,比如 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 落盘。这点容易被忽略,等发现要查两次时再改就得多绕一倍逻辑。










