cume_dist返回“≤当前值的行数占比”,范围(0,1]且最小值为1/n;percent_rank返回“(rank-1)/(总行数-1)”,范围[0,1]且首行为0;前者含自身、对重复值结果相同,后者基于排名、两端固定为0和1。

什么是 CUME_DIST,它和 PERCENT_RANK 有什么区别
CUME_DIST 返回当前行在分组内的「小于等于该行值的行数占比」,结果范围是 (0, 1],最小值一定是 >0(哪怕排第一,也是 1/N),而 PERCENT_RANK 的最小值是 0。关键区别在于:前者包含自身,后者不包含自身,且分母固定为总行数减 1。
比如 5 个用户成绩 [80, 85, 85, 90, 95],对两个 85 分用户:
- 第一个 85:小于等于 85 的有 3 行 →
CUME_DIST = 3/5 = 0.6 - 第二个 85:同样算进「≤85」,结果也是
0.6 - 而
PERCENT_RANK对这两个 85 都返回2/4 = 0.5(因为排名从 0 开始,最大值是 1)
CUME_DIST 必须配合 OVER 子句,且不能单独用在 WHERE 或 GROUP BY 中
它是个窗口函数,不是聚合函数,所以不能写成 SELECT CUME_DIST() FROM t GROUP BY subject 这类形式——会报错 ERROR: window function CUME_DIST requires an OVER clause。
正确写法必须带 OVER,且通常需要指定排序:
SELECT name, score, CUME_DIST() OVER (ORDER BY score) AS cum_pct FROM users;
常见误操作:
- 漏掉
ORDER BY→ 报错或结果无意义(默认按物理顺序,不可靠) - 写成
OVER (PARTITION BY dept ORDER BY score)却忘了业务上是否真要按部门分组计算百分比 - 试图在
HAVING或WHERE中直接引用cum_pct别名 → 不行,窗口函数在SELECT阶段才计算,WHERE阶段还不可见
处理并列成绩时,CUME_DIST 自动“跳过后续排名”,但百分比值不变
和 RANK() 类似,CUME_DIST 对相同值返回相同结果,且不影响后续值的分母(总行数不变)。例如 4 人成绩 [70, 80, 80, 90]:
- 70 →
CUME_DIST = 1/4 = 0.25 - 第一个 80 →
2/4 = 0.5 - 第二个 80 → 同样
2/4 = 0.5(不是 3/4) - 90 →
4/4 = 1.0
注意:这不是 bug,而是定义使然——它统计的是「值 ≤ 当前值的记录占比」,重复值自然共享同一占比。如果业务要求“每个用户独立占位”(比如用于奖学金名额筛选),就得换用 ROW_NUMBER() + 手动除法,或者加时间戳等辅助字段破重。
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 和旧版 MySQL 不行
如果你执行 SELECT CUME_DIST() OVER () 报错 FUNCTION does not exist 或提示语法错误,先查版本:
- MySQL:运行
SELECT VERSION();,确认 ≥ 8.0.2(窗口函数初支持) - PostgreSQL:≥ 8.4 就支持,但建议 ≥ 11 以获得稳定行为
- SQLite:至今(3.45)仍不支持
CUME_DIST,只能用子查询模拟(性能差,慎用)
替代方案(仅当必须兼容老环境时):
SELECT a.name, a.score, (SELECT COUNT(*) FROM users b WHERE b.score <p>但数据量稍大(>1 万行)就会明显变慢,因为每行都触发两次全表扫描。</p><p>真正容易被忽略的是:<code>CUME_DIST</code> 的分母永远是整个 <code>PARTITION</code> 内的总行数,不是去重后的值个数,也不是当前排名位置;只要没理解这点,算出来的“百分比”就可能和业务预期对不上。</p>










