首页/数据/SQL 速查/多个 CTE 与互相引用

多个 CTE 与互相引用

一条 WITH 里可以定义多个 CTE(逗号分隔);后定义的可引用先定义的,形成流水线式查询结构,不允许循环引用。

SQL 速查数据更新于 2026-10-13

一句话里写多个 CTE

WITH 后面可以用逗号分隔定义多个公共表表达式,后定义的名字可以引用前面已经定义的名字,整体变成一条自上而下的加工流水线:先做过滤,再做聚合,最后连接输出。不能反向引用,也不能互相引用成环。把复杂查询拆成几个有名字的步骤,除了可读性更好,也方便在排查时逐个替换中间步骤、核对每一步的行数与字段。

基本信息

适用平台MySQL 8.0 / PostgreSQL 12 及以上
数量限制一次 WITH 可定义任意多个,用逗号分隔
引用方向只能引用前面已定义的步骤,不允许循环引用
递归写法需要自引用时必须写成递归形式,显式声明递归关键字
作用范围只在当前这一条语句里有效,语句结束即消失

典型使用场景

  • 长 SQL 拆解:把过滤、聚合、排名分成几个命名步骤,便于评审
  • 同一个中间结果被多次引用,避免重复粘贴相同子查询
  • 分层加工:先取活跃用户,再取大额订单,最后做连接输出
  • 排查线上慢查询时逐个步骤单独执行,快速定位是哪一步变慢
  • 迁移与重构:把嵌套很深的子查询改写成平铺的流水线结构

多步骤流水线示例

多步骤流水线示例
WITH
active_users AS (                       -- 第一步:筛出活跃用户
    SELECT id, name FROM users WHERE status = 1
),
big_orders AS (                          -- 第二步:按用户汇总大额订单
    SELECT user_id, SUM(amount) AS total
    FROM orders
    WHERE amount > 100
    GROUP BY user_id
)
SELECT a.id, a.name, b.total             -- 第三步:两步骤连接输出
FROM active_users a
JOIN big_orders b ON b.user_id = a.id
ORDER BY b.total DESC;

-- 等价但仍然清晰的另一种组织方式:先算总量再筛
-- 注:后面定义的步骤可以引用前面定义的步骤,反过来不行

使用要点

关注点说明
命名可读性步骤名写清楚在做什么,评审时不用读完整 SQL
引用顺序只允许向后引用,出现环时语句直接报错
排序作用中间步骤里的排序通常没有意义,除非同时限制了行数
物化与否由优化器决定,写的时候不要假定它一定被缓存或一定被内联
PostgreSQL 12 起,非递归且只被引用一次的公用表表达式默认可以被内联进外层查询,想强制物化可以显式标注;12 之前的版本一律物化。写语句时不要把「物化」当成可以依赖的性能手段,需要稳定中间结果时用临时表。
MySQL 要求递归写法显式声明,并且有递归深度上限;一次定义多个步骤时,只有自引用的那一个需要标记为递归。

易错点

  • 步骤之间互相引用形成环,语句直接报错,拆分时保持单向依赖
  • 以为中间步骤一定会被物化:实际由优化器决定,据此推断性能容易得出错误结论
  • 在中间步骤里写排序却不限制行数,多数情况下被优化器忽略,白白增加理解成本
  • 把只用到一次、逻辑又很短的子查询强行拆成多个步骤,反而让 SQL 变长变难读
  • 步骤名重复或含义模糊,出问题时无法快速判断是哪一步算错了

数据来源与参考

  • MySQL 8.0 Reference Manual · WITH Clause
  • PostgreSQL Documentation · WITH Queries
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌