优先选 dense_rank(),因其不跳过重复名次,更符合“销量段位”语义;需按月排名时必须用 partition by year_month;处理并列需多字段排序并以 sku_id 兜底;取 topn 保并列应选 dense_rank() 而非 row_number()。

rank() 和 dense_rank() 在销量排序中怎么选?
rank() 会跳过重复名次后的数字,比如两个商品并列第1,下一个就是第3;dense_rank() 则不跳,两个第1后直接是第2。实际看销量榜单时,多数业务更关心“第几档”,而不是“第几个位置”,所以用 dense_rank() 更贴近语义。
- 如果要生成「销量段位」(如S/A/B/C级),用
dense_rank()配合ntile(4)更稳 - 如果做竞对对标,需要知道“比自己高多少个独立档位”,
rank()可能导致误判——比如某SKU销量卡在两个大爆款中间,rank()给它第100名,但其实前面只有5个真实对手 - MySQL 8.0+、PostgreSQL、StarRocks、Doris 都支持这两个函数;Hive 中需确认版本是否启用窗口函数(
set hive.cbo.enable=true可能影响优化)
按月分区计算销量累计排名要注意什么?
窗口函数默认不自动按时间切片,ORDER BY sale_amount DESC 是全局排序。想看“每个自然月内各商品的当月销量排名”,必须显式加 PARTITION BY year_month。
- 错误写法:
rank() OVER (ORDER BY sale_amount DESC)→ 所有月份混在一起排 - 正确写法:
rank() OVER (PARTITION BY year_month ORDER BY sale_amount DESC) -
year_month字段建议用DATE_FORMAT(sale_time, '%Y-%m')或to_char(sale_time, 'YYYY-MM')统一格式,避免字符串隐式转换导致分区失效 - 如果原始表没预计算
year_month,直接在PARTITION BY里写表达式(如PARTITION BY DATE_TRUNC('month', sale_time))在某些引擎里会拖慢性能,先物化更稳妥
销量相同但要按上架时间再排序,ORDER BY 怎么写?
窗口函数的 ORDER BY 支持多字段,但要注意 NULL 和稳定性:如果两个商品销量都是 1200,又都没填 onboard_date,它们就会被当成完全等价,rank() 给相同名次,且后续排序可能每次执行结果不一致(无稳定排序锚点)。
- 推荐写法:
ORDER BY sale_amount DESC, onboard_date ASC, sku_id ASC -
sku_id ASC是兜底:确保每行都有唯一排序依据,避免非确定性 - 不要用
ORDER BY sale_amount DESC, RAND()来“打散”并列——这会让结果不可复现,不利于排查和下游比对 - PostgreSQL 中可加
NULLS LAST显式控制空值位置:onboard_date ASC NULLS LAST
用 row_number() 做 TopN 筛选为什么常出错?
row_number() 生成唯一序号,适合取“每个类目销量前3的商品”,但容易漏掉并列情况——比如手机类目有5个SKU销量都是1000,row_number() 强行编为1/2/3/4/5,只留前3就丢了两个真实头部商品。
- 要保全并列,改用
dense_rank() OVER (PARTITION BY category ORDER BY sale_amount DESC),再WHERE rn - 如果业务明确要求“只取3个,不管并列”,才用
row_number();但得在注释里写清这个策略,否则后续接手的人可能误以为是 bug - 大表慎用
row_number()+ 全局排序:没有PARTITION BY时,整个数据集进一个 reducer,容易 OOM(尤其 Hive/Spark SQL)
窗口函数本身不难,难的是把业务规则准确翻译成 PARTITION BY、ORDER BY 和函数选择的组合。同一个“销量排名”,不同场景下该用哪个函数、要不要加兜底字段、分区键来源是否可靠——这些细节一旦错,结果就偏了,而且不容易被肉眼发现。










