不能只用sum()窗口函数,必须先用sum() over(partition by group_id)得组总和,再用sum() over(partition by group_id order by time)得累计值,二者相除才是组内累计占比。

窗口函数里 SUM() 怎么算组内累计占比
直接说结论:不能只用 SUM() 窗口函数,必须配合 SUM() OVER() 先算组内总和,再做除法。否则得到的是“累计和”,不是“累计占比”。
常见错误是写成 SUM(value) OVER (PARTITION BY group_id ORDER BY time) 就以为得到了贡献度——其实这只是按顺序加起来的累计值,离“占该组多少比例”还差一步归一化。
- 先用
SUM(value) OVER (PARTITION BY group_id)拿到每组总和(固定值,不随ORDER BY变) - 再用
SUM(value) OVER (PARTITION BY group_id ORDER BY time)拿到逐行累计值 - 两者相除,才是当前行在组内的累计贡献度(即累计权重)
MySQL 8.0+ 和 PostgreSQL 的写法差异
核心逻辑一致,但 MySQL 对浮点精度更敏感,PostgreSQL 默认保留小数位更多。如果结果看起来是 0 或整数,大概率是整数除法导致的截断。
- MySQL 必须显式转类型,比如写成
CAST(SUM(value) OVER (...) AS DECIMAL(10,4)) / SUM(value) OVER (PARTITION BY group_id) - PostgreSQL 可以直接写
SUM(value) OVER (...)::DECIMAL / SUM(value) OVER (PARTITION BY group_id) - SQLite 不支持窗口函数(除非是 3.25+ 且编译时启用了),别试
ORDER BY 缺失或错序导致累计值乱掉
累计贡献度一定依赖明确的排序逻辑。没写 ORDER BY,SUM() OVER (PARTITION BY ...) 就是组内总和;写了但字段不稳定(比如用 name 排序,存在重复),会导致同一组内每次执行结果不一致。
- 排序字段必须能唯一确定累计顺序,推荐组合主键或带时间戳的字段,例如
ORDER BY created_at, id - 如果业务上允许并列(比如同一天多个订单),需提前用
ROW_NUMBER()或RANK()打散重复 - 避免用
ORDER BY RAND()—— 累计值每次都不一样,毫无意义
性能隐患:大表 + 多层窗口嵌套
一个查询里嵌套两层 SUM() OVER(一层总和、一层累计)本身不慢,但如果再叠加 PARTITION BY 字段区分度过低(比如只有 3 个分组,但千万级数据),就会出现单个分区过大,内存吃紧甚至 OOM。
- 检查
PARTITION BY字段的基数,用SELECT COUNT(DISTINCT group_id) FROM table快速评估 - 如果分组太少,考虑是否真需要“按组累计”,还是应该先聚合再计算
- 生产环境务必加
EXPLAIN ANALYZE(PostgreSQL)或EXPLAIN FORMAT=TREE(MySQL 8.0+)看实际执行计划
真正容易被忽略的是:累计贡献度本质是有序依赖,一旦底层数据变更(比如补录历史记录),所有后续行的累计值都会漂移——它不是静态快照,得想清楚这个动态性是否符合业务预期。










