窗口函数并列排名是正常行为,由order by表达式值相等决定;rank()跳过并列名次,dense_rank()不跳;并列判定只依赖显式order by字段,与索引或主键无关;where/having中不可直接引用窗口函数结果,需子查询或cte。

窗口函数出现相同排名,不是函数“出错了”,而是你明确告诉它:某些行该并列——只要 ORDER BY 子句里用于排序的表达式值相等,RANK() 和 DENSE_RANK() 就会把它们排在同一位置。
为什么 ORDER BY 相同值就触发并列?
窗口函数的排名逻辑完全由 OVER (ORDER BY ...) 中的排序结果驱动。数据库不看原始表顺序、不看索引、也不管主键,只认 ORDER BY 计算出的排序键是否重复。
-
RANK()和DENSE_RANK()的设计目标就是处理“值相等 → 名次相同”这个业务需求,比如两个 95 分都该是第 1 名 - 只要
ORDER BY score DESC算出来两行 score 都是 95,它们在排序序列里就处于同一层级,函数自然给相同排名 - 这不是 bug,是 SQL 标准定义的行为;
ROW_NUMBER()才是例外(强制唯一)
RANK() 和 DENSE_RANK() 并列后为啥一个跳号一个不跳?
区别不在“怎么判断并列”,而在于“怎么分配下一个名次”。并列判定逻辑完全一致,差异只发生在并列之后的编号策略上。
-
RANK():两个第 1 名 → 下一个是第 3 名(跳过 2);三个第 1 名 → 下一个是第 4 名 -
DENSE_RANK():两个第 1 名 → 下一个是第 2 名;三个第 1 名 → 下一个是第 2 名 - 本质是:RANK() 统计“前面占了多少个名次”,DENSE_RANK() 只统计“前面有多少个不同分数”
为什么加了 id 还是并列?
因为你在 ORDER BY 里没写它。很多人以为建了 (score, id) 索引或表里有主键就能影响排名,但窗口函数只响应显式写出的 ORDER BY 表达式。
- 错误写法:
RANK() OVER (ORDER BY score DESC)→ id 完全不参与排序,哪怕每行 id 都不同,只要 score 相同就并列 - 正确破 ties 写法:
RANK() OVER (ORDER BY score DESC, id ASC)→ 此时 score 相同才用 id 排序,但并列判定仍只看 score - 注意:
id必须非空;若用created_at,得确认无重复且精度够(比如微秒级)
WHERE 里不能直接写 RANK() = 1?
会报错 column "rk" does not exist,因为 SQL 执行顺序中,WHERE 在窗口函数计算之前就执行了,此时 RK 别名根本不存在。
- 必须用子查询或 CTE 包一层:
SELECT * FROM (SELECT *, RANK() OVER (ORDER BY score DESC) AS rk FROM students) t WHERE t.rk = 1;
- 也不能在
HAVING里用,那是聚合函数的上下文,窗口函数不在此阶段生效 - 如果只是想取每个分组 Top 1,更稳的做法是用
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY score DESC, id DESC),再过滤rn = 1
真正容易被忽略的是:并列不是由数据本身决定的,而是由你写的 ORDER BY 表达式决定的。少写一个字段、漏掉 NULLS LAST、或者误以为索引能替代排序逻辑,都会让排名行为变得不可预测。











