订阅更新

ROW_NUMBER、RANK、DENSE_RANK 到底有什么区别?

SQL手册 第 1 题 约 4 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:广告素材消耗排名
  3. 三个函数的SQL和结果
  4. 面试高频追问:什么场景用哪个?
  5. 一个容易踩的坑
  6. 总结

先说结论

三个函数都是排名函数,区别在于遇到并列值时的处理方式:

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年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器