不能只靠 sum() over() 算累计百分比,必须用 sum() over(partition by category) 或 count(*) over(partition by category) 先求分组总数作分母,再与带 order by 的累计分子相除,并用 nullif 防除零、100.0 保精度。

怎么用 SUM() OVER() 计算分组内累计百分比
直接结论:不能只靠 SUM() OVER() 算出“累计百分比”,必须配合 COUNT() OVER() 或 SUM() OVER() 先求出分组总数,再做除法。否则容易把全表总数当成分组总数,结果全错。
典型场景是按 category 分组后,统计每个 order_date 的订单数,并计算“截至该日期的累计订单占本 category 总订单的比例”。关键在于:分母必须是当前分组内的总和,不是全表总和。
- 错误写法:
SUM(cnt) OVER (PARTITION BY category ORDER BY order_date) / SUM(cnt) OVER ()—— 分母没PARTITION BY,用了全表总和 - 正确写法:分母也加
PARTITION BY category,且推荐用SUM() OVER (PARTITION BY category)(静态窗口,不带ORDER BY) - 注意
ORDER BY在累计窗口中决定“累计顺序”,漏写会导致累计逻辑失效(比如按时间倒序,累计值反而变小)
SUM OVER 和 COUNT OVER 在分母选择上的区别
如果目标是“累计记录数占比”,用 COUNT(*) OVER (PARTITION BY category) 当分母更直观;如果是“累计金额占比”,就得用 SUM(amount) OVER (PARTITION BY category)。二者不能混用。
- 用
COUNT当分母:适用于统计行数比例,比如“某品类前 N 天订单量占该品类全部订单的百分之几” - 用
SUM当分母:适用于度量值比例,比如“某品类截至当日的销售额累计占该品类总销售额比例” - 若字段含
NULL,COUNT(*)会统计所有行,COUNT(col)会忽略NULL——分母选哪个,取决于业务定义的“总数”是否包含空值
常见报错和精度问题:除零、浮点截断、隐式类型转换
直接写 100.0 * cum_sum / total_sum 很容易遇到除零错误或整数除法截断(尤其在 PostgreSQL 或 SQL Server 中)。MySQL 默认可能返回 NULL 而非报错,但结果为 0。
- 防除零:用
NULLIF(total_sum, 0)替换分母,避免division by zero - 保精度:显式写成
100.0 * cum_sum / NULLIF(total_sum, 0),确保至少一个操作数是浮点,防止整除丢小数位 - 某些数据库(如 Hive)对窗口函数嵌套敏感,不要在
OVER子句里再套聚合函数,先用 CTE 或子查询算好分母
一个可直接跑的 PostgreSQL 示例
WITH base AS (
SELECT
category,
order_date,
COUNT(*) AS cnt
FROM orders
GROUP BY category, order_date
),
with_cum AS (
SELECT
category,
order_date,
cnt,
SUM(cnt) OVER (PARTITION BY category ORDER BY order_date) AS cum_cnt,
SUM(cnt) OVER (PARTITION BY category) AS total_cnt
FROM base
)
SELECT
category,
order_date,
ROUND(100.0 * cum_cnt / NULLIF(total_cnt, 0), 2) AS cum_pct
FROM with_cum
ORDER BY category, order_date;
这个结构清晰分离了“分组聚合 → 累计计算 → 百分比转换”三步,便于调试。真正容易被忽略的是:分母 total_cnt 必须来自不含 ORDER BY 的窗口,否则它会随行变化,失去“分组总数”的意义。










