MySQL 8.0+ 分组累计求和直接用 SUM(value) OVER (PARTITION BY group_col ORDER BY order_col) 即可,缺一不可:PARTITION BY 划分组,ORDER BY 确保组内逐行累加;漏掉 ORDER BY 则返回组内总和而非累计值,排序字段重复时建议追加唯一字段(如 id)保序。

MySQL 8.0+ 怎么用 SUM() 窗口函数做分组累计求和
直接用 SUM(value) OVER (PARTITION BY group_col ORDER BY order_col) 就行,这是最简洁、性能最好的方式。窗口函数会按分组内排序顺序逐行累加,不需要自连接或子查询。
常见错误是漏掉 ORDER BY —— 没它就变成组内总和(静态值),不是“累计”;另外 PARTITION BY 必须明确指定分组字段,否则整个结果集被当成一组。
- 确保
order_col在分组内唯一,或搭配ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW显式声明范围(默认就是这个) - 如果排序字段有重复值,累计值可能“跳变”,建议加二级排序,比如
ORDER BY date, id - MySQL 5.7 及更早版本不支持窗口函数,强行用会报错
ERROR 1064,得换方案
MySQL 5.7 或旧版怎么模拟分组累计求和
靠变量实现,但必须严格控制执行顺序:先 ORDER BY group_col, order_col,再用用户变量逐行计算。顺序乱了结果就全错。
典型写法:
SELECT group_col, order_col, value, @sum := IF(@prev = group_col, @sum + value, value) AS cumsum, @prev := group_col FROM t, (SELECT @sum := 0, @prev := NULL) AS _init ORDER BY group_col, order_col;
-
@prev必须在赋值前用于判断,所以@prev := group_col要放在表达式最后 - 不能在
WHERE或HAVING里引用累计列,变量只在 SELECT 阶段生效 - 这种写法在 MySQL 8.0+ 中已被标记为不可靠(官方文档明确警告变量赋值顺序不保证),仅作兼容性兜底
PostgreSQL 和 SQL Server 怎么写更安全
两者都原生支持标准窗口函数,写法和 MySQL 8.0+ 一致,但 PostgreSQL 对 NULL 更敏感:如果 order_col 有 NULL,默认排在最前,可能导致累计逻辑错位。
- 显式写
ORDER BY order_col NULLS LAST避免歧义 - SQL Server 的
OVER子句允许用ROWS明确帧范围,比如ROWS UNBOUNDED PRECEDING比省略更清晰 - PostgreSQL 中若需跳过
NULL值参与累计,得先用COALESCE(order_col, '9999-12-31')做转换
为什么 GROUP BY 加子查询容易出错
有人试图用 GROUP BY 配合关联子查询,比如 (SELECT SUM(t2.value) FROM t t2 WHERE t2.group_col = t1.group_col AND t2.order_col —— 这种写法在数据量稍大时性能急剧下降,且无法利用索引加速 <code> 条件。
- 每行都要触发一次子查询,时间复杂度接近 O(n²)
- 如果
order_col不是主键或没索引,执行计划常退化为全表扫描 - 某些场景下(如 order_col 有重复),子查询可能多算或漏算,而窗口函数天然按排序位置计数
真正难的不是语法,是确认业务里的“累计”到底依赖什么顺序——是时间?ID?还是某种业务权重?一旦排序逻辑模糊,所有实现都会偏移。

















