直接在group by后用with recursive会报错,因为递归cte必须是顶层查询或子查询的根节点,内部禁止聚合、group by等操作;正确做法是先分组排序再递归,或优先使用窗口函数。

为什么直接在GROUP BY后用WITH RECURSIVE会报错
因为 WITH RECURSIVE 是一个独立的查询结构,不能嵌套在 GROUP BY 的上下文中执行。你不能写成 SELECT ... FROM (WITH RECURSIVE ...) AS t GROUP BY ... 然后在里面再递归——递归CTE本身必须是顶层查询或子查询的根节点,且不允许与聚合同时“平级”出现。
常见错误现象:ERROR: recursive query "xxx" must not contain aggregates, GROUP BY, ORDER BY, LIMIT, etc.
- 递归CTE内部禁止任何聚合函数(
SUM()、COUNT())、GROUP BY、ORDER BY(除非在非递归分支末尾加) - 想“先分组、再对每组递归求和”,实际得换思路:把分组逻辑前置到递归外部,或让递归本身按组边界展开
- PostgreSQL 14+ 支持在递归分支中引用外部表,但依然不支持在递归定义里写
GROUP BY
正确做法:用递归CTE生成序列,再JOIN分组结果做累加
典型场景是:你有一组有序数据(比如按 category 和 seq 排列),希望对每个 category 内部按 seq 顺序做累计和(running sum),而不是全表一起算。
实操建议:
- 先用普通CTE或子查询完成分组/排序准备,例如
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY seq) AS rn FROM data) - 再用
WITH RECURSIVE对每个category单独建递归链,通过rn = 1作为锚点,rn = prev.rn + 1 AND prev.category = curr.category作为递归条件 - 递归体中用
prev.running_sum + curr.value计算累计值,避免后期再聚合
示例片段(PostgreSQL):
WITH RECURSIVE grouped_running AS (
-- 锚点:每个 category 的第一条
SELECT id, category, value, value AS running_sum, 1 AS rn
FROM ranked WHERE rn = 1
UNION ALL
-- 递归:找同一 category 下 rn+1 的下一行,并累加
SELECT r.id, r.category, r.value, gr.running_sum + r.value, r.rn
FROM ranked r
INNER JOIN grouped_running gr
ON r.category = gr.category AND r.rn = gr.rn + 1
)
SELECT * FROM grouped_running ORDER BY category, rn;
替代方案:窗口函数通常比递归CTE更简单高效
如果你真实需求只是“分组内累计求和”,SUM(value) OVER (PARTITION BY category ORDER BY seq) 就够了——它底层不递归,性能好,语法直白,还支持 RANGE/ROWS 控制范围。
什么时候才非用 WITH RECURSIVE?
- 需要基于前N行的复杂逻辑(比如:当前值 = 前两行之和 × 系数)
- 递推依赖非连续序号(如树形结构的深度优先路径和)
- 分组键本身是动态生成的,且需在递归中不断重切分(极少见)
误用递归CTE的代价:查询变慢几倍到几十倍,计划难以优化,还容易爆栈(max_recursive_depth 被触发)。
递归CTE中处理多分组的关键细节
最容易被忽略的是递归匹配条件必须严格限定分组边界,否则不同组的数据会串扰。
- 所有连接条件里,分组字段(如
category)必须出现在ON子句中,且用等值判断,不能漏 - 递归初始集(anchor)必须覆盖每个分组的起点,可用
DISTINCT ON (category) ORDER BY category, seq或ROW_NUMBER() = 1保证 - 如果原始数据有重复
seq值,必须引入唯一排序键(如id),否则递归可能跳行或死循环 - PostgreSQL 中可加
SEARCH DEPTH FIRST BY category, rn SET ordercol显式控制遍历顺序,便于调试
没有唯一序号、没显式分组隔离、又硬上递归——八成会得到错乱的累计结果,而且很难一眼看出来哪里错了。











