直接用 sum() over() 算不出正确累计百分比,因为它只做累计和而不做除法归一化;必须显式除以全局总和(如通过 cte 或子查询获取),并确保 order by 明确、处理 null 和整数除法问题。

为什么直接用 SUM() OVER() 算不出正确累计百分比?
因为 SUM() OVER() 只负责累加,不自动做除法归一化。你得到的是累计和,不是占比——比如总销售额 1000 万,某月累计 300 万,得先除以 1000 万才能是 30%。漏掉这步,结果全是绝对值,毫无百分比意义。
常见错误写法:SUM(sales) OVER (ORDER BY month) → 输出 50、120、200… 而不是 0.05、0.12、0.20。
- 必须显式除以全局总和(不是窗口内总和)
- 全局总和不能用
SUM() OVER()不带PARTITION BY,否则可能因分组逻辑错乱 - 注意 NULL 值:
SUM(sales)会自动忽略 NULL,但若全行为 NULL,结果为 NULL,后续除法失败
怎么写才对:用子查询或 CTE 先算总和
最稳的方式是把分母“总销售额”单独拎出来,避免窗口函数嵌套导致语义混淆。CTE 更易读,子查询更兼容老版本 PostgreSQL/MySQL 8.0+。
示例(PostgreSQL / SQL Server):
WITH total AS ( SELECT SUM(sales) AS total_sales FROM orders ) SELECT month, sales, SUM(sales) OVER (ORDER BY month) AS cumsum, ROUND(SUM(sales) OVER (ORDER BY month) * 100.0 / t.total_sales, 2) AS cum_pct FROM orders, total t;
-
* 100.0强制转浮点,防止整数除法截断(如 SQLite、旧版 MySQL) -
ROUND(..., 2)控制小数位,避免出现 12.349999999 - 别用
AVG() OVER()或COUNT() OVER()替代分母——它们统计的是行数或均值,不是总和
ORDER BY 必须明确,否则累计无意义
SUM() OVER() 没有 ORDER BY 就是“所有行一起加”,等价于普通 SUM(),得不到逐行累计效果。业务上你要按时间、ID 或其他逻辑顺序累加,这个顺序必须显式声明。
- 错误:
SUM(sales) OVER ()→ 每行都显示总和 - 正确:
SUM(sales) OVER (ORDER BY order_date)或OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 如果排序字段有重复值(比如多笔同天订单),建议加唯一键辅助:
ORDER BY order_date, order_id,避免窗口边界模糊
MySQL 8.0+ 和 SQLite 的兼容性注意点
MySQL 8.0+ 支持标准窗口函数,但默认开启严格模式时,ROUND(0.123 * 100, 2) 可能返回 12.30 而非 12.3;SQLite 则要求 3.25+ 才支持窗口函数,且不支持 ROWS BETWEEN 的简写语法。
- MySQL 中若遇
ERROR 3577: Window 'w' does not exist,说明你写了WINDOW w AS (...)但引用时拼错名 - SQLite 中必须写全:
SUM(sales) OVER (ORDER BY month ROWS UNBOUNDED PRECEDING),省略ROWS关键字会报错 - 所有引擎中,
NULL在ORDER BY中默认排最前,可能打乱业务预期,必要时加NULLS LAST(PostgreSQL/SQL Server 支持,MySQL 不支持)
累计百分比本质是两步:先累计,再归一。少走任何一步,结果就不是百分比。最容易被忽略的是分母来源——它必须稳定、全局、可复现,不能依赖窗口定义本身。











