DENSE_RANK比RANK更适合连续排名,因为DENSE_RANK在并列时后续不跳号(如[95,95,90]→[1,1,2]),而RANK会跳号(→[1,1,3]),能准确表达“第1名、第2名、第2名、第3名”这类连续档位语义。

为什么DENSE_RANK比RANK更适合连续排名
DENSE_RANK不会跳过重复值后的序号,比如成绩[95, 95, 90]用DENSE_RANK()得到[1, 1, 2],而RANK()会输出[1, 1, 3]——后者中间空了2。你要的是“并列不跳号”,就得选DENSE_RANK(),不是靠后期补数或窗口函数嵌套硬凑。
基本语法和PARTITION BY的常见误用
直接写DENSE_RANK() OVER (ORDER BY score DESC)就能按分数降序排;但一加PARTITION BY就容易出错——它不是分组后各自从1开始排那么简单。比如按department分区,同一部门内独立排名,但跨部门序号不累计、也不重叠,这是设计使然,不是bug。
容易踩的坑:
- 把
PARTITION BY当成“先分组再全局排序”,实际是“每组内独立计算DENSE_RANK” - 在
ORDER BY里混用ASC/DESC导致预期外的升序排列(默认ASC,别忘了写DESC) - 对NULL值没处理:多数数据库把NULL当作最小值(
ORDER BY col DESC时排最后),但SQLite等可能不同,建议显式写ORDER BY col DESC NULLS LAST(如果支持)
和ROW_NUMBER、RANK对比的实际影响
三者区别直接影响业务逻辑:
-
ROW_NUMBER():严格递增,哪怕值相同也给不同序号,适合做唯一行号 -
RANK():并列则同号,但后续跳号,适合“前N名含并列”的榜单(如奥运奖牌榜) -
DENSE_RANK():并列同号,后续不跳号,适合“第1名、第2名、第2名、第3名”这种连续语义场景
性能上三者几乎无差别,都是窗口函数,执行计划一致;但语义错用会导致前端分页错乱或统计口径偏差——比如用RANK算“Top 3”可能只返回2条记录(因跳号),而DENSE_RANK能稳定取到前三档数据。
MySQL 8.0+ 和旧版本的兼容写法
MySQL 8.0起原生支持DENSE_RANK(),但5.7及更早版本不支持窗口函数。此时只能模拟:
一种可行思路是用变量+子查询,但要注意变量顺序依赖执行计划,不稳定;更稳妥的做法是用自连接计数:
SELECT t1.name, t1.score,
(SELECT COUNT(DISTINCT t2.score)
FROM scores t2
WHERE t2.score >= t1.score) AS dense_rank
FROM scores t1
ORDER BY t1.score DESC;
这个写法在任意版本MySQL、PostgreSQL甚至SQL Server都可用,但大数据量时性能明显下降——因为每行都要扫一次表。真要兼顾兼容性和性能,得评估是否值得升级或引入应用层排序。
真正容易被忽略的是:即使语法跑通,也要确认目标数据库对NULL、字符排序、时区字段的ORDER BY行为是否一致——这些细节会让DENSE_RANK结果在不同环境出现微小偏移。

















