rank() 更适合爆款榜单,因其对相同销量商品赋予并列名次并跳过后续名次(如三款同为第2名,则下一名为第5名),符合电商“销量相同则排名并列”的业务需求;row_number() 强制连续编号,dense_rank() 虽不跳名次但易致“第2名后是第3名”引发误解。

为什么 RANK() 比 ROW_NUMBER() 更适合爆款榜单
电商榜单要体现“销量相同则名次并列”,比如三款商品都卖了 1200 件,它们必须同为第 2 名,后面跳到第 5 名——这正是 RANK() 的行为;而 ROW_NUMBER() 会强行分出 2、3、4,违背业务认知。
关键区别在于:当排序字段值相同时,RANK() 跳过后续名次,DENSE_RANK() 则不跳(如 1,2,2,3)。爆款榜单通常需要跳过(避免出现“第 2 名之后是第 3 名”造成误解),所以优先选 RANK()。
- 适用场景:按日/周销量汇总后生成 Top 50 榜单、后台运营看板、导出 Excel 排行表
- 注意:MySQL 8.0+、PostgreSQL、SQL Server、Oracle 均支持;MySQL 5.7 及更早版本不支持窗口函数,需用变量模拟(不推荐)
- 性能影响:对百万级商品表,
RANK() OVER (ORDER BY sales DESC)在sales字段有索引时基本无压力
怎么写一个带并列排名的爆款查询语句
假设你有一张 product_sales 表,含 product_id、product_name、sales_count(本周销量)字段,目标是取销量前 20 的并列排名。
SELECT product_id, product_name, sales_count, RANK() OVER (ORDER BY sales_count DESC) AS rank_num FROM product_sales ORDER BY rank_num, product_id LIMIT 20;
说明:
-
RANK() OVER (ORDER BY sales_count DESC)是核心,按销量降序排,自动处理并列 -
ORDER BY rank_num, product_id保证相同名次下商品顺序稳定(否则可能每次查询结果不同) - 如果想限制“只显示 rank_num ≤ 20 的记录”,不能只靠
LIMIT 20——因为并列可能导致实际返回 23 行。稳妥做法是用子查询或 CTE 过滤:WHERE rank_num
遇到 NULL 销量或重复商品 ID 怎么办
真实数据常有异常:某商品销量为 NULL,或同一 product_id 出现在多条记录中(比如分渠道统计未聚合)。
-
NULL默认排在最前(DESC时),但爆款榜单不该包含它。加WHERE sales_count IS NOT NULL预过滤 - 重复
product_id会导致同一商品多次上榜,必须先聚合:GROUP BY product_id, product_name并用SUM(sales_count) - 若需兼容历史脏数据,可在
OVER子句里加ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW——但没必要,聚合比窗口内修正常规得多
和前端分页、缓存配合时的坑
榜单接口常被前端轮询或 CDN 缓存,而 RANK() 结果依赖实时数据。容易忽略的关键点:
- 缓存键不能只含“page=1”,得带上时间维度(如
top20_week_2024W23),否则周一缓存的榜,周五还在用 - 分页时别用
LIMIT 20 OFFSET 20拼第 2 页——并列导致第 21 行可能是 rank_num = 3,实际漏掉 rank_num = 2 的剩余商品。应改用“游标分页”:记录上一页最后的(rank_num, sales_count, product_id)作为下一页起点 - 如果用 Redis 缓存榜单,存的是带
rank_num的完整结果集,而不是原始 SQL;更新销量后要主动清缓存,不能等 TTL
并列排名看着简单,真正卡住人的往往不是函数怎么写,而是数据聚合是否干净、缓存策略是否匹配名次跳跃特性、以及前端敢不敢接受“第 2 名有 5 个商品”这种非线性结果。










