DENSE_RANK() 是 MySQL 8.0+ 中实现并列不跳号排名的唯一标准方案,按 ORDER BY 扫描时遇新值加 1、重复值复用名次,如 [95,95,88,82] 得 1,1,2,3,且必须配合 OVER(ORDER BY) 使用,不可在 WHERE 中引用其别名。

直接说结论:DENSE_RANK() 是 MySQL 8.0+ 中解决「并列不跳号」排名问题的唯一标准方案,它让两个并列第1名之后紧接第2名,而不是第3名。
为什么 DENSE_RANK() 能做到名次连续不跳
它的计算逻辑是:按 ORDER BY 顺序扫描,每遇到一个**新值**就加 1,重复值复用刚分配的名次。比如分数为 [95, 95, 88, 82],结果就是 1, 1, 2, 3 —— 不像 RANK() 那样因并列“占位”而跳掉第 2 名。
- 它不关心“前面有多少行”,只关心“前面有多少个更优的**不同值**”
- 因此天然满足业务对“第几名”的直觉:并列即同名次,后续名次必须紧连
- 但注意:它不会合并行,
score = 88的那条记录的dense_rank就是 2,独立存在
DENSE_RANK() 必须搭配 OVER(ORDER BY),且不能在 WHERE 中直接引用
常见错误是写成这样:
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS drk FROM students WHERE drk <= 3; -- ❌ 报错:Unknown column 'drk' in 'where clause'
原因:窗口函数执行在 WHERE 过滤之后,别名此时不可见。
- 正确做法只有两种:用子查询包一层,或用 CTE
- 分组排名时,
PARTITION BY必须显式写出,漏掉就会全表误排 - 排序字段含 NULL 时,MySQL 8.0 默认按
NULLS FIRST处理(即DESC下排最前),若要排除,优先用WHERE score IS NOT NULL过滤
实战中容易被忽略的边界行为
比如查“每个部门薪资前 2 名”,写成:
WITH ranked AS (
SELECT dept, name, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM employees
)
SELECT * FROM ranked WHERE rk <= 2;这本身没错,但要注意:
- 如果某部门有 3 人并列第 1 名,结果会返回 3 行(
rk = 1),不是 2 行 —— 这是预期行为,“前 2 名”本就可能超过 2 条记录 - 若业务硬性要求“最多取 2 行/部门”,就不能依赖
DENSE_RANK(),得换ROW_NUMBER()或加额外限制(如按入职时间保序后取前 2) -
PARTITION BY字段若含 NULL,MySQL 会把所有 NULL 值归为同一组,若该字段本应非空,务必先WHERE dept IS NOT NULL
真正难处理的从来不是语法,而是你是否意识到:排名是否需要跨组连续、NULL 是否参与、以及“前 N 名”到底是指名次 ≤ N,还是行数 ≤ N —— 这些才是决定用不用 DENSE_RANK() 的关键。


















