percent_rank返回0到1之间的值,因其计算公式为(当前行rank−1)/(窗口总行数−1),首行恒为0.0、末行在窗口行数≥2时恒为1.0,反映相对排序位置而非实际占比。

PERCENT_RANK 为什么返回 0 和 1 之间的值
PERCENT_RANK 不是“四舍五入后的百分比”,它本质是基于**秩次位置**的归一化计算:(当前行的 RANK - 1) / (总行数 - 1)。所以首行永远是 0,末行在无并列时才是 1;一旦有重复值导致 RANK 跳跃(比如两个并列第 2 名,下一个就是第 4 名),中间就会出现非均匀分布的值。
常见误解是把它当“占比”用——比如误以为 PERCENT_RANK 为 0.7 表示“超过 70% 的数据”,其实它只反映相对排序位置,不保证累积分布意义。
必须配合 WINDOW 子句使用,不能单独写在 SELECT 列表里
直接写 SELECT PERCENT_RANK() FROM t 会报错,因为该函数没有定义排序基准和窗口范围。必须显式声明 OVER 子句:
SELECT score, PERCENT_RANK() OVER (ORDER BY score ASC) AS pct_rank FROM exam_results;
关键点:
-
ORDER BY是强制的,且决定排名方向(ASC从低到高,DESC从高到低) - 不加
PARTITION BY就是全表一个窗口;若需按班级、年份分组分别排名,得写OVER (PARTITION BY class ORDER BY score DESC) - 不能在
WHERE或HAVING中直接引用PERCENT_RANK()别名,要嵌套子查询或 CTE
和 RANK()、DENSE_RANK() 的结果差异很实际
三者都处理并列,但逻辑不同,直接影响 PERCENT_RANK 的分母和分子:
-
RANK():并列则占位(1,2,2,4),PERCENT_RANK分母固定为COUNT(*)-1,但分子用的是这个跳变后的秩 -
DENSE_RANK():并列不占位(1,2,2,3),但PERCENT_RANK**不接受DENSE_RANK输出作为输入**——它只认自己的排序逻辑 - 真正影响
PERCENT_RANK值的,只有你ORDER BY中字段的实际顺序和重复情况,跟是否显式调用RANK()无关
举例:数据 [85, 90, 90, 95],按 score ASC 计算:
– 第 1 行(85):(1-1)/(4-1) = 0.0
– 第 2、3 行(90):(2-1)/(4-1) ≈ 0.333(注意:两行同值,RANK 都是 2,所以 PERCENT_RANK 相同)
– 第 4 行(95):(4-1)/(4-1) = 1.0
NULL 值默认排在最前面(ASC)或最后面(DESC),会影响排名连续性
如果排序字段含 NULL,多数数据库(PostgreSQL、SQL Server、Oracle)默认把 NULL 当作最小值,ORDER BY score ASC 会让 NULL 排第一,导致 PERCENT_RANK 首值为 0,但这一行未必是有效数据。
稳妥做法:
- 显式控制
NULL位置:ORDER BY score ASC NULLS LAST(PostgreSQL/Oracle 支持),或ORDER BY (score IS NULL), score ASC(通用兼容写法) - 业务上明确是否应排除
NULL:WHERE score IS NOT NULL再计算,避免用无效数据拉偏整体分布 - MySQL 8.0+ 支持
NULLS FIRST/LAST,但旧版本只能靠表达式模拟
真实场景中,漏掉 NULL 处理常导致报表里出现“0% 排名却对应空分”这类难解释的结果。










