ROW_NUMBER 窗口函数详解
一、一句话理解
ROW_NUMBER() 给每一行分配一个唯一的序号,从 1 开始。即使两行数据完全相同,序号也绝不重复。
二、函数语法
ROW_NUMBER() OVER (PARTITION BY 分组列 ORDER BY 排序列)
PARTITION BY:可选,按指定列分组,每组内独立编号ORDER BY:必选,决定编号顺序
三、真实业务场景
场景1:每组取 Top N
查询每个部门薪资最高的 3 名员工。
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employee
) t
WHERE rn <= 3;
示例数据:
+----------+--------+--------+
| dept | name | salary |
+----------+--------+--------+
| 技术部 | 张三 | 50000 |
| 技术部 | 李四 | 45000 |
| 技术部 | 王五 | 42000 |
| 技术部 | 赵六 | 40000 |
| 销售部 | 孙七 | 48000 |
| 销售部 | 周八 | 35000 |
+----------+--------+--------+
结果(每个部门取前3):
+----------+--------+--------+
| dept | name | salary |
+----------+--------+--------+
| 技术部 | 张三 | 50000 |
| 技术部 | 李四 | 45000 |
| 技术部 | 王五 | 42000 |
| 销售部 | 孙七 | 48000 |
| 销售部 | 周八 | 35000 |
+----------+--------+--------+
注意:ROW_NUMBER 即使 salary 相同也不并列。如果赵六和王五薪资相同都是 42000,ROW_NUMBER 会随机给其中一人编号 3,另一人编号 4,导致只取到其中一人。如果需要并列结果,改用
DENSE_RANK()。
场景2:去重保留最新记录
用户表有多条记录(每次修改都插入一条),取每个用户的最新一条。
SELECT user_id, name, email, update_time
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY update_time DESC) AS rn
FROM user_history
) t
WHERE rn = 1;
四、与 RANK / DENSE_RANK 的对比
| 函数 | 相同值处理 | 示例 (A=100, B=100, C=90) |
|---|---|---|
| ROW_NUMBER | 不并列 | A=1, B=2, C=3 |
| RANK | 并列跳号 | A=1, B=1, C=3 |
| DENSE_RANK | 并列不跳号 | A=1, B=1, C=2 |
五、常见坑点
坑1:ORDER BY 不能省略
ROW_NUMBER 必须指定排序,否则结果是随机的。面试中忘记写 ORDER BY 直接扣分。
坑2:相同值顺序不确定
当 ORDER BY 的列有相同值时,ROW_NUMBER 的分配是随机的。需要稳定排序时,应在 ORDER BY 中加第二排序键(如主键)。
六、面试怎么考
- "ROW_NUMBER 和 RANK 有什么区别?" → 字节/阿里高频
- "怎么查询每个部门工资前3的员工?" → 子查询 + ROW_NUMBER
- "怎么去重只保留最新一条记录?" → PARTITION BY + ORDER BY DESC + rn=1
七、相关面试题
- 02. 每个用户的最长连续登录天数 — ROW_NUMBER + 差值法
- 13. 每个部门工资前3高的员工 — ROW_NUMBER + PARTITION BY Top N
- 03. 商品销售TOP N — ROW_NUMBER 经典去重
- 更多窗口函数面试题 →
📱关注公众号
「数据仓库技术」文章同步更新,不错过每一篇干货

💬加群交流
备注「数据仓库技术」加入社群,每日一道大厂SQL真题
