正确写法是SUM(value) OVER(PARTITION BY group_col ORDER BY sort_col),必须显式指定ORDER BY子句才能实现分组内逐行累计求和,否则默认返回分组总和而非运行总计。

SQL里SUM() OVER()怎么写才能得到分组内运行总计
必须显式指定ORDER BY子句,否则SUM() OVER(PARTITION BY ...)默认按无序处理,结果不可预测——多数数据库(如PostgreSQL、SQL Server)会返回整个分组的总和,而不是逐行累加。
正确写法是:SUM(value) OVER(PARTITION BY group_col ORDER BY sort_col ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。其中ROWS BETWEEN ...虽常可省略(因是默认窗口帧),但显式写出更安全,尤其在MySQL 8.0+或旧版PostgreSQL中避免隐式行为差异。
-
PARTITION BY定义分组边界,比如按category分组就写PARTITION BY category -
ORDER BY决定累加顺序,必须是确定性列(如时间戳、ID),不能是RANDOM()或无索引字段,否则排序不稳定 - 若
ORDER BY列有重复值,相同值的行会“并列累加”(即窗口包含所有相等行),需配合ROW_NUMBER()或额外排序键消除歧义
MySQL 8.0和PostgreSQL对SUM OVER的兼容性差异
MySQL 8.0+完全支持标准语法,但默认字符集排序可能影响ORDER BY行为(比如中文字段用utf8mb4_0900_as_cs和utf8mb4_general_ci结果不同);PostgreSQL要求ORDER BY列类型可比较,且对NULL默认排在最前(NULLS FIRST),而MySQL把NULL排在最后。
- 跨数据库移植时,显式写
ORDER BY ts ASC NULLS LAST(PostgreSQL)或改用IFNULL(ts, '9999-12-31')(MySQL)统一NULL位置 - MySQL不支持
NULLS FIRST/LAST语法,强行写会报错ERROR 1064 (42000) - PostgreSQL中若
ORDER BY列无索引,大表上SUM() OVER()性能可能骤降,建议在(group_col, sort_col)建联合索引
遇到Window function is not allowed in WHERE/HAVING怎么办
这是常见误用:想在WHERE里过滤运行总计结果,比如WHERE running_sum > 100。窗口函数只能出现在SELECT或ORDER BY中,不能用于WHERE或HAVING。
- 必须用子查询或CTE包裹:先算出
running_sum,再在外层WHERE过滤 - 别写成
SELECT *, SUM(x) OVER(...) AS s FROM t WHERE s > 100——这会直接报错 - CTE写法更清晰:
WITH t_with_sum AS ( SELECT *, SUM(amount) OVER(PARTITION BY dept ORDER BY date) AS running_total FROM sales ) SELECT * FROM t_with_sum WHERE running_total > 100;
为什么同一行数据多次执行SUM OVER结果不一样
根本原因通常是ORDER BY列存在重复值且未提供稳定排序依据。例如按created_date排序,但多条记录created_date完全相同,数据库可能每次返回不同物理顺序,导致窗口包含的行集合浮动。
- 解决方法是在
ORDER BY后追加唯一列,比如ORDER BY created_date, id - 如果没唯一列,可用
ORDER BY created_date, ROW_NUMBER() OVER(PARTITION BY created_date ORDER BY ctid)(PostgreSQL)或ORDER BY created_date, (SELECT MIN(id) FROM dual)(MySQL伪方案,实际应补主键) - 另一个隐藏坑:某些ORM(如Django ORM)自动生成的SQL可能隐式添加
LIMIT或OFFSET,导致窗口函数作用于截断后的结果集,务必检查生成SQL是否完整
运行总计看着简单,真正上线时最容易栽在排序稳定性、NULL处理和子查询嵌套这三处。特别是当业务要求“按创建时间累加”,而批量导入又造成大量同秒级时间戳时,不加第二排序键几乎必然出错。

















