跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

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

CTE中不能直接使用ORDER BY,必须放在最终SELECT中;递归CTE需锚点与UNION ALL连接,且不可含聚合等操作;多CTE按声明顺序可见,不支持跨CTE前向引用;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 名,并且只能出现在
FROM
或
JOIN
的右侧。SQL Server 还要求显式设置
MAXRECURSION
以防爆栈。 锚点和递归部分之间必须用
UNION ALL
连接,
UNION
会去重并破坏递归逻辑 递归成员中不能出现聚合、
GROUP BY
、
OUTER 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 #temp
或
CREATE TABLE #t
反而更快;PostgreSQL 9.3+ 支持
MATERIALIZED
(需显式指定),但 SQL Server 和 MySQL 目前都不原生支持物化 CTE。 用
EXPLAIN
或执行计划看 CTE 是否真的被展开,有没有重复扫描 避免在 CTE 里写
SELECT *
后又只取几列,字段越多,内存和网络开销越大 MySQL 8.0+ 对 CTE 优化较弱,复杂嵌套建议拆成临时表验证性能 事情说清了就结束。CTE 看似简单,但顺序、递归规则、作用域和执行模型这几处,最容易在调试时卡住半天。

相关文章