排名占比需用SUM() OVER(ORDER BY ...)计算累计和再除以SUM() OVER()总和,ORDER BY不可省略,NULL需COALESCE处理,精度与末位修正须显式控制。

用 SUM() OVER() 算排名占比,本质是“先算总数、再算累计、最后除一下”
排名占比不是简单排序后标个序号,而是每个值占整体的累计比例(比如销售额前3名占总销售额多少)。直接用 ROW_NUMBER() 或 RANK() 拿不到占比,必须靠窗口函数动态聚合。核心就一条:用 SUM() OVER(ORDER BY ...) 做升序累计和,再除以总和。
SUM() OVER(ORDER BY ...) 的 ORDER BY 不能漏,否则累计失效
没 ORDER BY 的 SUM() OVER() 默认是整表求和,和普通 SUM() 一样,起不到“按顺序累加”的作用。真正要算排名占比,必须明确排序依据(比如按 sales 降序),且窗口帧默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW —— 这个默认行为刚好符合累计需求,不用显式写。
- 错误写法:
SUM(sales) OVER()→ 所有行都显示总销售额,无法体现“排名中”的占比 - 正确写法:
SUM(sales) OVER(ORDER BY sales DESC)→ 每行显示从最高到当前值的累计销售额 - 如果排序字段有重复(如多人同分),
ORDER BY后建议加唯一键兜底,比如ORDER BY sales DESC, id ASC,避免窗口计算顺序不确定
占比 = 累计和 ÷ 总和,总和必须用 SUM() OVER() 而非子查询
有人习惯先用子查询算出总和再 JOIN,但这样多一次扫描,性能差,还容易出错。窗口函数完全可以在同一层完成:用一个不带 ORDER BY 的 SUM() OVER() 拿总和,和带 ORDER BY 的累计和组合使用。
SELECT name, sales, SUM(sales) OVER (ORDER BY sales DESC) AS cum_sales, SUM(sales) OVER () AS total_sales, ROUND(SUM(sales) OVER (ORDER BY sales DESC) * 1.0 / SUM(sales) OVER (), 4) AS cum_pct FROM sales_table;
-
SUM(sales) OVER ()是整表总和,每行值相同 - 乘
1.0是防止整数除法截断(尤其在 PostgreSQL/SQL Server 中) - MySQL 8.0+ 和 PostgreSQL 支持;SQLite 不支持窗口函数,别硬套
百分比精度和 NULL 处理是上线前最容易翻车的地方
业务报表里常要求保留两位小数,但直接 ROUND(..., 2) 可能导致累计占比加起来不等于 100%。更麻烦的是,只要 sales 字段有 NULL,SUM() OVER() 默认会跳过它,但累计窗口可能因排序把 NULL 排到开头或中间,造成逻辑断裂。
- 统一处理
NULL:COALESCE(sales, 0)再参与计算,避免累计中断 - 若需严格满足“最后一行占比=1”,建议最后用
CASE WHEN ROW_NUMBER() = COUNT(*) OVER() THEN 1.0 ELSE cum_pct END修正末位 - 别依赖客户端四舍五入——数据库层定好精度,否则前端加总对不上
窗口函数看着简洁,但排序逻辑、NULL 行为、类型隐式转换这三块,查不出错,一上线就偏移。

















