SQL分组累计求和必须用SUM() OVER(PARTITION BY ... ORDER BY ...),缺ORDER BY则退化为组内总和;PARTITION BY定义分组边界,ORDER BY确保累加顺序,二者缺一不可。

用窗口函数 SUM() OVER() 实现分组内累计求和
直接在 GROUP BY 后用 SUM() 会合并行,想保留原始明细又按组累加,必须用窗口函数。核心是把分组逻辑写进 OVER(PARTITION BY ... ORDER BY ...),而不是 GROUP BY。
常见错误是写成 SUM(value) OVER(PARTITION BY group_col) —— 缺少 ORDER BY,结果就是每组所有行都显示该组总和(非累计)。累计值必须明确排序依据,比如时间、ID 或业务序号。
-
PARTITION BY定义分组边界(如user_id、product_category) -
ORDER BY必须存在且语义合理(如created_at、seq_no),否则累计无意义 - 若排序字段有重复值,建议加唯一字段兜底,例如
ORDER BY created_at, id
避免 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 写法冗余
这个框架是累计计算的默认行为,只要写了 ORDER BY,不显式声明 ROWS 也没问题。多数场景下直接写 SUM(value) OVER(PARTITION BY group_col ORDER BY ts) 就够了。
只有当你需要“截止到上一行”或“滑动窗口”时才需定制 ROWS 范围。比如想排除当前行用 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING,但这是特例,不是累计求和的常规需求。
- 显式写
ROWS不仅啰嗦,还容易写错边界(如漏掉UNBOUNDED) - PostgreSQL、MySQL 8.0+、SQL Server 都支持省略写法;老版本 SQLite 或某些 OLAP 引擎可能不兼容,需查文档
- 如果排序字段为空(
NULL),不同数据库处理方式不同:有的排最前,有的排最后,影响累计顺序,建议提前COALESCE(ts, '1970-01-01')处理
当分组键和排序键来自不同表时,先 JOIN 再开窗
不能在子查询里只 SELECT 出分组字段就开窗——窗口函数作用于最终结果集,所以关联、过滤、排序都要在开窗前完成。
典型错误是:先 GROUP BY 汇总再试图开窗,或者把 JOIN 放在窗口函数之后。这会导致逻辑错乱或语法报错(如 Window function is not allowed here)。
- 正确顺序:FROM → JOIN → WHERE → SELECT(含窗口函数)
- 如果关联后数据量大,注意
ORDER BY字段是否已建索引,否则累计计算可能变慢 - 避免在窗口函数里嵌套聚合(如
SUM(AVG(x)) OVER(...)),会报错;需先聚合再开窗,或改用子查询
MySQL 5.7 或旧版 PostgreSQL 不支持窗口函数怎么办
只能用自连接或变量模拟,但稳定性和可读性差很多。优先升级数据库;若不可行,变量方案仅限单线程、单用户、有序数据场景。
MySQL 5.7 示例(依赖 ORDER BY 和用户变量执行顺序):
SELECT id, user_id, amount, @cum := IF(@prev = user_id, @cum + amount, amount) AS cum_sum, @prev := user_id FROM orders, (SELECT @cum := 0, @prev := NULL) r ORDER BY user_id, created_at;
这个写法在多行并发、优化器重排或 MySQL 8.0+ 中行为不可靠,@prev 和 @cum 的赋值顺序不保证。
- 变量方案无法处理
NULL分组键,也不支持并行执行 - PostgreSQL 9.4–9.6 可用
generate_series()+ 子查询模拟,但性能更差 - 真正稳定的降级方案是应用层累加:查出排序后的明细,在代码里遍历计算,比 SQL 模拟更可控
SUM() OVER(PARTITION BY ... ORDER BY ...) 就是最简解。难点不在语法,而在确认业务上的“累计顺序”是否真的明确——比如同一秒创建的多笔订单,靠时间戳无法确定先后,这时必须引入序列号或业务流水号,否则累计结果不稳定。

















