正确计算累计百分比需用SUM() OVER(ORDER BY)÷SUM() OVER(),分母必须无ORDER BY以确保固定值,且须用NULLIF防除零、100.0保精度。

用 SUM() OVER() 计算累积和再除以总和,是最直接的写法
累积百分比 = 当前行及之前所有行的累计值 ÷ 全表(或窗口内)总和。关键不是“先算累计再除”,而是要确保分母是固定值(比如全组总和),否则用 SUM() OVER() 直接除会出错——因为两个窗口函数默认对齐,分母也会跟着累积。
正确做法是:分子用 SUM(value) OVER (ORDER BY x) 得累积和;分母用 SUM(value) OVER ()(无 ORDER BY)得全局总和。
- 如果按分组计算(如每个品类单独算),分母必须加
PARTITION BY category保持一致性 -
ORDER BY必须明确,否则SUM() OVER(ORDER BY ...)结果不可靠(SQL 标准不保证无序时的累积顺序) - 注意 NULL:原始
value为 NULL 时,SUM会跳过,但若整列都 NULL,分母为 0,会导致除零错误
避免除零和小数精度问题的实操处理
直接写 SUM(val) OVER (ORDER BY id) * 100.0 / SUM(val) OVER () 看似简洁,但上线后容易报错或返回 NULL。
- 分母为 0 时,多数数据库(PostgreSQL、SQL Server)返回
NULL或报错;MySQL 默认返回NULL,但开启严格模式会报错 - 用
COALESCE(SUM(val) OVER (), 0)不能解决除零,得用NULLIF:分母写成NULLIF(SUM(val) OVER (), 0) - 乘
100.0(而非100)强制转浮点,避免整数截断(例如 SQLite、旧版 SQL Server 中5/10得0)
示例(兼容多数数据库):
SELECT
id,
val,
ROUND(
SUM(val) OVER (ORDER BY id) * 100.0 /
NULLIF(SUM(val) OVER (), 0),
2
) AS cum_pct
FROM t;
不同数据库对 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 的隐式处理
SUM(val) OVER (ORDER BY id) 在绝大多数引擎中等价于 SUM(val) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),但显式写出更安全。
- PostgreSQL 和 BigQuery 严格遵循标准,省略
ROWS也默认该范围 - Oracle 12c+ 同样支持简写;但 Oracle 11g 及更早版本要求显式声明
ROWS范围,否则报错 - SQL Server 2012+ 支持简写,但若
ORDER BY字段有重复值,不加ROWS可能导致“逻辑等值组”被一起累积(即相同排序值的行共享同一累积值),而显式ROWS能规避歧义
当需要按多字段排序或跳过某些行时,WHERE 和窗口函数的执行顺序很关键
窗口函数在 WHERE 之后、ORDER BY 之前执行。所以过滤必须在窗口计算前完成,否则累积基数会包含本该排除的行。
- 错误写法:
SELECT ..., SUM(val) OVER (ORDER BY id) FROM t WHERE val > 0—— 表面看没问题,但若WHERE条件依赖计算列(如上一行的值),就得用 CTE 或子查询先过滤 - 正确方式:用 CTE 预先筛选,再在其上套窗口函数,确保分母和分子都基于同一数据集
- 若需“跳过空值行但保留位置”,不能靠
WHERE,得用FILTER (WHERE val IS NOT NULL)(PostgreSQL)或条件聚合模拟(其他库)
真正容易被忽略的是:累积百分比的结果依赖于你最终呈现的行集。只要 WHERE、JOIN 或 GROUP BY 改变了基础行数,分母就必须同步重算——没有“一次写好到处复用”的通用表达式。










