符合中国式报表习惯的排名函数是DENSE_RANK(),它对相同分数赋予同一序号且后续序号连续不跳,如95、95、90→1、1、2;而ROW_NUMBER()严格递增,RANK()并列后跳号。

为什么 ROW_NUMBER() 和 RANK() 输出的排名不符合中国式报表习惯
中国式排名(即“并列不跳号”)要求:相同分数的人排同一序号,后续名次紧接其后。比如 95、95、90 → 应该是 1、1、2,而不是 ROW_NUMBER() 的 1、2、3,也不是 RANK() 的 1、1、3(它会跳号)。真正符合需求的是 DENSE_RANK() —— 它对重复值赋予相同序号,且后续序号连续不跳。
常见错误是直接套用 RANK(),结果导出报表时被业务方打回来:“第三名怎么空着?”
-
RANK():并列时同号,但占用后续位置(1,1,3) -
DENSE_RANK():并列时同号,后续紧接(1,1,2)✅ -
ROW_NUMBER():严格按顺序编号,无并列(1,2,3)
用 DENSE_RANK() 配合 OVER 实现并列不跳号排名
关键在 ORDER BY 子句的写法和是否加 PARTITION BY。若只做全表总排名,省略 PARTITION BY 即可;若需分组内排名(如各班级内排名),才加。
SELECT name, score, DENSE_RANK() OVER (ORDER BY score DESC) AS rank_cn FROM students;
注意:ORDER BY score DESC 决定高分在前;若用 ASC,最低分反而是第 1 名。
- 排序字段必须明确,不能写
ORDER BY 2这类位置序号(部分数据库不支持) - 多个排序条件可写成
ORDER BY score DESC, id ASC,避免因 score 相同导致结果不稳定 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 均支持;SQLite 不支持窗口函数
处理并列数据多、需要“同分同序+文字标注”的场景
单纯数字排名有时不够直观,业务常要求加“并列第X名”或“第X名(并列)”。这时不能只靠窗口函数,得配合字符串拼接和条件判断。
SELECT
name,
score,
DENSE_RANK() OVER (ORDER BY score DESC) AS rank_num,
CASE
WHEN COUNT(*) OVER (PARTITION BY score) > 1
THEN CONCAT('第', DENSE_RANK() OVER (ORDER BY score DESC), '名(并列)')
ELSE CONCAT('第', DENSE_RANK() OVER (ORDER BY score DESC), '名')
END AS rank_label
FROM students;
这里用了两次 DENSE_RANK() 是安全的——现代 SQL 引擎会复用计算,不会显著拖慢性能。但要注意:
-
COUNT(*) OVER (PARTITION BY score)统计当前分数出现次数,是判断是否并列的核心 - MySQL 中用
CONCAT(),PostgreSQL 用||或FORMAT(),别混用 - 如果 score 是浮点数,先
ROUND(score, 2)再分组,否则微小精度差异会导致“本该并列却分开排名”
当排序字段含 NULL,OVER 子句默认行为可能翻车
NULL 在 ORDER BY 中的默认位置因数据库而异:PostgreSQL 和 SQL Server 默认把 NULL 排在最前(NULLS FIRST),MySQL 8.0 默认排最后(NULLS LAST)。这会导致含 NULL 的记录意外卡在第 1 名或最后一名,破坏排名逻辑。
显式声明更稳妥:
DENSE_RANK() OVER (ORDER BY score DESC NULLS LAST)
- 想让 NULL 当作最低分参与排名 → 用
NULLS LAST - 想让 NULL 单独排前面且不参与实际名次竞争 → 先过滤或用
CASE WHEN score IS NULL THEN -1 ELSE score END转换 - Oracle 不支持
NULLS FIRST/LAST语法,得用ORDER BY NVL(score, -999999) DESC替代
真正麻烦的不是语法,而是不同环境跑出来排名不一致——上线前务必在目标数据库上实测含 NULL 数据的输出。

















