RANK()返回1,1,3,4而DENSE_RANK()返回1,1,2,3,是因为RANK()按“段位断层”设计:并列值共享名次,下一段名次=当前名次+并列行数;DENSE_RANK()按“档位连续”设计:值变则名次+1,不跳号。

为什么RANK()返回1,1,3,4而DENSE_RANK()返回1,1,2,3
RANK()跳号、DENSE_RANK()不跳号,是语义设计差异,不是计算错误。RANK()把相同值看作一个“段”,段内共享名次,下一段名次 = 当前段名次 + 段长度;DENSE_RANK()只关心“这是第几个不同值”,值变就+1,不管前面并列几行。
常见错误现象:RANK()结果里缺了第2名,业务方以为漏数据——其实是95分属于第二段,但这一段被命名为3,反映的是“断层感”;DENSE_RANK()结果连续,但可能让WHERE dense_rank 返回远超2行(比如10人并列第2名)。
- 分数
[100, 100, 95, 90]→RANK()是[1, 1, 3, 4],DENSE_RANK()是[1, 1, 2, 3] - 两者语法完全一致:
RANK() OVER (ORDER BY score DESC)vsDENSE_RANK() OVER (ORDER BY score DESC) - 性能无差别:底层都是排序后单次扫描,不因“跳”或“不跳”增加开销
WHERE里直接用RANK()或DENSE_RANK()会报错
窗口函数在SELECT阶段才计算,而WHERE执行在之前。所以WHERE RANK() OVER (...) 一定失败,报错通常是<code>Invalid use of window function或字段不存在。
正确做法必须套一层子查询或CTE:
SELECT * FROM ( SELECT *, RANK() OVER (ORDER BY amount DESC) AS rnk FROM sales ) t WHERE rnk <= 3;
- 漏掉这层嵌套是新手最高频的坑
- 如果还用了
PARTITION BY dept,却漏写PARTITION BY,会导致全表统一排序,业务逻辑彻底歪掉 -
PARTITION BY字段必须出现在子查询的SELECT列表中,否则外部无法对齐分组(多数数据库会报错或静默忽略)
什么时候该选RANK(),什么时候必须用DENSE_RANK()
RANK()适合强调“跨越了多少个不同梯队”,比如奥运奖牌榜(双金牌后银牌叫第三名);DENSE_RANK()适合需要稳定档位数量的场景,比如销售额分A/B/C三档——必须有且仅有三档,不能缺也不能多。
- 用
RANK()查“Top 10”可能只返回8行:因为第3、4名并列,跳到第6名,凑不够10个不同排名值 - 用
DENSE_RANK()做“前2档”可能返回20行:只要档位≤2,不管每档多少人 - 想取“分数最高的前N档人”,就不能用
RANK()的数值做WHERE过滤,得换思路
MySQL 5.7及更早版本根本不支持这两个函数
在MySQL 5.7上执行含OVER的语句,会直接报错:ERROR 1064 (42000): You have an error in your SQL syntax。这不是配置问题,是语法层级不兼容。
MySQL 8.0+、PostgreSQL、SQL Server、Oracle支持;SQLite和旧版MySQL不支持窗口函数。
- 若必须兼容旧版,只能用变量模拟(如
@rank := IF(@prev = score, @rank, @rank + 1)),但需严格初始化+强制ORDER BY,否则结果不可靠 - 别指望加括号或改写就能跑通,这是引擎级限制
真正容易被忽略的点是:跳不跳号本身不决定函数好坏,只取决于你是否在意“档位连续性”——业务要的是“第几档”,还是“跨过了几个门槛”。

















