rank() 和 row_number() 组内排名结果不同:rank() 并列时跳过后续名次,row_number() 强制连续编号;必须用 partition by 实现组内排序,order by 决定组内顺序,且需注意版本支持与索引优化。

为什么 RANK() 和 ROW_NUMBER() 在组内排名时结果不同
组内排名不是简单加个 WHERE 就能解决的,必须用窗口函数配合 PARTITION BY。关键区别在于:当存在相同分数时,RANK() 会跳过后续名次(比如两个第1名后直接是第3名),而 ROW_NUMBER() 强制连续编号(两个第1名后是第2名)。业务上“并列不占位”选 RANK(),“严格顺序编号”选 ROW_NUMBER()。
常见错误是漏写 PARTITION BY,导致全表排序而非组内排序;或者误把排序字段放在 PARTITION BY 里,造成每个分组只有一行。
-
PARTITION BY后跟的是分组字段,比如department、category -
ORDER BY在OVER子句内,决定组内排序依据,比如score DESC - MySQL 8.0+ 才支持窗口函数,执行前先确认版本:
SELECT VERSION();
按部门分组、分数降序排名的完整写法
假设有一张员工表 employees,含字段 id、name、department、score,要给每个部门内按分数从高到低排 RANK():
SELECT id, name, department, score, RANK() OVER (PARTITION BY department ORDER BY score DESC) AS dept_rank FROM employees;
注意:如果某部门有两人同为 95 分,他们都会得到 dept_rank = 1,下一名得 dept_rank = 3(跳过 2)。
- 若要避免跳号,改用
DENSE_RANK(),同分者仍并列,但后续名次紧接(95、95、90 → 1、1、2) -
ORDER BY中可加多字段,比如score DESC, id ASC解决分数相同时的稳定排序 - 不能在
WHERE或GROUP BY中直接引用窗口函数别名dept_rank,需套一层子查询或 CTE
WHERE 条件过滤后还能不能做组内排名
可以,但必须把过滤逻辑放在窗口函数计算之前,否则会丢失分组完整性。例如“只看 score ≥ 60 的员工,并在其所在部门中排名”——不能写成 WHERE score >= 60 后再 PARTITION BY department,因为可能某个部门所有 score
正确做法是先过滤,再开窗:
SELECT id, name, department, score, RANK() OVER (PARTITION BY department ORDER BY score DESC) AS rank_above_60 FROM employees WHERE score >= 60;
这里每个部门内部的排名,只基于该部门中满足 score >= 60 的人计算,不会掺杂被过滤掉的记录。
- 不要试图在
OVER子句里写WHERE—— 语法不支持 - 如果需要“部门总人数”和“部门及格人数”两个维度,建议用 CTE 分开算,避免逻辑耦合
- 大表慎用
RANK()+ 多字段ORDER BY,可能触发临时表或文件排序,注意EXPLAIN中的Using filesort
性能差、卡顿?检查这三个地方
窗口函数本身不索引友好,尤其带 PARTITION BY 和复杂 ORDER BY 时,容易成为慢查询源头。
- 确保
PARTITION BY字段和ORDER BY字段上有联合索引,例如(department, score)(注意顺序) - 避免在
ORDER BY中使用函数或表达式,如ORDER BY ABS(score),会导致索引失效 - 如果只是取每组 Top N,比整个结果集都算排名更高效:用
ROW_NUMBER()+ 外层WHERE rn ,且务必让 MySQL 能利用索引完成排序
真正难的不是写出语法,而是理解 PARTITION BY 划分的是逻辑分组边界,而这个边界一旦受 WHERE 影响,组内基数就变了——这点很容易被忽略。











