ROW_NUMBER、RANK、DENSE_RANK 到底有什么区别?
先说结论
三个函数都是排名函数,区别在于遇到并列值时的处理方式:
ROW_NUMBER(): 强制不重复,并列也给不同名次(1, 2, 3, 4)
RANK(): 允许并列,但跳号(1, 1, 3, 4)
DENSE_RANK(): 允许并列,不跳号(1, 1, 2, 3)
光看定义可能有点抽象,我们直接上场景。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
这道题基本是SQL面试的第一关,出现频率极高,但很多人只能说出"排序方式不同"就卡住了。今天用一个真实业务场景帮你把这三个函数彻底搞透。
业务场景:广告素材消耗排名
假设你是短剧出海公司的数据开发(没错就是我的日常),需要统计每个地区的广告素材按消耗金额排名。数据长这样:
-- 广告素材消耗表 ad_cost
| region | creative_id | cost |
|--------|-------------|-------|
| 美国 | A001 | 5000 |
| 美国 | A002 | 5000 |
| 美国 | A003 | 3000 |
| 美国 | A004 | 2000 |
| 日本 | B001 | 8000 |
| 日本 | B002 | 6000 |
注意美国区的A001和A002消耗相同,都是5000。
三个函数的SQL和结果
SELECT
region,
creative_id,
cost,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY cost DESC) AS rn,
RANK() OVER (PARTITION BY region ORDER BY cost DESC) AS rk,
DENSE_RANK() OVER (PARTITION BY region ORDER BY cost DESC) AS dr
FROM ad_cost
美国区的结果:
| creative_id | cost | rn | rk | dr |
|-------------|------|----|----|----|
| A001 | 5000 | 1 | 1 | 1 |
| A002 | 5000 | 2 | 1 | 1 |
| A003 | 3000 | 3 | 3 | 2 |
| A004 | 2000 | 4 | 4 | 3 |
看出区别了吗?
ROW_NUMBER:A001和A002虽然cost相同,但被强制分了1和2,谁排前面取决于引擎(不确定性)
RANK:两个都是第1,但第3个直接跳到3(跳号)
DENSE_RANK:两个都是第1,第3个是2(连续)
面试高频追问:什么场景用哪个?
这才是面试官真正想听的。
ROW_NUMBER —— TopN取数
最常用的场景。比如"取每个地区消耗最高的3个素材":
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY cost DESC) AS rn
FROM ad_cost
) t
WHERE rn <= 3
为什么用ROW_NUMBER而不是RANK?因为如果第3名有2个并列,RANK会返回4条数据,而业务方通常只要严格的3条。ROW_NUMBER能保证结果行数确定。
RANK —— 竞赛/考试排名
适合需要体现"并列后跳号"的场景,比如考试排名:第1名2个人,下一个就是第3名,没有第2名。
DENSE_RANK —— 分档/分级
适合需要连续分档的场景。比如"把用户按消费金额分成5个档次",你不希望因为并列导致某个档次被跳过。
一个容易踩的坑
ROW_NUMBER在遇到相同值时,排名结果是不确定的。同样的SQL跑两次,A001和A002谁排第1可能不一样。
如果你需要结果稳定,一定要在ORDER BY里加一个唯一字段兜底:
ROW_NUMBER() OVER (
PARTITION BY region
ORDER BY cost DESC, creative_id ASC -- 加creative_id保证稳定
) AS rn
这个细节面试里说出来,绝对加分。我之前做埋点数据去重的时候就踩过这个坑——同一个用户同一秒有两条曝光事件,ROW_NUMBER去重后每次跑出来的结果都不一样,下游数据忽高忽低,排查了半天才发现是这个问题。
总结
| 函数 | 并列处理 | 是否跳号 | 典型场景 |
|---|---|---|---|
| ------ | ---------- | ---------- | ---------- |
| ROW_NUMBER | 强制不同 | — | TopN取数、去重 |
| RANK | 允许并列 | 跳号 | 竞赛排名 |
| DENSE_RANK | 允许并列 | 不跳号 | 分档分级 |
记住一句话:取数用ROW_NUMBER,排名用RANK,分档用DENSE_RANK。
面试的时候不要只背定义,能结合业务场景说清楚"什么时候用哪个"才是关键。
下一题我们聊一道经典中等题:怎么查连续登录N天的用户? 这道题思路非常巧妙,但很多人第一次见都会懵。
你们面试被问过排名函数吗?评论区聊聊~
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长