因为GROUP BY会压缩行数、丢失明细,只能得到每组总和;而分组内累计和需保留原始行并按指定顺序逐行累加,必须用SUM() OVER(PARTITION BY dept ORDER BY hire_date)窗口函数实现。

为什么 SUM() 加 GROUP BY 不能直接得到分组内累计和
直接写 SELECT dept, SUM(salary) FROM emp GROUP BY dept 只能拿到每组总和,不是“按入职时间顺序逐行累加”的运行总计。窗口函数才是解决这个问题的正确路径——它不压缩行数,允许在分组内部再定义排序和累积逻辑。
常见错误是把 ORDER BY 漏掉或放错位置:窗口函数里的 ORDER BY 必须出现在 OVER() 内部,且必须和累计方向一致;否则结果是整组总和(等价于没写 ORDER BY),或者报错(如 MySQL 8.0 前不支持无序窗口聚合)。
SUM() OVER(PARTITION BY ... ORDER BY ...) 的写法要点
核心结构固定:SUM(salary) OVER(PARTITION BY dept ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。其中:
-
PARTITION BY dept划定分组边界,每个部门独立计算累计值 -
ORDER BY hire_date决定累计顺序,必须是确定性排序字段(避免用id这类可能重复或无业务意义的列) -
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是默认框架,可省略,但显式写出更清晰 - 若想包含当前行之后若干行(如滑动窗口),才需调整
ROWS子句
不同数据库对 ORDER BY 的强制要求差异
PostgreSQL 和 SQL Server 要求 ORDER BY 必须存在,否则报错;MySQL 8.0+ 和 Oracle 允许省略,但会退化为组内无序累计(结果不可预测)。实际使用中一律显式写上 ORDER BY,避免跨库迁移出问题。
另一个易错点是排序字段类型:如果用 hire_date 是 DATE 类型没问题,但若用 salary 作为排序依据,相同薪资会导致累计顺序不稳定(尤其在 ROW_NUMBER() 或需要精确对齐时)。此时应追加二级排序,例如 ORDER BY salary, emp_id。
累计和与累计占比的联动写法
常需要同时算出“当前累计占本组总和的比例”,这时不能嵌套窗口函数(如 SUM(SUM() OVER(...)) OVER(...) 语法非法),而要用子查询或 CTE:
WITH grouped AS (
SELECT dept, salary, hire_date,
SUM(salary) OVER(PARTITION BY dept ORDER BY hire_date) AS running_sum,
SUM(salary) OVER(PARTITION BY dept) AS total_dept
FROM emp
)
SELECT dept, salary, hire_date,
running_sum,
ROUND(running_sum * 100.0 / total_dept, 2) AS pct
FROM grouped;注意 total_dept 的窗口定义里没有 ORDER BY,所以它返回的是每行所在组的固定总和;而 running_sum 是动态变化的。两者的组合才能安全算出比例。
真正麻烦的是当分组键本身有 NULL 值时:PARTITION BY dept 会把所有 dept IS NULL 的行归为一组,但多数业务场景希望它们被排除或单独处理——得提前用 WHERE dept IS NOT NULL 过滤,而不是依赖窗口函数兜底。

















