用窗口函数实现分组内累计求和需配合partition by与order by,缺一不可;累计占比=当前行累计和÷本组总和,分母须用sum() over(partition by category)获取;mysql 8.0+支持,旧版需自连接或子查询替代。

用窗口函数实现分组内累计求和
直接在 GROUP BY 后接 SUM() 只能得到每组总和,没法逐行累加。必须用窗口函数,且关键在于把 PARTITION BY 和 ORDER BY 配合好——前者限定“在哪个组内算”,后者决定“按什么顺序累加”。
常见错误是漏掉 ORDER BY,导致 SUM() OVER (PARTITION BY ...) 算出的是整组静态和,不是累计值;或者把排序字段选成无序的(比如用 id 但实际业务要按时间),结果逻辑错乱。
-
SUM(amount) OVER (PARTITION BY category ORDER BY create_time):按类别分组,按创建时间递增累计 - 若需从最新到最旧累计,改用
ORDER BY create_time DESC,但注意这会影响后续占比计算的分母取值 - 避免对非数值字段(如
name)做ORDER BY,容易因重复值引发不确定排序
计算分组内累计占比时分母怎么取
累计占比 = 当前行累计和 ÷ 该组总和。难点在于:不能用 AVG() 或 COUNT(),必须精准拿到每组的总和作为分母。最稳的方式是嵌套一层子查询或 CTE,先算出每组总和,再关联进来;或者直接用窗口函数二次聚合:SUM(amount) OVER (PARTITION BY category) 就是该组总和,它不依赖 ORDER BY,所以可安全用作分母。
- 错误写法:
SUM(amount) / SUM(SUM(amount)) OVER ()—— 这会跨组求和,分母变成全表总和 - 正确写法:
SUM(amount) OVER (PARTITION BY category ORDER BY create_time) * 1.0 / SUM(amount) OVER (PARTITION BY category) - 乘
1.0是防止整数除法截断(尤其在 PostgreSQL、SQL Server 中)
MySQL 8.0+ 与旧版兼容性差异
MySQL 在 8.0 前不支持窗口函数,强行用会报错 ERROR 1064。如果必须兼容低版本,只能用自连接或变量模拟,但极难维护且在多并发下不可靠。强烈建议升级或换用 CTE + 子查询组合替代。
- MySQL 8.0+:直接用
SUM() OVER,语法与其他主流数据库(PostgreSQL、SQL Server、Oracle)一致 - MySQL 5.7 及更早:可用
(SELECT SUM(t2.amount) FROM table t2 WHERE t2.category = t1.category AND t2.create_time 模拟累计和,但性能差,数据量过万就明显变慢 - SQLite 3.25+ 支持窗口函数,但不支持
FILTER子句,别试图用它做条件累计
ORDER BY 字段含 NULL 或重复值时的行为
当 ORDER BY 字段有 NULL,不同数据库处理方式不同:PostgreSQL 默认把 NULL 排最后,MySQL 8.0 默认排最前。更麻烦的是重复值——比如多个记录 create_time 完全相同,窗口函数会把它们视为“同一位置”,累计和会在该位置一次性加上所有匹配行的值,而不是逐行递增。
- 解决重复时间问题:在
ORDER BY后追加一个唯一字段,如ORDER BY create_time, id - 显式控制
NULL位置:用ORDER BY create_time ASC NULLS LAST(PostgreSQL、Oracle 支持),MySQL 不支持该语法,需提前用COALESCE(create_time, '9999-12-31')替换 - 累计占比结果可能因排序不确定性而波动,务必在业务层校验边界值(首行应为最小占比,末行为 100%)
窗口函数本身不难,真正卡住人的往往是分组、排序、NULL 处理三者叠加后的隐式行为。跑通一条语句后,建议用小样本手工验算两行,比看执行计划更快定位偏差来源。











