核心是分子用SUM(col) OVER (ORDER BY x)算累积和,分母用SUM(col) OVER ()得固定总和,需防NULL、除零及整数截断。

用 SUM() OVER() 算累计占比,核心是两步:先算累计和,再除以总和
直接写 SUM(col) OVER (ORDER BY x) 只能得到累计值,不是百分比。必须显式除以全量总和——而这个总和不能写成常量,得用窗口函数算出来,否则分组后会出错。
常见错误是写成 SUM(col) OVER (ORDER BY x) / SUM(col),这会触发 MySQL 8.0+ 的“混合窗口与非窗口聚合”报错,或在 PostgreSQL 中隐式转为 GROUP BY 行为,结果错乱。
- 正确做法:用
SUM(col) OVER ()获取全局总和(不带ORDER BY和PARTITION BY) - 累计和用
SUM(col) OVER (ORDER BY order_col),注意ORDER BY列必须有确定顺序(比如时间、ID),否则累计结果不稳定 - 如果数据有重复
order_col值,MySQL 和 PostgreSQL 默认按“范围窗口(RANGE)”处理,可能导致同一排序值的多行共享相同累计值;需要精确到行,加ROWS UNBOUNDED PRECEDING
MySQL 8.0+ 和 PostgreSQL 写法一致,但 SQLite 不支持窗口函数
SQLite 3.25+ 虽然支持 OVER,但直到 3.42 才完整支持 SUM() OVER(),旧版本会报 no such function: sum 或直接语法错误。别在 SQLite 上硬套。
PostgreSQL 和 MySQL 8.0+ 语法完全兼容,示例:
SELECT
category,
amount,
SUM(amount) OVER (ORDER BY category ROWS UNBOUNDED PRECEDING) AS cumsum,
ROUND(
SUM(amount) OVER (ORDER BY category ROWS UNBOUNDED PRECEDING) * 100.0 /
SUM(amount) OVER (), 2
) AS cum_pct
FROM sales;注意 * 100.0:用 100 整数会导致整除截断(尤其在 PostgreSQL 中),结果全为 0。
GROUP BY 后要累计占比?必须先聚合再开窗
如果原始表是明细数据(如每笔订单),但你想按 product_id 汇总后再算品类累计占比,不能直接在明细上 OVER (PARTITION BY product_id) —— 那算的是每个产品内部的累计,不是品类维度的分布。
- 先用子查询或 CTE 汇总:
SELECT product_id, SUM(sales) AS total_sales FROM orders GROUP BY product_id - 再对汇总结果开窗:
SUM(total_sales) OVER (ORDER BY total_sales DESC) - 若需按销售排名倒序累计(如“前 N 个产品占多少”),
ORDER BY total_sales DESC是关键,顺序反了占比曲线就颠倒
NULL 值会让累计中断,必须提前处理
SUM() 窗口函数默认跳过 NULL,看起来没问题,但一旦 ORDER BY 列含 NULL,不同数据库行为不一:MySQL 把 NULL 排最前,PostgreSQL 排最后,导致累计起点错位。
更隐蔽的问题是,如果某行 amount 为 NULL,它不会参与累计,但后续行的累计值会“跳过”它——表面上数字连续,实际漏掉了该记录的权重。
- 稳妥做法:用
COALESCE(amount, 0)显式转 0,确保每行都计入 - 如果业务上
NULL代表“未发生”,那转 0 合理;如果代表“未知”,就得先定义语义,不能无脑填充 - 检查
ORDER BY列是否含NULL:加WHERE order_col IS NOT NULL或用COALESCE(order_col, 'ZZZZ')统一归位
累计占比看着简单,真正上线时最容易栽在 NULL 处理、排序列歧义、以及没意识到 SUM() OVER () 和普通 SUM() 在上下文中的求值时机差异。

















