rank()跳号而dense_rank()不跳,因前者按“段位断层”计算(并列后跳过被占名次,如95,95,90,85→1,1,3,4),后者按“档位连续”计算(值变即+1,同例→1,1,2,3)。

RANK() 和 DENSE_RANK() 不是“选一个就行”,而是必须根据业务语义决定用哪个——并列时要不要跳号,直接决定结果是否符合预期。
为什么 RANK() 会跳号而 DENSE_RANK() 不跳?
两者唯一区别就是对重复值的编号策略:
-
RANK():遇到相同排序值,给相同名次,但后续名次跳过已占用数量。例如分数[95, 95, 90, 85]→ 排名是[1, 1, 3, 4](两个第 1 名,跳过第 2 名) -
DENSE_RANK():同样并列,但后续名次紧接上一档。同例 →[1, 1, 2, 3](两个第 1 名,下一个就是第 2 名)
这不是 bug,是设计。比如“部门薪资前 3 名”若用 RANK(),当出现两个并列第 1 名 + 一个第 3 名时,WHERE rk 会漏掉第 4 名(实际是第 3 名),而 <code>DENSE_RANK() 能稳定覆盖所有“属于前三档”的人。
必须写 ORDER BY,否则报错 Window 'w' lacks an ORDER BY clause
MySQL 8.0 强制要求 RANK() 和 DENSE_RANK() 的 OVER() 中包含 ORDER BY,哪怕你只是想按主键顺序排:
- ❌ 错误:
RANK() OVER (PARTITION BY dept_id)—— 缺ORDER BY,直接报错 - ✅ 正确:
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) - ✅ 也可用主键保序:
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY id)(前提是id能反映插入或业务顺序)
注意:ORDER BY NULL 已被 MySQL 8.0 禁用;如果字段含 NULL,默认按 NULLS FIRST 排,可能全挤在最前,建议提前过滤或用 COALESCE(salary, 0) 控制位置。
分组排名必须加 PARTITION BY,漏了就成全表排名
想查“每个部门薪资前三”,只写 RANK() OVER (ORDER BY salary DESC),结果是把所有人混在一起排——PARTITION BY dept_id 不是可选项,是逻辑前提:
- ✅ 正确结构:
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) - ⚠️ 注意:
PARTITION BY字段为NULL的行会被归入同一组,不是被忽略。若dept_id IS NULL是脏数据,先加WHERE dept_id IS NOT NULL - ⚠️ 性能提示:大表场景下,
(dept_id, salary)建联合索引能显著加速窗口排序
不能写在 WHERE 或 GROUP BY 里,得套子查询或 CTE
窗口函数计算发生在 WHERE 过滤之后、SELECT 投影之前,所以语法上禁止出现在 WHERE、GROUP BY 或 HAVING(除非搭配聚合):
- ❌ 错误:
SELECT * FROM employees WHERE RANK() OVER (...) —— 报语法错误 - ✅ 正确(子查询):
SELECT * FROM ( SELECT *, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk FROM employees ) t WHERE t.rk
- ✅ 更清晰(CTE):
WITH ranked AS ( SELECT *, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk FROM employees ) SELECT * FROM ranked WHERE rk
最后提醒一点:用 DENSE_RANK() 取“前三档”时,返回行数可能远超 3 行(比如第 3 档有 5 人并列),这是正常行为——业务上“前三名”本就不等于“三个人”。











