在SQL查询场景中,排名是高频需求,比如需要统计班级学生的成绩排名、部门员工的业绩排名等,这类需求可以通过SELECT语句结合窗口函数高效实现,不需要复杂的子查询嵌套。

常用排名窗口函数介绍
SQL中用于排名的窗口函数主要有三个,分别是ROW_NUMBER、RANK、DENSE_RANK,三者的排名逻辑存在差异,适用场景也不同。
ROW_NUMBER函数
该函数会为每一行数据分配一个唯一的连续序号,即使排序字段的值相同,也会分配不同的序号,序号不会重复也不会跳号。
RANK函数
该函数会根据排序字段的值进行排名,相同值的行会获得相同的排名,下一个排名会跳过重复的排名数量,比如两个并列第一,下一个排名就是第三。
DENSE_RANK函数
该函数同样会给相同值的行分配相同排名,但下一个排名不会跳号,比如两个并列第一,下一个排名就是第二。
基础语法结构
SELECT结合窗口函数实现排名的基础语法如下:
-- 基础语法结构
SELECT
查询字段1,
查询字段2,
排名函数() OVER (
PARTITION BY 分组字段 -- 可选,用于按指定字段分组后分别排名
ORDER BY 排序字段 [ASC|DESC] -- 必选,指定排名的排序规则
) AS 排名别名
FROM 表名;
其中PARTITION BY是可选子句,如果不指定则会对全表数据进行统一排名,指定后会按照分组字段拆分数据,在每个分组内单独进行排名。
具体使用示例
假设我们有一张学生成绩表student_score,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT | 学生ID |
| class_id | INT | 班级ID |
| score | INT | 考试成绩 |
我们先插入一些测试数据:
INSERT INTO student_score (id, class_id, score) VALUES (1, 1, 90), (2, 1, 85), (3, 1, 90), (4, 1, 80), (5, 2, 95), (6, 2, 88), (7, 2, 95), (8, 2, 82);
全表统一排名示例
如果需要统计所有学生的成绩排名,不区分班级,使用ROW_NUMBER的实现代码如下:
SELECT
id,
class_id,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num_rank
FROM student_score;
上述查询的结果中,两个90分的学生会分别获得1和2的排名,两个95分的学生会分别获得1和2的排名。
如果使用RANK函数,相同分数的学生排名会一致,下一个排名跳号:
SELECT
id,
class_id,
score,
RANK() OVER (ORDER BY score DESC) AS rank_result
FROM student_score;
此时两个95分的学生排名都是1,下一个88分的学生排名是3,跳过了2。
如果使用DENSE_RANK函数,相同分数排名一致,下一个排名不跳号:
SELECT
id,
class_id,
score,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank_result
FROM student_score;
此时两个95分的学生排名都是1,下一个88分的学生排名是2,没有跳号。
分组后排名示例
如果需要统计每个班级内的学生成绩排名,就需要加上PARTITION BY子句按班级分组:
-- 每个班级内按成绩降序排名,使用DENSE_RANK函数
SELECT
id,
class_id,
score,
DENSE_RANK() OVER (
PARTITION BY class_id
ORDER BY score DESC
) AS class_rank
FROM student_score;
该查询会先按class_id把数据分成1班和2班两组,然后在每个班级内单独按照成绩降序进行排名,两个班级的排名互不影响。
使用注意事项
- 窗口函数只能出现在SELECT子句和ORDER BY子句中,不能用于WHERE、GROUP BY等子句。
- 如果排序字段存在NULL值,NULL值会被排在最后(DESC排序时)或者最前(ASC排序时),不同数据库的实现可能略有差异,使用前可以先测试。
- 排名函数的执行顺序在WHERE、GROUP BY、HAVING之后,在ORDER BY之前,所以如果需要先过滤数据再排名,需要先写过滤条件。
窗口函数的OVER子句中,除了PARTITION BY和ORDER BY,还可以添加ROWS/RANGE子句指定窗口范围,不过排名场景中一般很少用到,按需了解即可。