RANK()跳号、DENSE_RANK()不跳号是设计意图而非缺陷:前者按“段位断层”计算(并列共享名次,下一段名次=当前名次+并列行数),后者按“档位连续”计算(值变则名次+1)。

RANK() 跳号、DENSE_RANK() 不跳号,不是实现缺陷,而是设计意图的直接体现——前者强调“段位断层”,后者强调“档位连续”。
为什么 RANK() 会跳过名次?
RANK() 的计算逻辑是:当前名次 + 并列行数 = 下一名次。它把并列看作一个“排名段”,段内共享名次,段与段之间留出空档,以体现实际差距。
常见错误现象:RANK() 返回 1, 1, 3, 4,有人误以为第 3 行“丢了名次”或“结果错乱”——其实它准确表达了:前两人同属第一段,第三名属于第二段(中间缺了“第二段”的编号,因为没人单独占这一档)。
- 比如分数
[100, 100, 95, 90]→RANK()结果是[1, 1, 3, 4](两个第一之后,下一个不同分值直接叫“第三名”) - 如果用
WHERE rank <= 3想取“前三档”,会漏掉score = 90那行(它的 rank 是 4),但它确实是第四高分 - 这种行为在 MySQL 8.0+、PostgreSQL、SQL Server 中完全一致;MySQL 5.7 及更早版本不支持,会报
ERROR 1064 (42000)
为什么 DENSE_RANK() 名次连续?
DENSE_RANK() 的逻辑更简单:只要值变化,名次就 +1,不管前面并列了几行。它不关心“段有多宽”,只关心“这是第几档”。
使用场景明确:DENSE_RANK() 适合做固定档位划分,比如“销售额分 A/B/C 三档”,必须有且仅有三档,不能缺也不能多。
- 同样分数
[100, 100, 95, 90]→DENSE_RANK()结果是[1, 1, 2, 3] - 但要注意:它不会合并行,
score = 100的两人仍各自输出,dense_rank值都是 1 - 如果误用
WHERE dense_rank <= 2查 Top 2,可能返回 10 行(比如 8 人并列第 2 名),远超预期
RANK() 和 DENSE_RANK() 都不能在 WHERE 中直接引用
窗口函数是在 SELECT 阶段才计算的,而 WHERE 执行在 SELECT 之前。所以以下写法会报错:
SELECT * FROM sales WHERE RANK() OVER (ORDER BY amount DESC) <= 3;
正确做法必须用子查询或 CTE:
SELECT * FROM ( SELECT *, RANK() OVER (ORDER BY amount DESC) AS rnk FROM sales ) t WHERE rnk <= 3;
- 漏掉这层嵌套是新手最常踩的坑,报错信息通常是
Invalid use of window function或字段不存在 - 如果还用了
PARTITION BY,比如按部门排名,却漏写PARTITION BY dept,会导致全校/全表统一排序,业务语义彻底歪掉
真正容易被忽略的点是:跳不跳号本身不决定对错,而是由业务是否允许“空档”来驱动。比如“奥运奖牌榜”用 RANK()(并列金牌后直接银牌,不设“第2名”),而“内部绩效 ABC 分档”必须用 DENSE_RANK()(A/B/C 三档缺一不可)。选错函数,不是语法问题,是语义错配。

















