PERCENT_RANK()用于计算分组内按指定字段排序后的百分位位置,公式为(当前行排名-1)/(组内总行数-1),结果范围0到1,首行恒为0、末行恒为1,需配合PARTITION BY和ORDER BY使用,单行分组返回NULL。

用 PERCENT_RANK() 直接算分组内累计分布频率
多数人想实现“每个分组内,按某字段排序后,当前行占该组的百分位位置”,PERCENT_RANK() 就是为此设计的窗口函数。它返回的是(当前行排名 - 1) / (组内总行数 - 1),结果范围是 0 到 1,首行恒为 0,末行恒为 1。
关键点在于必须配合 PARTITION BY 和 ORDER BY:
-
PARTITION BY指定分组字段,比如category或dept_id -
ORDER BY决定组内排序依据,比如salary或score - 如果组内只有一行,
PERCENT_RANK()返回NULL(因为分母为 0),需提前过滤或用COALESCE处理
示例:
SELECT name, dept_id, salary,<br> PERCENT_RANK() OVER (PARTITION BY dept_id ORDER BY salary) AS pct_rank<br>FROM employees;
CUME_DIST() 和 PERCENT_RANK() 的区别在哪
两者都算累计分布,但定义不同:CUME_DIST() 返回“小于等于当前值的行数 / 组内总行数”,结果范围是 1/N 到 1;而 PERCENT_RANK() 是基于排名的线性插值,首尾固定为 0 和 1。
典型差异场景:
- 当存在重复值时,
CUME_DIST()对所有相同值返回相同结果(比如三个并列第2名,都得到 3/10 = 0.3) -
PERCENT_RANK()对相同值返回相同排名,但后续值的百分位会跳变(并列第2名 → 排名都是 2,计算时统一用 2) - 如果业务要求“严格按值大小累积占比”,选
CUME_DIST();若要“按顺序位置做归一化”,选PERCENT_RANK()
MySQL 8.0+ 才支持,旧版本得绕开
MySQL 5.7 及更早版本不支持窗口函数,强行写 PERCENT_RANK() 会报错 ERROR 1064: You have an error in your SQL syntax。
替代方案只能用自连接或变量模拟,但有严重限制:
- 变量方式(
@rank := @rank + 1)在 MySQL 8.0+ 已不推荐,且无法可靠保证执行顺序 - 自连接方式性能差,O(n²),数据量过千就明显卡顿
- 如果必须兼容旧版,建议把数据拉到应用层(Python/Java)排序后计算,比硬写 SQL 更稳
ORDER BY 里不能用表达式或别名
常见错误是写成 ORDER BY salary_desc(别名)或 ORDER BY ABS(salary)(表达式),多数数据库(PostgreSQL、SQL Server、MySQL 8.0+)会直接报错 ERROR: window function requires valid ORDER BY 或类似提示。
正确做法只有两种:
- 直接引用原始列名,如
salary、created_at - 先在子查询或 CTE 中算好表达式列,再在外层窗口函数中引用该列名
例如:
WITH ranked AS (<br> SELECT *, ABS(salary) AS abs_salary FROM employees<br>)<br>SELECT *, PERCENT_RANK() OVER (PARTITION BY dept_id ORDER BY abs_salary)<br>FROM ranked;
实际用的时候,最容易被忽略的是分组内单行导致的 NULL,以及旧版 MySQL 的语法断层——这两个点不提前踩坑,查半天也不知道为什么结果不对或直接报错。

















