推荐使用 dense_rank(),因其在并列时连续编号(如2、2、3),适用于“并列也算名额”的场景;row_number() 强制唯一编号,rank() 并列后跳号;三者均需配合 partition by 和 order by 使用,且须通过子查询过滤。

用 RANK()、DENSE_RANK() 还是 ROW_NUMBER()?
三者都常用来做分组 Top N,但行为差异直接影响结果是否符合业务预期。比如用户按订单金额取每城市前 3 名,若同一城市有两人并列第 2 名:ROW_NUMBER() 会强行拆成 2 和 3,跳过真正的第 3 名;RANK() 给两人都是 2,下一个排 4;DENSE_RANK() 则是 2、2、3。多数场景下推荐 DENSE_RANK(),尤其当“并列也算名额”时。
实操建议:
- 要严格限制返回行数(比如必须只取 3 行),用
ROW_NUMBER() - 允许并列且不跳号(如榜单排名),选
DENSE_RANK() - 允许并列但跳号(如考试名次“第1、第2、第2、第4”),用
RANK() - 所有函数都需配合
PARTITION BY分组和ORDER BY排序,缺一不可
写法:子查询 + 窗口函数过滤是最稳的路径
直接在 WHERE 中用窗口函数会报错(如 WHERE ROW_NUMBER() OVER (...) ),因为窗口函数不能出现在 WHERE 阶段。必须先计算排名,再在外层筛选。
示例(MySQL 8.0+ / PostgreSQL / SQL Server):
SELECT city, name, amount
FROM (
SELECT city, name, amount,
DENSE_RANK() OVER (PARTITION BY city ORDER BY amount DESC) AS rk
FROM orders
) t
WHERE rk
<p>注意点:</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/ai/1264" title="ProfilePicture.AI"><img
src="https://img.php.cn/upload/ai_manual/001/431/639/68b6db81bde05595.png" alt="ProfilePicture.AI" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/ai/1264" title="ProfilePicture.AI" class="overflowclass">ProfilePicture.AI</a>
<p class="overflowclass">ProfilePicture.AI是一款用于制作个人资料头像和 AI 写真头像的在线工具。</p>
</div>
<a rel="nofollow" href="/ai/1264" title="ProfilePicture.AI" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 别忘了给子查询起别名(如
t),否则会报Every derived table must have its own alias -
PARTITION BY city是分组依据,ORDER BY amount DESC决定谁排前面 - 如果排序字段有 NULL,默认排最前(MySQL)或最后(PostgreSQL),必要时加
NULLS LAST或IS NULL处理
性能坑:大表慎用,索引能救一半命
窗口函数本身不走索引,PARTITION BY + ORDER BY 字段组合如果没有联合索引,执行计划里很可能出现 Using filesort 或全表扫描。100 万行以上表,没索引可能跑几十秒。
优化建议:
- 为
(city, amount)建联合索引(顺序不能反:分组字段在前,排序字段在后) - 避免在窗口函数中用表达式排序,比如
ORDER BY ABS(amount)会让索引失效 - PostgreSQL 可考虑物化 CTE 缓存中间结果;MySQL 8.0+ 的
CTE默认不物化,别指望它提升性能 - 如果只是查 Top 1,有时
GROUP BY + MAX()或关联子查询反而更快
兼容性问题:低版本 MySQL 怎么办?
MySQL 5.7 及更早版本不支持窗口函数,硬上会报 ERROR 1064: You have an error in your SQL syntax。这时候得绕路。
替代方案(适用于中小数据量):
- 用自连接计数:对每个
city,统计有多少同组记录的amount大于当前行,小于等于 2 就是 Top 3 - 用变量模拟排名(但不推荐:变量执行顺序不稳定,易出错)
- 应用层分页:查出全部,用 Python/Java 分组后取前 N —— 数据量不大时最省事
- 升级到 MySQL 8.0 是根治办法,窗口函数语法统一、语义清晰、维护成本低
真正麻烦的是跨数据库迁移时——同一份 SQL 在 PostgreSQL 跑得好好的,扔进旧版 MySQL 就崩,得提前检查目标环境版本。










