cte在存储过程与普通查询中行为一致,但变量作用域、谓词下推时机、执行上下文易致性能骤降或结果偏差;必须前置分号,谓词需下推至各cte内,禁用非确定性函数,且递归须设显式终止条件。

CTE 在存储过程中和普通查询里行为一致,但变量作用域、谓词下推时机、执行上下文这三点容易出错,直接导致性能骤降或结果偏差。
WITH 前必须加分号,否则存储过程编译失败
SQL Server 和 PostgreSQL 都要求 WITH 是语句开头;如果前一条是 SET @var = ... 或 IF 块结尾没分号,就会报 Incorrect syntax near the keyword 'with'。
- 所有 CTE 定义前统一加
;,哪怕前面是空行或注释 - 在存储过程里尤其要检查
DECLARE、SET、PRINT等语句末尾是否带分号 - DBeaver 或 SSMS 的自动补全不可靠,生产脚本必须显式写
别把高选择率条件留在外层,谓词必须下推到每个 CTE
比如想查 tenant_id = @tenant_id AND status = 'active' 的用户订单,若只在外层 WHERE 过滤,CTE 可能先扫全表再裁剪——而 @tenant_id 本可走索引。
- 每个 CTE 的子查询里都带上
WHERE tenant_id = @tenant_id - 避免在 CTE 中用
SELECT *,只取后续真正需要的列 - 如果 CTE 被
UNION ALL多次引用,且基表大,考虑改用临时表并建索引
UNION ALL 各分支列名、数量、类型必须严格对齐
常见报错 all queries in a UNION must have the same number of columns,表面是列数不等,深层常因隐式转换(如 VARCHAR 和 NVARCHAR)或别名缺失引发。
- 显式写出所有列名,不用
SELECT * - 各分支中同位置列用
CAST或CONVERT统一类型,比如都转成DECIMAL(18,2) - 列名以第一个分支为准,后续分支用
AS显式对齐,别依赖数据库自动推导
递归 CTE 必须设终止条件,且不能在 WHERE 里用非确定性函数
在存储过程中跑递归 CTE,若没限制层级或父 ID 检查,可能卡死或超时。PostgreSQL 和 SQL Server 对递归成员的 WHERE 子句有严格限制。
- 锚点部分(anchor)过滤根节点,递归部分(recursive term)加
level 或 <code>parent_id IS NOT NULL - 禁止在递归分支的
WHERE里写GETDATE()、NEWID()等非确定性函数 - SQL Server 默认递归上限是 100,需用
OPTION (MAXRECURSION n)调整,但别设为 0
最易被忽略的是:CTE 不是临时表,它不缓存统计信息,也不支持索引——你看到的“逻辑拆分”只是给优化器多了一次重写机会,实际是否物化完全取决于数据库版本和查询复杂度。别指望加个 WITH 就自动提速,先 EXPLAIN 或查看执行计划里的 Compute Scalar 和 Table Spool 节点再说。











