dense_rank()与rank()结果不同,根本原因是:rank()遇相同值跳过后续名次(如1,1,3,4),dense_rank()不跳(如1,1,2,3);前者反映实际竞争排位,后者表示连续档位层级。

为什么DENSE_RANK()和RANK()返回的数字不一样
根本原因就一条:遇到相同排序值时,RANK()跳过后续名次,DENSE_RANK()不跳。这不是bug,是设计意图——前者表达“实际排位顺序”,后者表达“档位层级”。比如分数为 95, 95, 90, 85(降序),结果分别是:
-
RANK()→1, 1, 3, 4(两个第1名,没人第2名,所以下一个是第3名) -
DENSE_RANK()→1, 1, 2, 3(两个第1名,紧接就是第2名)
别被“排名”这个词带偏:业务上你要的是“第几名”还是“第几档”,决定了该选谁。发奖金名单常要连续档位,用 DENSE_RANK();淘汰制晋级名额必须卡死人数,RANK() 更贴切。
RANK()跳号不是计算错误,是语义正确
很多人看到 RANK() 返回 1, 1, 3, 4 就觉得“漏了2”,其实是误解了它的定义:RANK() 的结果 = “有多少人分数严格高于你” + 1。两个95分的人,都满足“0人高于我”,所以都是第1名;而90分的人,有2人高于他,所以是第3名——这个逻辑完全自洽。
- 它不保证序号连续,但保证“名次反映竞争关系”
-
DENSE_RANK()则是“当前分数在去重后的分数列表中排第几”,所以天然连续 - 两者对 NULL 值行为一致:默认按
NULLS FIRST处理,若不想让空值全挤在最前,得提前WHERE score IS NOT NULL或用COALESCE(score, -1)
ORDER BY 缺失会直接报错,不是返回空或乱序
MySQL 8.0 强制要求 RANK() 和 DENSE_RANK() 的 OVER() 子句中必须包含 ORDER BY,否则立刻报错:Window 'w' lacks an ORDER BY clause。这不是警告,是语法硬性限制。
- 哪怕你只想按插入顺序编号,也得写
ORDER BY id或ORDER BY created_at,不能省略 -
ORDER BY NULL在 MySQL 8.0 已被禁用,别试 - 如果排序字段有重复值(如多人同分),建议补唯一字段保序,例如
ORDER BY score DESC, id ASC,否则并发环境下结果可能非确定
PARTITION BY 漏写会导致全表误排,不是性能问题而是逻辑错误
想查“每个部门薪资前三”,却只写 RANK() OVER (ORDER BY salary DESC),结果是所有人混在一起排——这不是慢,是错得离谱。因为没 PARTITION BY dept_id,MySQL 把整张表当做一个窗口处理。
-
PARTITION BY定义“谁跟谁比”,不是可选项,是分组排名的必要条件 - 分区字段值为
NULL会被归入同一组,不是被忽略;若部门ID为空,这些员工会一起参与一个虚拟“NULL部门”的排名 - 想同时出“公司总榜 + 各部门子榜”,必须用两个独立窗口函数,不能靠一个
OVER子句兼顾
真正容易被忽略的点是:窗口函数只能出现在 SELECT 列表或 HAVING(配合聚合时),绝不能写进 WHERE。想筛“部门内前3”,必须套子查询或 CTE,否则语法直接报错。











