rank()并列跳名(如两个第1名后为第3名),dense_rank()并列不跳名(两个第1名后为第2名),row_number()强制唯一序号;三者在分组内对相同值的排序逻辑不同,直接影响排名变动分析的准确性。

rank()、dense_rank()、row_number() 在分组内排序的区别到底在哪
三者都依赖 PARTITION BY 实现分组,但对相同值的处理逻辑完全不同——这直接决定你统计“排名变动”时会不会漏掉或重复计数。
-
rank():并列跳名,比如两个第1名后直接是第3名,中间空缺;适合强调“名次层级”,但计算变动时需注意空缺导致的位移错位 -
dense_rank():并列不跳名,两个第1名后是第2名;更贴近业务中“梯队划分”的直觉,做同比/环比排名差时数值连续,不易出错 -
row_number():强制唯一序号,相同值也会被随机打散成不同序号;一旦用于排名变动分析,会把本该并列的记录误判为“上升/下降”,必须慎用
示例:按部门分组查员工薪资排名
SELECT dept, name, salary, rank() OVER (PARTITION BY dept ORDER BY salary DESC) AS rnk, dense_rank() OVER (PARTITION BY dept ORDER BY salary DESC) AS drnk, row_number() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees;
如何用窗口函数算“比上期排名升了几名”
核心不是只写一个 rank(),而是要先按时间维度(如月份)和分组维度(如部门+岗位)双层 PARTITION BY,再用 LAG() 拉取上期排名做减法。
- 必须确保数据有明确时间字段(如
report_month),且已按该字段排序,否则LAG()取到的不是上期 - 推荐用
dense_rank()+LAG()组合:避免因并列跳名导致“从第1变第3”算出-2,实际只是新增一人并列第1 - 注意
LAG(rank_col, 1, NULL)的第三个参数设为NULL,让首期变动值为空,而非错误地与默认0相减
实操片段:
SELECT dept, job, report_month, salary,
dense_rank() OVER (PARTITION BY dept, job ORDER BY salary DESC) AS curr_rnk,
LAG(dense_rank() OVER (PARTITION BY dept, job ORDER BY salary DESC))
OVER (PARTITION BY dept, job ORDER BY report_month) AS prev_rnk,
COALESCE(prev_rnk - curr_rnk, NULL) AS rank_change
FROM salary_snapshot;
为什么在 GROUP BY 后再套窗口函数会报错
因为窗口函数执行阶段晚于 GROUP BY 和 HAVING,但早于 ORDER BY 和 LIMIT。如果先 GROUP BY,聚合后的结果集已无原始行粒度,rank() OVER (PARTITION BY ...) 就找不到分组依据的原始字段了。
- 正确顺序:先用子查询或 CTE 把明细数据准备好,再在其上开窗;不要试图在
SELECT中一边GROUP BY一边OVER - 典型报错:
column "xxx" must appear in the GROUP BY clause or be used in an aggregate function,本质是 SQL 引擎发现你在聚合后还想按未聚合字段分组 - 若真需聚合后排名(比如按部门平均薪资排部门名次),应先聚合,再对聚合结果单独开窗,即两层查询嵌套
MySQL 8.0 vs PostgreSQL vs Hive 对动态排名的支持差异
语法看似一致,但边界行为和性能表现差别不小,尤其涉及大数据量分组排名时。
- MySQL 8.0:支持全部三种排名函数,但
PARTITION BY字段若含NULL,会被归为同一组;PostgreSQL 则默认将NULL单独分组,需显式写GROUP BY dept IS NOT NULL, dept才对齐 - Hive(特别是旧版):
dense_rank()在某些小版本存在空值排序 bug,建议用rank()+ 去重逻辑替代;且窗口函数无法下推到 Map 阶段,大表慎用 - 性能提示:所有引擎中,
ORDER BY字段最好有索引(或分区键),否则分组内排序会触发大量磁盘临时文件;PostgreSQL 还可配合ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW限定帧范围加速
真正难的不是写出 rank,而是确认你的“分组”语义是否和业务对齐——比如“同部门同职级”算一组,还是“同部门+近似职级带宽”才算一组,这决定了 PARTITION BY 里要不要加 CASE WHEN 或区间映射。










