一、PERCENT_RANK() 是什么?(一句话解释)
"百分比排名:你在群体中的相对位置(0% - 100%)"
就像考试后老师说:"你的成绩超过了全班 80% 的同学",这就是 PERCENT_RANK。
公式:(当前排名 - 1) / (总行数 - 1)
张三: 第 1 名 → (1-1)/(10-1) = 0/9 = 0.00 (0%)
李四: 第 2 名 → (2-1)/(10-1) = 1/9 = 0.11 (11%)
王五: 第 5 名 → (5-1)/(10-1) = 4/9 = 0.44 (44%)
赵六: 第 10 名 → (10-1)/(10-1) = 9/9 = 1.00 (100%)
特点:
- 最小值永远是 0(第 1 名)
- 最大值永远是 1(最后一名)
- 结果是 小数(0.00 - 1.00),乘以 100 就是百分比
二、和 RANK/DENSE_RANK 的区别
| 函数 | 返回值 | 范围 | 含义 | 示例 |
|---|---|---|---|---|
RANK() |
整数 | 1, 2, 3... | 绝对排名 | 第 5 名 |
DENSE_RANK() |
整数 | 1, 2, 3... | 紧凑排名 | 第 5 名 |
PERCENT_RANK() |
小数 | 0.0 - 1.0 | 相对位置 | 超过 44% 的人 |
直观对比:
sql
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank,
COUNT(*) OVER () AS total,
ROUND(PERCENT_RANK() OVER (ORDER BY score DESC)::DECIMAL * 100, 2) AS percentile
FROM students;
-- 结果(假设 10 人):
-- name | score | rank | total | percentile
-- ------+-------+------+-------+------------
-- 张三 | 100 | 1 | 10 | 0.00 ← 第 1 名,0%
-- 李四 | 95 | 2 | 10 | 11.11 ← 超过 11% 的人
-- 王五 | 90 | 3 | 10 | 22.22
-- ...
-- 赵六 | 60 | 10 | 10 | 100.00 ← 最后一名,100%
三、8 个实用场景
场景 1:计算百分位(最经典)
需求: 告诉员工"你的业绩超过了公司多少比例的人"
sql
SELECT
emp_name,
sales_amount,
ROUND(PERCENT_RANK() OVER (ORDER BY sales_amount ASC)::DECIMAL * 100, 2) AS percentile
FROM sales_performance
ORDER BY percentile DESC;
解读:
percentile = 90:你的业绩超过了 90% 的人(顶尖水平)percentile = 50:你的业绩中等,超过了一半的人percentile = 10:你的业绩较低,只超过了 10% 的人
注意: ORDER BY ASC 时,数值越大,percentile 越高;ORDER BY DESC 时相反。
场景 2:找出前/后 N% 的数据
需求: 找出业绩最好的前 10% 员工
sql
SELECT * FROM (
SELECT
emp_name,
sales_amount,
PERCENT_RANK() OVER (ORDER BY sales_amount DESC) AS pr
FROM sales_performance
) t
WHERE pr <= 0.1; -- 前 10%
其他用法:
sql
-- 后 20%(需要改进的员工)
WHERE pr >= 0.8
-- 中间 60%(中等水平)
WHERE pr BETWEEN 0.2 AND 0.8
-- 去掉最高和最低各 5%(排除异常值)
WHERE pr BETWEEN 0.05 AND 0.95
场景 3:分组百分位(部门内对比)
需求: 计算每个员工在部门内的百分位
sql
SELECT
dept_name,
emp_name,
sales_amount,
ROUND(PERCENT_RANK() OVER (
PARTITION BY dept_name
ORDER BY sales_amount DESC
)::DECIMAL * 100, 2) AS dept_percentile
FROM employees
ORDER BY dept_name, dept_percentile DESC;
结果示例:
dept_name | emp_name | sales_amount | dept_percentile
----------+----------+--------------+----------------
销售部 | 张三 | 100000 | 0.00 ← 销售部第 1
销售部 | 李四 | 95000 | 33.33 ← 超过销售部 33% 的人
技术部 | 王五 | 80000 | 0.00 ← 技术部第 1
技术部 | 赵六 | 75000 | 50.00 ← 技术部中等
优势: 跨部门对比更公平(销售部的 10 万可能不如技术部的 8 万难拿)
场景 4:标准化不同量纲的数据
需求: 把销售额(万元)和客户数(个)统一成百分位,方便综合评分
sql
SELECT
emp_name,
sales_amount,
customer_count,
ROUND(PERCENT_RANK() OVER (ORDER BY sales_amount DESC)::DECIMAL * 100, 2) AS sales_pct,
ROUND(PERCENT_RANK() OVER (ORDER BY customer_count DESC)::DECIMAL * 100, 2) AS customer_pct,
ROUND(
(PERCENT_RANK() OVER (ORDER BY sales_amount DESC) +
PERCENT_RANK() OVER (ORDER BY customer_count DESC)) / 2 * 100,
2
) AS combined_score
FROM employee_performance;
为什么用 PERCENT_RANK?
- 销售额范围:10万 - 1000万
- 客户数范围:5 - 500
- 直接相加没意义,转换成百分位后可以加权平均
场景 5:检测数据分布
需求: 分析销售业绩的分布是否均匀
sql
SELECT
CASE
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) < 0.25 THEN 'Q1 前25%'
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) < 0.50 THEN 'Q2 25-50%'
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) < 0.75 THEN 'Q3 50-75%'
ELSE 'Q4 后25%'
END AS quartile,
COUNT(*) AS emp_count,
AVG(sales_amount) AS avg_sales,
MIN(sales_amount) AS min_sales,
MAX(sales_amount) AS max_sales
FROM sales_performance
GROUP BY quartile
ORDER BY quartile;
用途: 快速了解业绩集中度,是否有"二八定律"现象
场景 6:动态阈值划分等级
需求: 根据实际分布划分 ABCD 等级(而非固定阈值)
sql
SELECT
emp_name,
sales_amount,
CASE
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) <= 0.1 THEN 'S 级 (前10%)'
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) <= 0.3 THEN 'A 级 (10-30%)'
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) <= 0.6 THEN 'B 级 (30-60%)'
WHEN PERCENT_RANK() OVER (ORDER BY sales_amount DESC) <= 0.9 THEN 'C 级 (60-90%)'
ELSE 'D 级 (后10%)'
END AS grade
FROM sales_performance;
优势: 自动适应数据变化,不需要手动调整阈值
场景 7:异常值检测
需求: 找出极端高或极端低的订单金额
sql
SELECT
order_no,
amount,
ROUND(PERCENT_RANK() OVER (ORDER BY amount ASC)::DECIMAL * 100, 2) AS percentile
FROM orders
WHERE PERCENT_RANK() OVER (ORDER BY amount ASC) < 0.01 -- 最低 1%
OR PERCENT_RANK() OVER (ORDER BY amount ASC) > 0.99; -- 最高 1%
用途: 风控系统识别可疑交易
场景 8:生成累积分布图数据
需求: 为报表生成累积分布数据
sql
SELECT
ROUND(PERCENT_RANK() OVER (ORDER BY sales_amount ASC)::DECIMAL * 100, 2) AS x_axis,
sales_amount AS y_axis
FROM sales_performance
ORDER BY x_axis;
用途: 在 Excel 或 BI 工具中绘制累积分布曲线
四、核心语法
sql
PERCENT_RANK() OVER (
PARTITION BY column1, column2 -- 可选:分组依据
ORDER BY column3 ASC/DESC -- 必填:排序规则
)
关键点:
- 不需要参数 :
PERCENT_RANK()括号里是空的 - 必须配合 OVER():声明这是窗口函数
- ORDER BY 必填:决定排名方向
- 返回值是小数:0.0 - 1.0,通常乘以 100 转成百分比
五、计算公式详解
PERCENT_RANK = (rank - 1) / (total_rows - 1)
其中:
- rank = RANK() 的值(从 1 开始)
- total_rows = 窗口内的总行数
示例推导:
sql
-- 假设有 5 行数据
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank,
COUNT(*) OVER () AS total,
(RANK() OVER (ORDER BY score DESC) - 1)::DECIMAL / (COUNT(*) OVER () - 1) AS manual_pr,
PERCENT_RANK() OVER (ORDER BY score DESC) AS auto_pr
FROM students;
-- 结果:
-- name | score | rank | total | manual_pr | auto_pr
-- ------+-------+------+-------+-----------+---------
-- 张三 | 100 | 1 | 5 | 0/4=0.00 | 0.00
-- 李四 | 95 | 2 | 5 | 1/4=0.25 | 0.25
-- 王五 | 90 | 3 | 5 | 2/4=0.50 | 0.50
-- 赵六 | 85 | 4 | 5 | 3/4=0.75 | 0.75
-- 钱七 | 80 | 5 | 5 | 4/4=1.00 | 1.00
特殊情况:
- 如果只有 1 行:
(1-1)/(1-1) = 0/0,PostgreSQL 返回NULL - 如果有并列:使用
RANK()的排名(会跳号)
六、性能优化
1. 避免重复计算
sql
-- ❌ 慢:多次调用 PERCENT_RANK
SELECT
emp_name,
PERCENT_RANK() OVER (ORDER BY sales DESC) AS pr,
CASE
WHEN PERCENT_RANK() OVER (ORDER BY sales DESC) <= 0.1 THEN '优秀'
ELSE '普通'
END AS level
FROM employees;
-- ✅ 快:用子查询或 CTE
WITH ranked AS (
SELECT
emp_name,
sales,
PERCENT_RANK() OVER (ORDER BY sales DESC) AS pr
FROM employees
)
SELECT
emp_name,
pr,
CASE WHEN pr <= 0.1 THEN '优秀' ELSE '普通' END AS level
FROM ranked;
2. 合理使用索引
sql
-- 为 ORDER BY 字段创建索引
CREATE INDEX idx_employees_sales ON employees (sales_amount);
-- 为 PARTITION BY + ORDER BY 创建复合索引
CREATE INDEX idx_emp_dept_sales ON employees (dept_id, sales_amount);
七、常见错误
错误 1:忘记乘以 100
sql
-- ❌ 不直观:0.85 是什么意思?
SELECT PERCENT_RANK() OVER (ORDER BY score) AS pr FROM students;
-- ✅ 清晰:85.00% 一目了然
SELECT ROUND(PERCENT_RANK() OVER (ORDER BY score)::DECIMAL * 100, 2) AS percentile
FROM students;
错误 2:混淆 ASC 和 DESC
sql
-- 场景:分数越高越好
SELECT
name,
score,
PERCENT_RANK() OVER (ORDER BY score DESC) AS pr_desc, -- 高分 pr 低
PERCENT_RANK() OVER (ORDER BY score ASC) AS pr_asc -- 高分 pr 高
FROM students;
-- 结果:
-- name | score | pr_desc | pr_asc
-- ------+-------+---------+--------
-- 张三 | 100 | 0.00 | 1.00 ← 最高分
-- 李四 | 60 | 1.00 | 0.00 ← 最低分
-- 记忆技巧:
-- ORDER BY DESC(降序):第 1 名 pr=0,最后一名 pr=1
-- ORDER BY ASC(升序):第 1 名 pr=1,最后一名 pr=0
错误 3:在 WHERE 中直接使用
sql
-- ❌ 错误
SELECT * FROM employees
WHERE PERCENT_RANK() OVER (ORDER BY salary DESC) <= 0.1;
-- ✅ 正确:用子查询
SELECT * FROM (
SELECT *, PERCENT_RANK() OVER (ORDER BY salary DESC) AS pr
FROM employees
) t
WHERE pr <= 0.1;
错误 4:单行数据返回 NULL
sql
-- 如果窗口内只有 1 行
SELECT PERCENT_RANK() OVER () AS pr FROM single_row_table;
-- 结果:pr = NULL(因为除以 0)
-- ✅ 处理:用 COALESCE
SELECT COALESCE(PERCENT_RANK() OVER (), 0) AS pr FROM single_row_table;
八、PERCENT_RANK vs CUME_DIST
这两个函数很像,但有细微差别:
| 函数 | 公式 | 最小值 | 最大值 | 并列处理 |
|---|---|---|---|---|
PERCENT_RANK() |
(rank-1)/(n-1) | 0 | 1 | 并列值相同 |
CUME_DIST() |
小于等于当前值的行数/n | 1/n | 1 | 并列值相同 |
对比示例:
sql
SELECT
name,
score,
ROUND(PERCENT_RANK() OVER (ORDER BY score DESC)::DECIMAL * 100, 2) AS pr,
ROUND(CUME_DIST() OVER (ORDER BY score DESC)::DECIMAL * 100, 2) AS cd
FROM students;
-- 结果(假设有并列):
-- name | score | pr | cd
-- ------+-------+-------+------
-- 张三 | 100 | 0.00 | 33.33 ← 3 个人并列第 1
-- 李四 | 100 | 0.00 | 33.33
-- 王五 | 100 | 0.00 | 33.33
-- 赵六 | 95 | 37.50 | 66.67
-- 钱七 | 90 | 75.00 | 100.00
-- 💡 区别:
-- PERCENT_RANK 基于排名位置
-- CUME_DIST 基于累积比例
九、记忆口诀
PERCENT_RANK 百分位,相对位置看得清
零到一百是范围,小数乘百变百分比
前百分之几好判断,异常检测也轻松
ASC DESC 要注意,高低方向别搞混
十、总结
核心要点
- PERCENT_RANK() = 百分比排名(0% - 100%)
- 适用场景 = 百分位计算、前 N% 筛选、标准化数据、异常检测
- 返回值 = 小数 0.0 - 1.0,通常乘以 100 转成百分比
- 计算公式 = (排名 - 1) / (总行数 - 1)
- 使用时机 = 需要"相对位置"而非"绝对排名"时
快速参考
sql
-- 基本模板
SELECT
字段列表,
ROUND(PERCENT_RANK() OVER (
PARTITION BY 分组字段 -- 可选
ORDER BY 排序字段 DESC -- 必填
)::DECIMAL * 100, 2) AS percentile
FROM 表名;
-- 前 10%
SELECT * FROM (
SELECT *, PERCENT_RANK() OVER (ORDER BY sales DESC) AS pr
FROM employees
) t WHERE pr <= 0.1;
-- 分组百分位
SELECT dept, name, sales,
ROUND(PERCENT_RANK() OVER (PARTITION BY dept ORDER BY sales DESC)::DECIMAL * 100, 2) AS pct
FROM employees;
实战速查
sql
-- 1. 计算百分位
SELECT name, score,
ROUND(PERCENT_RANK() OVER (ORDER BY score DESC)::DECIMAL * 100, 2) || '%' AS percentile
FROM students;
-- 2. 找前 20%
SELECT * FROM (
SELECT *, PERCENT_RANK() OVER (ORDER BY revenue DESC) AS pr
FROM companies
) t WHERE pr <= 0.2;
-- 3. 部门内对比
SELECT dept, emp, sales,
ROUND(PERCENT_RANK() OVER (PARTITION BY dept ORDER BY sales DESC)::DECIMAL * 100, 2) AS dept_pct
FROM employees;
-- 4. 异常值检测
SELECT * FROM (
SELECT *, PERCENT_RANK() OVER (ORDER BY amount ASC) AS pr
FROM transactions
) t WHERE pr < 0.01 OR pr > 0.99;