SQL窗口函数完全指南:ROW_NUMBER、RANK、DENSE_RANK实战详解
窗口函数是数据分析师从"会SQL"到"精通SQL"的分水岭。本文用真实业务场景,把三个最常用的排名函数讲透。
一、什么是窗口函数
普通聚合函数(如 SUM、COUNT)会把多行数据压缩成一行。而窗口函数不同:它在每一行上计算,但可以"看到"同一窗口内的其他行。
结果:行数不变,但每行多了一列"窗口内的计算结果"。
语法模板:
`-- 窗口函数基本语法 函数名() OVER ( PARTITION BY 分组列 -- 按什么分组(可选) ORDER BY 排序列 DESC -- 按什么排序 )
## 二、三个排名函数的核心区别
先用一个简单的例子看清楚区别:
`-- 示例数据:销售员业绩表
-- 姓名 部门 销售额
-- 小王 华南 100
-- 小李 华南 80
-- 小张 华南 80 ← 和小李并列
-- 小赵 华南 60
SELECT
姓名,
销售额,
ROW_NUMBER() OVER (ORDER BY 销售额 DESC) AS row_num,
RANK() OVER (ORDER BY 销售额 DESC) AS rank_num,
DENSE_RANK() OVER (ORDER BY 销售额 DESC) AS dense_num
FROM 销售表;
输出结果:
`姓名 销售额 row_num rank_num dense_num 小王 100 1 1 1 小李 80 2 2 2 小张 80 3 2 2 ← 同分同名次 小赵 60 4 4 3 ← 区别在这里!
三个函数的本质区别:
- ROW_NUMBER():不管有没有并列,每行给一个唯一序号。并列的也强行分开排。
- RANK():并列的给相同名次,但下一个名次会跳过。(小李小张都是第2,下一个直接第4)
- DENSE_RANK():并列的给相同名次,下一个名次不跳过。(小李小张都是第2,下一个是第3)
## 三、ROW_NUMBER():最常用的去重神器
## 场景1:每个用户只取最新的一条订单
`-- 业务场景:orders表有重复数据,每个user_id保留最新的一条
SELECT *
FROM (
SELECT
user_id,
order_id,
order_time,
amount,
-- 按用户分组,按时间倒序排名
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY order_time DESC
) AS rn
FROM orders
) t
WHERE rn = 1; -- 只取每个用户的第1条(最新)
注意:为什么不用 RANK?因为如果同一用户有两条完全相同时间的订单,RANK 会返回2条(名次都是1),而 ROW_NUMBER 严格只保留1条。
场景2:分页查询(OFFSET/LIMIT的替代方案)
`-- 获取销售额排名第11-20名的员工(第2页) SELECT * FROM ( SELECT 员工姓名, 销售额, ROW_NUMBER() OVER (ORDER BY 销售额 DESC) AS rn FROM 员工表 ) t WHERE rn BETWEEN 11 AND 20;
## 四、RANK():体育竞技的标准排名
## 场景:学生考试成绩排名
`-- 期末考试成绩排名(允许并列,符合真实考试规则)
SELECT
student_name,
score,
RANK() OVER (
PARTITION BY class_id -- 按班级分别排名
ORDER BY score DESC
) AS class_rank,
RANK() OVER (
ORDER BY score DESC -- 全校排名
) AS school_rank
FROM exam_results
WHERE subject = '数学'
ORDER BY class_id, class_rank;
适用场景:运动会名次、考试排名、竞赛排名——凡是"并列后下一名跳过"的场景,用 RANK。
五、DENSE_RANK():电商平台的销量排行榜
场景:商品销量榜,连续显示名次
`-- 各品类TOP10商品(连续排名,不跳号) SELECT * FROM ( SELECT category, product_name, sales_qty, DENSE_RANK() OVER ( PARTITION BY category ORDER BY sales_qty DESC ) AS sales_rank FROM product_sales WHERE stat_date = '2026-04-14' ) t WHERE sales_rank 为什么用 DENSE_RANK 而不是 RANK?
如果第2名有3个商品并列,用 RANK 下一个是第5名,用户看到"第3名""第4名"空了,体验不好。DENSE_RANK 确保显示1、2、3、4……连续名次。
六、综合实战:销售绩效分析
`-- 真实业务场景:销售员月度绩效分析 -- 需求: -- 1. 每个大区内的销售额排名 -- 2. 同分员工并列名次(用RANK) -- 3. 标记每个大区前3名(用于发奖金) -- 4. 为去除重复数据准备唯一序号(用ROW_NUMBER)
WITH sales_ranked AS ( SELECT region, -- 大区 salesman, -- 销售员姓名 sales_amount, -- 销售额 -- 大区内排名(允许并列,跳号) RANK() OVER ( PARTITION BY region ORDER BY sales_amount DESC ) AS region_rank, -- 大区内连续排名(允许并列,不跳号) DENSE_RANK() OVER ( PARTITION BY region ORDER BY sales_amount DESC ) AS region_dense_rank, -- 全公司排名(唯一序号) ROW_NUMBER() OVER ( ORDER BY sales_amount DESC ) AS company_row_num FROM sales_data WHERE month = '2026-04' ) SELECT region, salesman, sales_amount, region_rank, CASE WHEN region_rank = 1 THEN '🥇 第一名' WHEN region_rank = 2 THEN '🥈 第二名' WHEN region_rank = 3 THEN '🥉 第三名' ELSE '普通员工' END AS award_level FROM sales_ranked WHERE region_dense_rank
-
取"前N名"时,业务上要不要包含并列情况决定用哪个
-
"前3名发奖金":用
DENSE_RANK() <= 3,并列第3也发 -
"只取前3条记录":用
ROW_NUMBER() <= 3,强制只取3条
八、速查表
函数并列处理名次跳跃典型场景
ROW_NUMBER()强制唯一无跳跃去重、分页 RANK()相同名次会跳跃体育竞技、考试 DENSE_RANK()相同名次不跳跃电商排行榜、连续名次
总结
三个函数记住一句话:
-
ROW_NUMBER:给每行一个不重复的门牌号
-
RANK:奥运会规则,并列第2后下一个是第4
-
DENSE_RANK:电商榜规则,并列第2后下一个是第3
选哪个,看业务需求:要唯一用ROW_NUMBER,要允许并列看要不要跳号选RANK或DENSE_RANK。
🚢 船长Talk | 数据分析师,专注SQL实战教程。关注公众号「船长Talk」,持续更新数据分析技能干货。