rank()跳号、dense_rank()不跳号是设计差异而非bug,核心在于并列时是否留空档:rank()按段分配名次并跳过后续序号,dense_rank()按不同值个数连续编号;二者不可在where中使用,因窗口函数属select阶段,晚于where执行。

RANK()跳号,DENSE_RANK()不跳号是设计使然,不是bug
两者唯一区别就是并列时是否“留空档”:RANK()把相同值看作一个段,段内共享名次,下一段名次 = 当前名次 + 段长度;DENSE_RANK()只关心“这是第几个不同值”,值一变就+1,不管前面并列几行。
比如分数 [100, 100, 95, 90]:
-
RANK()返回[1, 1, 3, 4]—— 两个100占第1段,95是第2段但被命名为3(跳过2) -
DENSE_RANK()返回[1, 1, 2, 3]—— 值变了两次,所以有3个档位
这不是计算错误,而是语义差异:RANK()强调“断层感”,DENSE_RANK()强调“档位连续”。业务说“拿金牌的两人并列第一,银牌从第三名开始发”,那就是RANK();如果说“我们只设金、银、铜三档,95分必须是第二档”,就得用DENSE_RANK()。
WHERE里直接写RANK()或DENSE_RANK()一定报错
窗口函数在SQL执行顺序中属于SELECT阶段,而WHERE早于SELECT运行。所以以下写法必然失败:
SELECT * FROM sales WHERE RANK() OVER (ORDER BY amount DESC) <p>报错信息通常是 <code>Invalid use of window function</code> 或字段不存在。</p><p>正确做法只有两种:</p>
- 套一层子查询:
SELECT * FROM (SELECT *, RANK() OVER (ORDER BY amount DESC) AS rnk FROM sales) t WHERE rnk - 用CTE:
WITH ranked AS (SELECT *, RANK() OVER (...) AS rnk FROM sales) SELECT * FROM ranked WHERE rnk
漏掉这层嵌套是新手最高频的坑,没有例外。
PARTITION BY漏写或写错会导致业务逻辑彻底歪掉
想按部门分别排名,却忘了写 PARTITION BY dept_id,结果变成全表统一排序——所有部门的员工挤在一起排,A部门第1名可能比B部门第10名工资还低。
还有几个关键细节:
-
PARTITION BY字段必须出现在子查询的SELECT列表中,否则外部无法对齐分组(多数数据库会报错或静默忽略) -
PARTITION BY字段值为NULL会被归为同一组,不是被过滤掉 - 如果数据量大,
PARTITION BY字段最好建索引,否则MySQL可能无法利用索引加速排序
选RANK()还是DENSE_RANK()取决于业务是否允许“空档”
用 RANK() 查 “Top 10” 可能只返回8行:因为第3、4名并列,直接跳到第6名,凑不够10个不同排名值;
用 DENSE_RANK() 做 “前2档” 可能返回20行:只要档位≤2,不管每档多少人(比如18人并列第2名)。
真正容易被忽略的点是:跳不跳号本身不决定对错,而是由业务是否允许“空档”来驱动。奥运奖牌榜要体现“双金之后银牌是第三名”,就必须用RANK();薪资体系要求“A/B/C三档必须全覆盖”,就得用DENSE_RANK()。
MySQL 5.7及更早版本根本不支持这两个函数,执行会直接报错 ERROR 1064 (42000) —— 如果还在用老版本,得先升级或改用变量模拟,但性能和可维护性差很多。











