排名统计是数据分析中非常常见的需求,比如给学生成绩排名、给销售业绩排序、给商品销量做榜单。SQLite从3.25版本开始支持窗口函数,其中RANK()和DENSE_RANK()是两个最容易混淆的函数。它们都能处理并列名次的问题,但处理方式完全不同:一个会跳号,一个不跳号。如果选错了函数,排行榜的名次就会出现间断或者重复,直接影响业务结果的正确性。本文结合具体SQL示例,把这两个函数的区别讲清楚。

一、先看两个函数的基本语法和核心区别
窗口函数的标准语法是在函数名后面跟上OVER子句,用来定义分区和排序规则。SQLite中写法如下:
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank_num,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank_num
FROM students;假设表中有这样一组数据:张三95分、李四90分、王五90分、赵六85分。执行上面的查询,结果会是:张三的RANK是1、DENSE_RANK也是1;李四和王五并列,RANK都是2、DENSE_RANK也都是2;到了赵六这里区别就出来了,他的RANK是4,而DENSE_RANK是3。
这就是两者的核心区别:RANK在遇到并列名次时会跳过被占用的名次。李四和王五占了第2名和第3名两个位置,所以下一个人直接从4开始,名次是不连续的,但总人数和最高名次是对得上的。而DENSE_RANK是密集排名,并列的人只占用一个名次,不管多少人并列第二,下一个人的名次永远是3,名次序列连续没有空洞,但最大名次数可能小于总记录数。
可以这样记:RANK像体育比赛的并列规则,两人并列亚军就没有季军;DENSE_RANK像档次划分,并列的人属于同一档,下一档紧跟着编号。
二、为什么会有这种差异:窗口函数的计算逻辑
理解差异的根源,需要知道这两个函数在OVER子句定义的窗口内是怎么计数的。RANK的计算规则是:当前行的排名等于窗口内排在它前面的行数加1。李四和王五的分数都是90,按分数降序排,排在90分前面的只有张三一行,所以两人RANK都是2。赵六前面有三行(张三、李四、王五),所以RANK是4。并列的行共享同一个排名,但它们各自都占了一个位置。
DENSE_RANK的计算规则则是:当前行的排名等于排在它前面的不同取值个数加1。在赵六之前,分数只有95和90两个不同值,所以DENSE_RANK是3。并列的行不管有多少人,都被压缩成一个取值来计数。
顺便提一下第三个常被一起讨论的函数ROW_NUMBER(),它最简单,就是给窗口内的行顺序编号,即使分数相同也会得到1、2、3、4这样互不相同的序号,完全没有并列的概念。三个函数放在一起看,处理并列的方式分别是:ROW_NUMBER不承认并列、RANK承认并列但跳号、DENSE_RANK承认并列且不跳号。下面的示例把三者对比得很直观:
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY score DESC) AS drnk
FROM students
ORDER BY score DESC;查询结果中,两名90分的学生row_num分别是2和3,rnk都是2,drnk也都是2,而85分的赵六三种结果分别是4、4、3,一眼就能看出差别。
三、结合PARTITION BY实现分组内排名
实际业务中很少只做全表排名,更多是分组内排名,比如每个班级内部按成绩排名、每个月内部按销量排名。这时需要在OVER子句中加上PARTITION BY,它会把数据按指定字段切分成多个分区,每个分区独立计算排名,互不干扰。
SELECT
class,
name,
score,
RANK() OVER (PARTITION BY class ORDER BY score DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS drnk
FROM students;这条语句会对每个班级分别计算RANK和DENSE_RANK,每个班的排名都从1开始。PARTITION BY不影响两个函数本身的差异,只是把计算范围缩小到了分组内部。需要注意的是,最终输出结果的顺序不一定按排名排,如果希望结果按班级和名次展示,最好在外面再套一层ORDER BY,或者直接调整OVER子句里的排序字段。
还有一个常见用法是配合子查询筛选前几名。比如取每个班级的前三名,如果用ROW_NUMBER,并列第三的学生只会保留一个,可能不符合预期;如果用DENSE_RANK筛选WHERE drnk <= 3,并列的学生会全部保留,甚至可能超过三个人。用RANK筛选则可能出现结果不足三人的情况。这个细节在取Top N时特别容易踩坑,选择哪个函数完全取决于业务上对并列名次的处理要求。
四、如何根据业务场景选择正确的函数
选择的关键问题是:并列名次之后,下一个名次应该是几。如果业务要求名次能反映真实的竞争位置,比如竞赛评奖、只取固定名额的选拔,用RANK更合适。例如比赛只取前三名发奖,两人并列第二时,按照RANK规则第三名就空缺了,这符合大多数竞赛的评奖逻辑,用WHERE rnk <= 3筛选正好取到该拿奖的人。
如果业务要求名次连续、用来表示等级或档次,比如绩效评级分档、会员等级划分、销售提成档位,用DENSE_RANK更合适。举个例子,提成规则是第一名拿一档提成、第二名拿二档提成,如果用RANK,两人并列第一后下一个直接是第三名,二档提成永远没人拿,这时候DENSE_RANK才能保证档位连续覆盖。
还有一些场景需要注意细节。比如统计某个名次上有多少人,用RANK配合计数会很方便,因为名次值和总人数的关系是确定的;而在分页展示排行榜时,DENSE_RANK的名次连续性对前端展示更友好,不会出现用户翻页时看到名次从2直接跳到5的困惑。如果只是需要给结果集编个号做标识,比如配合LIMIT做随机抽样编号,那用ROW_NUMBER就够了,没必要引入排名语义。
最后提醒一点,SQLite对窗口函数的支持从3.25.0版本才开始,如果在使用旧版本的SQLite(比如某些老版本的移动端嵌入式环境),这些函数会直接报语法错误,升级SQLite版本是唯一的解决办法。写SQL时建议像上面的例子一样,给排名列起清晰的别名,并把两个函数放在一起对比输出结果,确认名次逻辑符合业务预期后再定型,这样能避免大部分排名统计的错误。
SQLite窗口函数RANK函数DENSE_RANK修改时间:2026-09-15 13:52:37