percent_rank的计算逻辑是(rank-1)/(total_rows-1),其中rank采用rank()规则(并列名次相同且跳过后续),首行为0.0、末行为1.0,中间值受并列影响而压缩;它不基于dense_rank,也不等价于cume_dist或ntile。

PERCENT_RANK的计算逻辑是什么
PERCENT_RANK 不是简单地把行号除以总行数,而是按公式 (rank - 1) / (total_rows - 1) 计算。其中 rank 是当前行在分组内的**排名(用RANK()规则,即并列名次相同且跳过后续)**,total_rows 是该窗口内总行数。
关键点:首行永远是 0.0(因为 rank=1 → (1-1)/(n-1)=0),末行永远是 1.0(无论是否并列,只要它是最后一行,rank 取最大值,分子 = 分母)。中间值取决于并列情况——并列越多,相邻 PERCENT_RANK 值越“挤”。
和RANK、DENSE_RANK一起用时结果差异明显
同一数据集下三者输出不同,尤其在有重复值时:RANK() 跳过后续名次,DENSE_RANK() 不跳,而 PERCENT_RANK() 基于 RANK() 的结果算分母,所以它会“放大”并列带来的间隔不均。
- 假设窗口内 5 行,值为 [10,20,20,30,40]:
→RANK()得 [1,2,2,4,5] →PERCENT_RANK()为 [0.0, 0.25, 0.25, 0.75, 1.0] - 若用
DENSE_RANK()算出 [1,2,2,3,4],但PERCENT_RANK()**不会用它**——它只认RANK()逻辑 - 注意:没有
ORDER BY子句时,PERCENT_RANK()报错;空窗口(0行)不合法,数据库通常直接拒绝
常见错误:误以为它等价于NTILE或CUME_DIST
PERCENT_RANK() 和 CUME_DIST() 都返回 0~1 区间值,但含义不同:CUME_DIST 是“≤当前值的行数 / 总行数”,而 PERCENT_RANK 是“严格<当前值的行数占比”。两者在无重复值时结果接近,但只要有重复,CUME_DIST 会比 PERCENT_RANK 更大(比如上面例子中,第二个 20 的 CUME_DIST 是 0.4,PERCENT_RANK 是 0.25)。
NTILE(4) 是强行四等分桶,和百分位无关;有人想用 PERCENT_RANK 找 top 10%,实际应写 PERCENT_RANK() OVER (...) ,不是 <code>(那是 bottom 10%)。
实际写法与性能注意点
必须搭配 OVER 子句,且 ORDER BY 不可省略;PARTITION BY 可选,但漏掉会导致全表排序,大数据量时很慢。
示例:
SELECT score, PERCENT_RANK() OVER (ORDER BY score) AS pct_rank, CUME_DIST() OVER (ORDER BY score) AS cume_dist FROM exam_results;
性能上,PERCENT_RANK 需要完整扫描窗口内所有行来确定 rank 和 total_rows,无法流式计算;如果只关心 top N 或分位点(如 median),用 PERCENTILE_CONT 可能更高效——PERCENT_RANK 是“每行一个排名”,不是“查某个分位数值”。
真正容易被忽略的是:它对 NULL 的处理依赖数据库,默认多数按 ORDER BY 中的 NULLS FIRST/LAST 规则排位,但有些引擎(如旧版 MySQL)不支持该子句,NULL 会被统一排最前或最后,直接影响分母和分子——查之前先 SELECT COUNT(*) 确认实际参与计算的非 NULL 行数。










