SQL CTE(公用表表达式)使用技巧

12次阅读

cte 中不能直接使用 order by,必须放在最终 select 中;递归 cte 需锚点与 union all 连接,且不可含聚合等操作;多 cte 按声明顺序可见,不支持跨 cte 前向引用;cte 非物化,重复引用会多次执行。

SQL CTE(公用表表达式)使用技巧

CTE 里不能直接用 ORDER BY,除非配合 TOP 或 OFFSET

很多人写完 WITH cte AS (SELECT * FROM users ORDER BY created_at) 就报错,因为标准 SQL 规定:CTE 定义体内不允许出现 ORDER BY,它不保证结果顺序,也不被允许存在——哪怕你只是想“先排好再用”。

真正能生效的排序,得放在最终 SELECT 里;如果非要在 CTE 内部“控制顺序”,常见做法是加 TOP (100) PERCENT(SQL Server)或 OFFSET 0 ROWS(通用),但注意:这仅是绕过语法检查, 不保证后续引用时顺序保留

  • SQL Server 可临时用 SELECT TOP (100) PERCENT * FROM users ORDER BY created_at
  • PostgreSQL / SQL Standard 必须把 ORDER BY 移到最外层 SELECT
  • CTE 不是视图,它不缓存排序状态,多次引用同一 CTE 时顺序可能不一致

递归 CTE 必须包含锚点 + 递归成员,且 UNION ALL 是硬性要求

WITH RECURSIVE(PostgreSQL)或普通 WITH(SQL Server)做树形查询时,漏掉锚点查询、或者用了 UNION 而不是 UNION ALL,都会直接报错或无限循环。

锚点是起点(比如所有根节点),递归成员必须引用自身 CTE 名,并且只能出现在 FROMJOIN 的右侧。SQL Server 还要求显式设置 MAXRECURSION 以防爆栈。

  • 锚点和递归部分之间必须用 UNION ALL 连接,UNION 会去重并破坏递归逻辑
  • 递归成员中不能出现聚合、GROUP BYOUTER JOIN 等不支持的操作
  • SQL Server 默认最大递归深度是 100,超限报错 Msg 530,需加 OPTION (MAXRECURSION n)

多个 CTE 共享作用域,但不能跨 CTE 引用未定义的别名

WITH a AS (……), b AS (……), c AS (SELECT * FROM a JOIN b ……) 是合法的,但反过来——比如在 a 里引用 b,就会报 Invalid object name 'b'。CTE 按声明顺序依次可见,后声明的可以前向引用,但前声明的看不到后面的。

另外,CTE 名称只在当前语句有效,下一条 SELECT 就失效;也不能在存储过程中跨语句复用,除非重新定义。

  • CTE 列表中靠前的项无法引用靠后的项,哪怕看起来“逻辑上应该能”
  • 嵌套子查询里不能直接用外层 CTE,必须把 CTE 拉平或改用临时表
  • 名字冲突时,CTE 优先级高于同名表,容易导致 column not found 却查不到原因

CTE 性能不等于视图或子查询,过度嵌套可能拖慢执行计划

CTE 是逻辑结构,不是物化结果。数据库优化器通常会把它内联展开,等价于巨型子查询。这意味着:它不会自动缓存中间结果,也不会减少重复计算——如果同一个 CTE 被引用三次,很可能执行三次底层查询。

某些场景下(如大表关联 + 过滤后反复使用),用 SELECT …… INTO #tempCREATE TABLE #t 反而更快;PostgreSQL 9.3+ 支持 MATERIALIZED(需显式指定),但 SQL Server 和 MySQL 目前都不原生支持物化 CTE。

  • EXPLAIN 或执行计划看 CTE 是否真的被展开,有没有重复扫描
  • 避免在 CTE 里写 SELECT * 后又只取几列,字段越多,内存和网络开销越大
  • MySQL 8.0+ 对 CTE 优化较弱,复杂嵌套建议拆成临时表验证性能

事情说清了就结束。CTE 看似简单,但顺序、递归规则、作用域和执行模型这几处,最容易在调试时卡住半天。

text=ZqhQzanResources