mysql 8.0+ 用 sum(amount) over(partition by category order by id) 最直接实现分组累计求和;必须同时指定 partition by 和 order by,否则结果不可复现,旧版需变量或自连接但风险高或性能差。

MySQL 8.0+ 用 SUM() OVER() 最直接
窗口函数是目前最干净的解法,不需要自连接或变量。关键在于 OVER() 子句里必须明确指定分组和排序逻辑,否则累计和会跨组错乱。
常见错误是只写 OVER(PARTITION BY category) 却漏掉 ORDER BY —— 这会导致每组内求和顺序不确定,结果不可复现。
- 必须同时包含
PARTITION BY(分组)和ORDER BY(组内累计顺序) - 如果想按时间递增累计,
ORDER BY create_time比ORDER BY id更语义准确 - 注意:MySQL 5.7 及更早版本不支持窗口函数,强行使用会报错
ERROR 1064
SELECT category, amount, SUM(amount) OVER(PARTITION BY category ORDER BY id) AS cumsum FROM sales;
PostgreSQL 和 SQL Server 同样适用 SUM() OVER()
语法一致,但 PostgreSQL 对 NULL 处理更严格:如果 amount 为 NULL,SUM() 会跳过它;而 SQL Server 在某些兼容模式下可能把 NULL 当作 0,需提前用 COALESCE(amount, 0) 统一处理。
性能上,三者在大数据量时都依赖排序字段是否有索引。没索引的 ORDER BY 字段会让 OVER() 执行变慢,甚至触发磁盘临时表。
- PostgreSQL 推荐在
(category, id)上建复合索引 - SQL Server 建议用
INCLUDE把amount加进索引,避免回表 - 别在
OVER()里用表达式排序,比如ORDER BY UPPER(name),会失去索引优势
MySQL 5.7 或旧版只能靠变量模拟,但要注意执行顺序陷阱
用用户变量 @cumsum := @cumsum + amount 看似简单,实际极易出错:MySQL 不保证 SELECT 中变量赋值的执行顺序,尤其当语句含 JOIN、GROUP BY 或优化器重排时,累计值可能突然归零或跳跃。
唯一相对可靠的方式是强制用子查询先排序,再逐行计算:
SELECT
category,
amount,
@cumsum := IF(@prev = category, @cumsum + amount, amount) AS cumsum,
@prev := category
FROM (SELECT * FROM sales ORDER BY category, id) t,
(SELECT @cumsum := 0, @prev := '') init;
-
ORDER BY必须在子查询里完成,不能只在外层加 - 变量初始化必须和主查询在同一
SELECT,拆成两条语句会失效 - 该写法在 MySQL 8.0+ 已被标记为“不推荐”,未来版本可能彻底移除支持
用自连接实现兼容性最强,但数据量大时很慢
原理是让每行和它所在分组中所有“
典型错误是忘记限制关联条件中的分组一致性,导致跨组累加:
- 必须同时匹配
t1.category = t2.category和t2.id - 如果排序字段有重复值(如多个同一天的订单),要加
OR (t2.id = t1.id AND t2.rowid 避免重复计数(需有唯一辅助字段) - WHERE 条件不能下推到关联子句里,否则可能过滤掉用于累计的中间行
SELECT t1.category, t1.amount, SUM(t2.amount) AS cumsum FROM sales t1 JOIN sales t2 ON t1.category = t2.category AND t2.id <p>真正麻烦的不是语法,而是不同数据库对“分组内顺序”的隐含假设——有些默认按插入顺序,有些按主键,有些根本无序。只要没显式 <code>ORDER BY</code>,累计和就不可靠。</p>











