SQL 窗口函数:一次讲清 row_number 和 rank 的区别
三个排序函数,名字很像,结果差很多。取「每组最新一条」时选错,会静默丢数据。
核心区别
| 函数 | 遇到并列时 | 结果示例 |
|---|---|---|
row_number() | 强制不并列,顺序由引擎决定 | 1, 2, 3, 4 |
rank() | 并列同名次,后续名次跳号 | 1, 1, 3, 4 |
dense_rank() | 并列同名次,后续名次不跳号 | 1, 1, 2, 3 |
最容易出事的场景
需求:每个用户取最近一次下单记录。
-- ❌ 有风险:同一秒下两单时,rank 会给两行都打上 1,过滤后行数变多
SELECT * FROM (
SELECT *, RANK() OVER (PARTITION BY uid ORDER BY created_at DESC) AS rn
FROM orders
) t WHERE rn = 1;
-- ✅ 稳定:无论是否并列,每组只出一行
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY uid ORDER BY created_at DESC, id DESC) AS rn
FROM orders
) t WHERE rn = 1;
注意第二个写法里加上了 id DESC 作为第二排序键。这不只是为了消除并列,
更是为了让结果可复现 —— 不加的话,同一份数据两次执行可能给出不同的行。
什么时候该用 rank
当你需要「并列就都是第一」的语义时。比如排行榜:
-- 取销售额前 3 名,并列的都算进来
SELECT * FROM (
SELECT name, amount,
RANK() OVER (ORDER BY amount DESC) AS rk
FROM sales
) t WHERE rk <= 3;
一个性能细节
窗口函数在 PARTITION BY 加 ORDER BY 之后需要排序。
如果表很大,先把无关列筛掉再开窗,比先开窗再筛快得多:
-- 好:先缩小数据集
WITH recent AS (
SELECT uid, id, created_at FROM orders WHERE created_at >= '2026-10-01'
)
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY uid ORDER BY created_at DESC, id DESC) rn
FROM recent
) t WHERE rn = 1;
← 返回首页