窗口排序函数详细解读及示例
窗口排序函数是 SQL 中用于对窗口内的数据进行排序并赋予序号的一类函数。本文详细解读
ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE()、PERCENT_RANK()和CUME_DIST()的用法、区别及实战示例。
📑 目录
- [1. 窗口排序函数概述](#1. 窗口排序函数概述)
- [2. ROW_NUMBER():唯一连续序号](#2. ROW_NUMBER():唯一连续序号)
- [3. RANK():跳跃排名](#3. RANK():跳跃排名)
- [4. DENSE_RANK():连续排名](#4. DENSE_RANK():连续排名)
- [5. NTILE():分桶排名](#5. NTILE():分桶排名)
- [6. PERCENT_RANK():相对百分比排名](#6. PERCENT_RANK():相对百分比排名)
- [7. CUME_DIST():累积分布](#7. CUME_DIST():累积分布)
- [8. 六大函数对比总结](#8. 六大函数对比总结)
- [9. 综合实战示例](#9. 综合实战示例)
1. 窗口排序函数概述
窗口排序函数属于窗口函数的一种,其基本语法如下:
sql
函数名() OVER (
PARTITION BY 分组字段 -- 可选,指定分区
ORDER BY 排序字段 -- 必选,指定排序规则
)
| 函数 | 返回值 | 特点 |
|---|---|---|
ROW_NUMBER() |
从 1 开始的连续整数 | 每个分区内序号唯一且连续,不分并列 |
RANK() |
跳跃排名 | 并列时序号相同,但下一个序号跳跃(如 1,1,3) |
DENSE_RANK() |
连续排名 | 并列时序号相同,下一个序号连续(如 1,1,2) |
NTILE(n) |
1 到 n 的桶号 | 将分区内数据尽可能均匀地分成 n 个桶 |
PERCENT_RANK() |
0 到 1 之间 | 相对排名:(RANK-1) / (总行数-1) |
CUME_DIST() |
0 到 1 之间 | 累积分布:<=当前值的行数 / 总行数 |
2. ROW_NUMBER():唯一连续序号
含义
为每一行分配一个唯一的、连续的序号,从 1 开始。即使排序字段的值相同,也会随机(或根据其他列)赋予不同的序号。
语法
sql
ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 分组 TopN(如每个部门薪资前三)
- 数据去重(保留每组第一条)
- 生成行号
示例
表结构 :emp(emp_id, dept, salary)
| emp_id | dept | salary |
|---|---|---|
| 1 | A | 1000 |
| 2 | A | 2000 |
| 3 | A | 2000 |
| 4 | B | 1500 |
SQL:
sql
SELECT emp_id, dept, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM emp;
结果:
| emp_id | dept | salary | rn |
|---|---|---|---|
| 2 | A | 2000 | 1 |
| 3 | A | 2000 | 2 |
| 1 | A | 1000 | 3 |
| 4 | B | 1500 | 1 |
注意:A部门中薪资相同(2000)的两条记录分别获得了 1 和 2,没有并列。
3. RANK():跳跃排名
含义
排序后,相同值的行获得相同序号,但下一个不同值的序号会跳过中间的数量。例如:1, 1, 3, 4...
语法
sql
RANK() OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 需要处理并列且允许名次跳跃的排名(如比赛排名)
- 财务或学术排名(如奖学金评定)
示例
SQL:
sql
SELECT emp_id, dept, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM emp;
结果:
| emp_id | dept | salary | rk |
|---|---|---|---|
| 2 | A | 2000 | 1 |
| 3 | A | 2000 | 1 |
| 1 | A | 1000 | 3 |
| 4 | B | 1500 | 1 |
注意:两个 2000 并列第 1,下一个行获得第 3 名(跳过了 2)。
4. DENSE_RANK():连续排名
含义
排序后,相同值的行获得相同序号,但下一个不同值的序号是连续的(不跳跃)。例如:1, 1, 2, 3...
语法
sql
DENSE_RANK() OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 需要处理并列但希望名次连续的场景(如业绩等级评定)
- 压缩排名,不产生空洞
示例
SQL:
sql
SELECT emp_id, dept, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dr
FROM emp;
结果:
| emp_id | dept | salary | dr |
|---|---|---|---|
| 2 | A | 2000 | 1 |
| 3 | A | 2000 | 1 |
| 1 | A | 1000 | 2 |
| 4 | B | 1500 | 1 |
注意:两个 2000 并列第 1,下一个行获得第 2 名(连续)。
5. NTILE():分桶排名
含义
将分区内的数据尽可能均匀地分成 n 个桶(组),并为每行分配一个桶号(1 到 n)。若无法均匀分配,前若干桶会多一行。
语法
sql
NTILE(n) OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 数据分箱(如将用户按消费金额分成高、中、低三档)
- 采样(取第 1 桶数据)
- 并行处理任务划分
示例
SQL(将 A 部门 4 条数据分成 3 个桶):
sql
SELECT emp_id, dept, salary,
NTILE(3) OVER (PARTITION BY dept ORDER BY salary DESC) AS bucket
FROM emp;
结果:
| emp_id | dept | salary | bucket |
|---|---|---|---|
| 2 | A | 2000 | 1 |
| 3 | A | 2000 | 1 |
| 1 | A | 1000 | 2 |
| (假设第4条) | A | 900 | 3 |
注意:4 行分 3 桶,桶 1 有 2 行,桶 2 和桶 3 各有 1 行。排序决定了行进入哪个桶。
6. PERCENT_RANK():相对百分比排名
含义
计算某行的相对排名位置,公式为:(RANK - 1) / (总行数 - 1)。返回值范围 [0, 1]。通常用来表示"超过百分之多少的行"。
语法
sql
PERCENT_RANK() OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 计算百分位数(如成绩超过 90% 的同学)
- 数据分布分析
示例
SQL:
sql
SELECT emp_id, dept, salary,
RANK() OVER w AS rk,
PERCENT_RANK() OVER w AS pr
FROM emp
WINDOW w AS (PARTITION BY dept ORDER BY salary DESC);
结果(A 部门共 3 行):
| emp_id | dept | salary | rk | pr |
|---|---|---|---|---|
| 2 | A | 2000 | 1 | (1-1)/(3-1)=0.0 |
| 3 | A | 2000 | 1 | 0.0 |
| 1 | A | 1000 | 3 | (3-1)/(3-1)=1.0 |
公式中分母为
(总行数-1),所以最大值总是 1.0,最小值总是 0.0。
7. CUME_DIST():累积分布
含义
计算小于等于当前值的行数占总行数的比例。公式:<=当前值的行数 / 总行数。返回值范围 (0, 1]。
语法
sql
CUME_DIST() OVER (PARTITION BY col1 ORDER BY col2)
适用场景
- 计算经验累积分布函数(ECDF)
- 判断某值在整个数据集中的"分位"
示例
SQL:
sql
SELECT emp_id, dept, salary,
CUME_DIST() OVER (PARTITION BY dept ORDER BY salary DESC) AS cd
FROM emp;
结果(A 部门共 3 行):
| emp_id | dept | salary | cd |
|---|---|---|---|
| 2 | A | 2000 | 2/3 ≈ 0.6667 |
| 3 | A | 2000 | 2/3 ≈ 0.6667 |
| 1 | A | 1000 | 3/3 = 1.0 |
解释:2000 有两行,小于等于 2000 的行数是 2,总行数 3,所以 cd = 2/3。1000 是最大值(降序排列下1000最小),所有行都 ≤ 1000,故 cd = 1。
8. 六大函数对比总结
| 函数 | 公式 | 相同值处理 | 序号连续性 | 值域 |
|---|---|---|---|---|
ROW_NUMBER() |
无 | 不同序号,随机定序 | 连续 (1,2,3,4) | 正整数 |
RANK() |
无 | 相同序号 | 跳跃 (1,1,3,4) | 正整数 |
DENSE_RANK() |
无 | 相同序号 | 连续 (1,1,2,3) | 正整数 |
NTILE(n) |
无 | 可能分到不同桶 | 桶号连续 | 1...n |
PERCENT_RANK() |
(RANK-1)/(count-1) |
相同值得到相同值 | 连续浮点 | [0,1] |
CUME_DIST() |
<=当前值的行数/总行数 |
相同值得到相同值 | 连续浮点 | (0,1] |
快速记忆
- ROW_NUMBER:唯一不重复,类似行号。
- RANK:跳号,适合"竞赛排名"。
- DENSE_RANK:不跳号,适合"等级评定"。
- NTILE:分桶,等频划分。
- PERCENT_RANK:相对排名(从0%到100%)。
- CUME_DIST:累积占比(包含自身)。
9. 综合实战示例
场景 :员工绩效表 performance(emp_id, dept, score),要求:
- 每个部门按分数降序生成唯一序号(用于去重)。
- 生成有并列且不跳号的排名(用于发奖金档位)。
- 将每个部门员工按分数分为高、中、低三档(NTILE 3)。
- 计算每个员工分数在部门内的累积占比(CUME_DIST)。
SQL:
sql
SELECT emp_id, dept, score,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY score DESC) AS row_num,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY score DESC) AS dense_rk,
NTILE(3) OVER (PARTITION BY dept ORDER BY score DESC) AS tier,
CUME_DIST() OVER (PARTITION BY dept ORDER BY score DESC) AS cum_dist
FROM performance;
结果示例(假设某部门分数为 95, 95, 90, 85):
| emp_id | dept | score | row_num | dense_rk | tier | cum_dist |
|---|---|---|---|---|---|---|
| 1 | A | 95 | 1 | 1 | 1 | 0.5 |
| 2 | A | 95 | 2 | 1 | 1 | 0.5 |
| 3 | A | 90 | 3 | 2 | 2 | 0.75 |
| 4 | A | 85 | 4 | 3 | 3 | 1.0 |
解释:
cum_dist为 0.5 表示有 50% 的员工分数 ≤ 95(包括自己);NTILE(3) 将 4 行分 3 档,排序后前两行(95)进入 tier1,第三行(90)进入 tier2,第四行(85)进入 tier3。
以上详细解读了 Hive/Spark SQL 中常用的窗口排序函数。掌握它们的区别和适用场景,可以高效解决排名、分桶、分布分析等问题。