rank()是解决并列排名的标准解法,同分同名且跳号(如95分→2,90分→4);join仅适用于无窗口函数的旧版本兜底,性能差且易错。

用 RANK() 替代 JOIN 是更直接的解法
想在排行榜里让相同分数的人并列(比如两个 95 分都排第 2 名,跳过第 3 名),JOIN 不是首选方案——它本质是关联表,不是排序/排名工具。硬用 JOIN 模拟并列排名会绕远路、难维护、易出错。真正该用的是窗口函数:RANK()。
它天然支持“同值同名次,名次跳跃”逻辑,且一行代码就能搞定:
SELECT name, score, RANK() OVER (ORDER BY score DESC) AS rank_num FROM players;
结果中两个 95 分都会得到 2,下一个 90 分就是 4(跳过 3)。
RANK() 和 DENSE_RANK() 的区别必须分清
“并列”有两种常见需求,选错函数会导致名次不符合预期:
-
RANK():同分同名,名次跳跃(95→2,95→2,90→4) -
DENSE_RANK():同分同名,名次不跳跃(95→1,95→1,90→2)
多数排行榜(如游戏、竞赛)要求“并列后跳位”,所以默认优先试 RANK();若需连续名次(比如只显示前 10 名,不允许空缺),才换 DENSE_RANK()。
注意:ROW_NUMBER() 是严格递增的,同分也会强分先后,不适合并列场景。
如果非要用 JOIN 模拟(比如旧版 MySQL 5.7 不支持窗口函数)
这时得靠自连接 + 子查询数“有多少人分数更高”,再加 1 得名次。但代价明显:
- 性能差:对每行都要扫描全表比对
- 写法冗长,容易漏
DISTINCT或写错>/>= - 无法处理 NULL 分数(需额外
WHERE score IS NOT NULL)
示例(仅限不得已时参考):
SELECT a.name, a.score, (SELECT COUNT(DISTINCT b.score) FROM players b WHERE b.score > a.score) + 1 AS rank_num FROM players a ORDER BY a.score DESC;
关键点:COUNT(DISTINCT b.score) 确保同分只算一次;>(不是 >=)才能让当前分进入“+1”的位置。
ORDER BY 中的 NULLS LAST 或排序方向影响并列一致性
如果数据里有 NULL 分数,不同数据库默认把 NULL 排最前或最后,会导致并列逻辑错乱。例如:
- PostgreSQL 默认
NULLS FIRST,ORDER BY score DESC会让NULL排第一,被误算成“最高分” - MySQL 8+ 和 SQL Server 支持显式写
NULLS LAST
稳妥做法是提前过滤或统一处理:
SELECT name, score, RANK() OVER (ORDER BY score DESC NULLS LAST) AS rank_num FROM players WHERE score IS NOT NULL;
不加 NULLS LAST 且没 WHERE 过滤,在部分数据库下可能让所有 NULL 都挤在名次最前,还占着 1、2……
窗口函数是标准解法,JOIN 模拟只是兼容性兜底。真正上线前,务必用真实数据跑一遍边界 case:最高分并列、最低分并列、全同分、含 NULL —— 这些地方最容易漏判。











