CUME_DIST返回“≤当前值的行数占比”,结果范围(0,1]且重复值结果相同;PERCENT_RANK基于RANK计算,公式为(rank-1)/(总行数-1),首行为0、末行为1,重复值仅首行参与排名。

什么是 CUME_DIST,它和 PERCENT_RANK 有什么区别
CUME_DIST 返回的是「小于等于当前行值的行数占总行数的比例」,结果范围是 (0, 1],且相同值一定得到相同结果。它不依赖排序位置编号,而是基于实际值的分布密度计算。
和 PERCENT_RANK 最关键的区别在于:当有重复值时,CUME_DIST 把所有相等的行视为一个整体向上累计;而 PERCENT_RANK 按排名位置算,相同值会共享同一个排名但分母仍是总行数减一,导致结果可能为 0(首行)且无法覆盖全部区间。
常见错误现象:PERCENT_RANK 在单行数据或全同值时返回 0 或 NaN,而 CUME_DIST 始终返回有效比例(如单行就是 1.0)。
基本语法与窗口定义必须写全,否则报错
CUME_DIST() 是纯窗口函数,括号里永远不传参数——这点容易被误写成 CUME_DIST(col) 或 CUME_DIST(*),会直接报错 ERROR: window function CUME_DIST takes no arguments。
必须配合 OVER 子句,且至少包含 ORDER BY;PARTITION BY 可选但常用:
SELECT score, CUME_DIST() OVER (ORDER BY score) AS cume_dist FROM exam_scores;
使用场景:做成绩分布分析、销售金额分层、指标达标率统计等需要「某值及以下占比」的业务口径。
注意点:
-
ORDER BY必须明确,不能用表达式别名(如写ORDER BY s而非原始列名或表达式) - 升序(默认)下,最小值对应最小累积比例;降序需显式写
ORDER BY score DESC,此时最大值反而得最小比例 - NULL 默认排在最前(ASC)或最后(DESC),会影响累积起点,必要时用
NULLS LAST控制
处理重复值时的结果逻辑要心里有数
假设有数据 [85, 85, 92, 98],共 4 行:
两个 85 并列最小,那么「≤85」的行数是 2 → CUME_DIST = 2/4 = 0.5;≤92 的行数是 3 → 0.75;≤98 是全部 4 行 → 1.0。
也就是说,重复值不会被“摊薄”,而是整块计入分子。这和业务中常说的「达到 85 分及以上的人占 50%」完全一致。
性能影响:只要 ORDER BY 列有索引,CUME_DIST 计算是 O(n) 窗口扫描,无额外排序开销;但若在大表上对无索引列开窗,可能触发临时排序,拖慢查询。
兼容性提醒:CUME_DIST 在 PostgreSQL 8.4+、SQL Server 2005+、Oracle 9i+、Snowflake、BigQuery 中都支持;MySQL 直到 8.0.2 才支持窗口函数,旧版需用变量模拟(不推荐,易出错)。
和 NTILE 混用时要注意语义冲突
有人想用 CUME_DIST 辅助分桶,比如「取累积分布前 30% 的记录」,写成 WHERE CUME_DIST() OVER (...) —— 这逻辑看似合理,但实际可能返回 0 行或远超预期行数。
原因:窗口函数不能直接在 WHERE 中使用(执行顺序在 WHERE 之后),必须套子查询或 CTE;更关键的是,CUME_DIST 是连续比例,而分位点本身未必恰好落在某个真实值上。
正确做法:
- 先用子查询算出
cume_dist列 - 再在外层过滤,例如:
SELECT * FROM ( SELECT *, CUME_DIST() OVER (ORDER BY amount) AS cd FROM sales ) t WHERE cd <= 0.3;
- 如果目标是固定分组数量(如四分位),优先用
NTILE(4);CUME_DIST更适合回答「某个具体数值处于什么百分位段」
最容易被忽略的一点:当你把 CUME_DIST 结果转成百分比显示(如 ×100 后加 %),记得保留足够小数位——浮点误差可能导致 0.9999999999 显示为 99.99999999%,而不是预期的 100%。业务报表中建议用 ROUND(cd * 100, 2) 控制精度。

















