SQL 开窗函数排序详解
ROW_NUMBER vs RANK vs DENSE_RANK vs NTILE vs PERCENT_RANK
五大排序开窗函数对比
| 函数 | 相同值处理 | 排名连续性 | 特点 | 典型场景 |
|---|---|---|---|---|
| ROW_NUMBER() | 随机/按顺序分配唯一序号 | 连续 | 无重复序号 | 分页、去重、分配唯一ID |
| RANK() | 相同值同排名,占多个位置 | 跳跃 | 1,1,3,4(跳2) | 竞赛排名、允许并列 |
| DENSE_RANK() | 相同值同排名,不占位置 | 连续 | 1,1,2,3(不跳) | 等级划分、连续评级 |
| NTILE(n) | 强制分n组 | 连续 | 等分桶 | 分位数、AB测试分组 |
| PERCENT_RANK() | 计算百分比排名 | 0-1之间 | (rank-1)/(总行数-1) | 百分位分析 |
直观对比示例
假设成绩表 scores:
SELECT
学生,
分数,
ROW_NUMBER() OVER (ORDER BY 分数 DESC) AS rn,
RANK() OVER (ORDER BY 分数 DESC) AS rk,
DENSE_RANK() OVER (ORDER BY 分数 DESC) AS drk,
NTILE(3) OVER (ORDER BY 分数 DESC) AS nt,
ROUND(PERCENT_RANK() OVER (ORDER BY 分数 DESC), 2) AS pr
FROM scores;
执行结果对比
| 学生 | 分数 | ROW_NUMBER | RANK | DENSE_RANK | NTILE(3) | PERCENT_RANK |
|---|---|---|---|---|---|---|
| 张三 | 100 | 1 | 1 | 1 | 1 | 0.00 |
| 李四 | 100 | 2 | 1 | 1 | 1 | 0.00 |
| 王五 | 90 | 3 | 3 | 2 | 2 | 0.40 |
| 赵六 | 80 | 4 | 4 | 3 | 2 | 0.60 |
| 孙七 | 80 | 5 | 4 | 3 | 3 | 0.60 |
| 周八 | 70 | 6 | 6 | 4 | 3 | 1.00 |
💡
关键观察:
• 张三和李四分数相同(100分),RANK() 和 DENSE_RANK() 都给他们并列第1
• 王五90分,RANK() 给他第3名(跳过了第2),而 DENSE_RANK() 给他第2名(连续)
• ROW_NUMBER() 强制给李四第2名(即使分数相同)
各函数详解与示例
- ROW_NUMBER()
为每一行分配唯一的连续序号,即使值相同也强制排序。
**用途:**分页查询、去重保留一条、分配唯一ID
- RANK()
相同值同排名,下一名跳过中间名次(跳号)。
**用途:**竞赛排名、奥运奖牌榜、考试排名
- DENSE_RANK()
相同值同排名,下一名紧接(不跳号)。
**用途:**等级评定(A/B/C/D)、连续评级
- NTILE(n)
强制将数据分成n组,每组人数尽可能相等。
**用途:**前25%用户、AB测试分组、分位数分析
代码示例
-- 1. ROW_NUMBER():每班前3名,分数相同只取一个
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY 班级 ORDER BY 成绩 DESC) AS rn
FROM students
) WHERE rn <= 3;
-- 2. NTILE():按消费金额分4组(ABCD等级)
SELECT
NTILE(4) OVER (ORDER BY 消费金额 DESC) AS 消费等级,
NTILE(10) OVER (ORDER BY 活跃度 DESC) AS 活跃十分位
FROM users;
-- 3. PERCENT_RANK():计算百分位
SELECT
学生,
ROUND(PERCENT_RANK() OVER (ORDER BY 分数), 2) AS 百分位
FROM scores;
-- 结果:0=第1名,1=最后1名,0.9=超过90%的人
使用场景速查表
- 每班前3名,分数相同只取一个ROW_NUMBER()
- 竞赛排名,允许并列,跳号RANK()
- 等级划分(优秀/良好/及格)DENSE_RANK()
- 按成绩分4组(ABCD等级)NTILE(4)
- 计算成绩超过90%的同学PERCENT_RANK()
- 分页查询(第2页,每页10条)ROW_NUMBER()
- 找出销售额前20%的店铺NTILE(5)
窗口函数完整语法
函数名() OVER (
[PARTITION BY 分组列] -- 分组(可选)
[ORDER BY 排序列] -- 排序(可选)
[ROWS/RANGE 窗口范围] -- 滑动窗口(可选)
)
分组排名示例
SELECT
班级,
学生,
分数,
RANK() OVER (PARTITION BY 班级 ORDER BY 分数 DESC) AS 班内排名
FROM scores;
-- 结果:每个班级内部独立排名
数据库支持情况
MySQL 8.0+PostgreSQLSQL ServerOracleSQLite 3.25+Spark SQLHiveClickHouse