必须用sum() over(partition by... order by...),因group by会压缩行数而无法保留逐行累计值;缺partition by则全表累计,缺order by则组内无序等价于总和。

SQL分组累计求和必须用SUM() OVER,不能靠GROUP BY + SUM()
因为GROUP BY会压缩行数,而累计求和要求每行都保留原始数据并附带当前分组内的累加值。窗口函数是唯一标准解法,几乎所有主流数据库(PostgreSQL、SQL Server、Oracle、MySQL 8.0+、DuckDB)都支持。
常见错误是写成:SELECT dept, SUM(salary) FROM emp GROUP BY dept ORDER BY hire_date——这根本得不到“按入职时间顺序逐行累加”的效果,连结果行数都不对。
-
SUM(salary) OVER (PARTITION BY dept ORDER BY hire_date):按部门分组,再在组内按入职时间排序后累计 - 缺
PARTITION BY就是全表累计;缺ORDER BY就是分组内无序求和(等价于该组总和,每行重复) - MySQL 5.7 或 SQLite(未启用窗口函数扩展)会报错
ERROR 1064: You have an error in your SQL syntax,需确认版本或换数据库
ORDER BY子句决定累计方向,且必须可排序字段
累计不是自动从第一行加到最后一行——它严格依赖ORDER BY表达的逻辑顺序。如果用ORDER BY name,相同姓名会导致累计顺序不确定(尤其多行同名时),结果不可复现。
- 优先使用唯一、业务语义明确的字段,如
hire_date、id、create_time - 若主排序字段可能重复,追加次级字段保序:
ORDER BY dept, hire_date, id - 升序(默认)是从最早/最小值开始累加;降序(
ORDER BY hire_date DESC)则从最新/最大值开始——注意业务含义是否合理 - NULL值默认排在最前(PostgreSQL)或最后(MySQL),建议显式控制:
ORDER BY hire_date ASC NULLS LAST(PostgreSQL支持,MySQL不支持)
不同数据库对ROWS BETWEEN的支持差异影响性能与语义
默认的SUM() OVER (ORDER BY ...)等价于ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,即从分组首行加到当前行。但有些场景需要限制窗口范围,比如“最近3条记录的累计”。
- PostgreSQL / SQL Server / Oracle 支持完整
ROWS BETWEEN语法,如:SUM(sales) OVER (PARTITION BY region ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) - MySQL 8.0+ 支持,但不支持
NULLS FIRST/LAST;SQLite 需 3.28+ 且编译时启用ENABLE_WINDOW_FUNCTIONS - BigQuery 和 Redshift 默认行为一致,但Redshift旧版对
ROWS范围有性能警告——大数据量时避免UNBOUNDED PRECEDING,改用ROWS BETWEEN 100 PRECEDING AND CURRENT ROW更稳
累计值为NULL时的处理:别忽略COALESCE
只要参与累计的字段(如salary)某行为NULL,从那一行开始后续累计值就全为NULL——这是SQL三值逻辑的必然结果,不是Bug。
- 安全写法:
COALESCE(SUM(COALESCE(salary, 0)) OVER (PARTITION BY dept ORDER BY hire_date), 0) - 先用内层
COALESCE(salary, 0)把空薪转0,再累计,最后外层COALESCE(..., 0)兜底整个窗口结果(极少数情况窗口本身无数据) - 不要只写
COALESCE(SUM(...), 0)——SUM遇到NULL直接返回NULL,外层COALESCE已无力回天
ORDER BY会让累计结果看起来“偶尔错”,查半天才发现是同名员工入职日相同导致顺序漂移。










