PERCENT_RANK()返回0到1(不含1)的浮点数,表示当前行在分组内的相对位置,公式为(rank-1)/(total_rows_in_group-1),首行为0.0,末行为(n-1)/n。

PERCENT_RANK函数返回什么值
PERCENT_RANK() 不是四舍五入后的整数百分比,而是按「当前行在分组内的相对位置」计算的 0 到 1(不含 1)之间的浮点数。第一行永远是 0.0,最后一行是 (n-1)/n(n 为分组行数),不是 1.0。
它底层公式是:(rank - 1) / (total_rows_in_group - 1),其中 rank 是 RANK() 的结果(相同值并列,跳过后续名次)。
- 如果分组只有 1 行,
PERCENT_RANK()返回0.0(分母为 0,SQL 标准定义为 0) - 值相同时,
PERCENT_RANK()给出相同结果 —— 它基于RANK(),不是DENSE_RANK() - 不支持
ORDER BY中用表达式直接排序(如ORDER BY salary * 1.2),需提前算好列或用子查询
必须搭配 OVER() 使用,不能单独写
直接写 SELECT PERCENT_RANK(); 会报错:function PERCENT_RANK() must have an OVER clause。它没有参数,但必须配合 OVER 子句声明排序和分组逻辑。
- 最简用法:
PERCENT_RANK() OVER (ORDER BY score DESC)—— 全表按 score 降序排,算全局百分位 - 分组计算:
PERCENT_RANK() OVER (PARTITION BY dept_id ORDER BY salary)—— 每个部门内单独排名 -
PARTITION BY和ORDER BY缺一不可;只写PARTITION BY不排序会报错
和 CUME_DIST、NTILE 的关键区别
容易混淆的是:PERCENT_RANK() 和 CUME_DIST() 都返回 0–1 区间值,但算法不同:前者基于 rank,后者基于「≤当前值的行数 / 总行数」;NTILE(100) 是把数据等分成 100 桶,不是真正百分位。
-
CUME_DIST()最后一行一定是1.0;PERCENT_RANK()最后一行永远 - 当有大量重复值时,
PERCENT_RANK()可能出现大片相同结果(比如前 10 名都得0.0),而CUME_DIST()更平滑 -
NTILE(100)强制分 100 组,但每组行数可能不均(尤其总行数不能被 100 整除),且不反映真实分布密度
实际查百分位数(如 P95)要绕一下
PERCENT_RANK() 本身不直接返回某百分位对应的值,它只是给每行打一个“位置分数”。想查工资的 P95(即 95% 的人薪资 ≤ 该值),得用 PERCENT_RANK() + 过滤 + 取最小值:
SELECT MIN(salary) FROM ( SELECT salary, PERCENT_RANK() OVER (ORDER BY salary) AS pr FROM employees ) t WHERE pr >= 0.95;
注意这不是标准定义的分位数算法(比如不插值),实际业务中更推荐用 PERCENTILE_CONT(0.95) OVER()(PostgreSQL/SQL Server 支持),但 PERCENT_RANK() 在 MySQL 8.0+ 或没原生分位函数时,是唯一能逼近的窗口函数。
真正麻烦的是:当数据量小或分布稀疏时,PERCENT_RANK() 算出的 “≥0.95” 行可能根本不存在 —— 这时候得 fallback 到最近的可用值,或者改用插值逻辑,不能硬取 MIN()。

















