带阈值的分组累计求和指按排序逐行累加,当累积值达阈值(如100)时重置为0重新累加,本质是动态分段;它无法用普通窗口函数直接实现,需递归CTE模拟状态传递,依赖明确排序字段。

什么是带阈值的分组累计求和
它不是简单用 SUM() OVER (PARTITION BY ... ORDER BY ...) 就能解决的:当某组内按顺序累加到某个值(比如 100)时,后续行要“断开”,从 0 重新开始累计。这本质上是动态划分组段(running sum reset),属于窗口函数无法直接表达的逻辑。
用递归 CTE 模拟逐行判断(PostgreSQL / SQL Server / Oracle)
核心思路是把“当前行是否触发重置”变成一个可传递的状态变量。递归 CTE 是目前最通用、语义最清晰的解法。
- 必须有明确排序字段(如
id或created_at),否则“累计”无意义 - 递归锚点选最小排序值那行,
running_sum初始为该行值,group_id初始为 1 - 递归部分检查:若上一行
running_sum + 当前行值 ,则延续;否则重置 <code>running_sum为当前值,group_id加 1 - 注意 PostgreSQL 要加
SEARCH DEPTH FIRST BY id SET ordercol避免无序执行
WITH RECURSIVE ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY id) AS rn
FROM sales
),
rec AS (
SELECT id, category, amount, amount AS running_sum, 1 AS group_id, rn
FROM ranked WHERE rn = 1
UNION ALL
SELECT r.id, r.category, r.amount,
CASE WHEN rec.running_sum + r.amount <= 100
THEN rec.running_sum + r.amount
ELSE r.amount END,
CASE WHEN rec.running_sum + r.amount <= 100
THEN rec.group_id
ELSE rec.group_id + 1 END,
r.rn
FROM ranked r
JOIN rec ON r.category = rec.category AND r.rn = rec.rn + 1
)
SELECT id, category, amount, running_sum, group_id
FROM rec
ORDER BY category, rn;MySQL 8.0+ 可用变量模拟(但需极度谨慎)
MySQL 不支持递归 CTE 的早期版本常用用户变量,但 8.0+ 仍有人沿用——风险在于:官方文档明确说明变量赋值顺序**不保证**,尤其在 ORDER BY 和窗口函数混用时极易出错。
- 必须用
ORDER BY子句强制排序,并且该排序要写在变量引用的同一层查询中 - 不能在同一个
SELECT里既读又写同一变量(如@sum := @sum + amount),某些版本会跳过中间状态 - 更安全的做法是先用
ROW_NUMBER()排序,再在外层用变量,但仍不如递归 CTE 可靠
为什么不能只靠窗口函数 + CASE?
因为窗口函数的累计逻辑是静态依赖排序,而“重置点”本身由累计结果动态决定——这是一个循环依赖:running_sum 影响是否重置,是否重置又影响下一个 running_sum。SQL 标准中没有原生支持这种反馈式累计的语法。
你可能会看到用 SUM(amount) OVER (...) - SUM(CASE WHEN ... THEN amount END) OVER (...) 的变通写法,但那只能处理最多一次重置,无法应对多次阈值穿越。真要硬套,代码会迅速变得不可维护且容易漏 case。
实际项目里,如果数据量不大(

















