在MySQL 8.0的数据库开发中,计算业务数据的排名是高频需求,比如学生成绩排名、商品销量排名等场景都需要用到排名计算。传统MySQL版本中只能通过嵌套查询实现这类需求,而MySQL 8.0引入的窗口函数让排名计算有了更优的选择。

使用嵌套查询计算排名的实现方式
嵌套查询计算排名的核心逻辑是,对于每一条待排名的记录,统计表中比该记录排序字段值更高(或更低)的记录数量,这个数量加1就是当前记录的排名。我们以学生成绩表为例,先创建测试表并插入测试数据。
-- 创建学生成绩表
CREATE TABLE student_score (
id INT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(50) NOT NULL,
score INT NOT NULL
);
-- 插入测试数据
INSERT INTO student_score (student_name, score) VALUES
('张三', 90),
('李四', 85),
('王五', 90),
('赵六', 80),
('钱七', 95);
计算普通排名(允许并列,并列后跳号)
这种排名规则下,相同分数的学生排名相同,下一个排名会跳过并列占用的位置,比如两个90分都是第1名,那么85分就是第3名。实现代码如下:
SELECT
s1.student_name,
s1.score,
(SELECT COUNT(DISTINCT s2.score) FROM student_score s2 WHERE s2.score >= s1.score) AS rank_num
FROM student_score s1
ORDER BY s1.score DESC;
上述查询中,子查询会统计所有分数大于等于当前记录分数的不同分数值数量,这个数量就是当前记录的排名。执行结果如下:
| student_name | score | rank_num |
|---|---|---|
| 钱七 | 95 | 1 |
| 张三 | 90 | 2 |
| 王五 | 90 | 2 |
| 李四 | 85 | 4 |
| 赵六 | 80 | 5 |
计算连续排名(允许并列,并列后不跳号)
如果希望相同分数排名相同,但下一个排名连续不跳号,比如两个90分都是第1名,85分就是第2名,需要调整子查询的统计逻辑,改为统计大于当前分数的记录数量加1:
SELECT
s1.student_name,
s1.score,
(SELECT COUNT(s2.score) FROM student_score s2 WHERE s2.score > s1.score) + 1 AS rank_num
FROM student_score s1
ORDER BY s1.score DESC;
嵌套查询计算排名的局限性
虽然嵌套查询可以实现排名计算,但存在明显的不足:
- 性能较差:对于每一条主查询的记录,都需要执行一次子查询,当表数据量较大时,会产生大量的重复查询,执行效率很低。
- 逻辑复杂:不同的排名规则需要编写不同的子查询逻辑,代码可读性和可维护性较差,新手不容易理解。
- 功能有限:如果需要计算分组排名,嵌套查询的逻辑会更加复杂,需要额外添加分组条件,进一步降低性能。
使用窗口函数替代嵌套查询计算排名
MySQL 8.0新增的窗口函数专门用于解决这类排序、排名、分组聚合的场景,语法更简洁,性能也更好。常用的排名相关窗口函数有三个:
RANK():允许并列排名,并列后跳号,和第一个嵌套查询的普通排名规则一致。DENSE_RANK():允许并列排名,并列后不跳号,和第二个嵌套查询的连续排名规则一致。ROW_NUMBER():不考虑并列,每条记录都有唯一连续的排名,即使分数相同排名也不同。
窗口函数实现排名的代码示例
还是使用上面的学生成绩表,分别用三个窗口函数计算排名:
-- 使用RANK()计算排名
SELECT
student_name,
score,
RANK() OVER (ORDER BY score DESC) AS rank_num
FROM student_score;
-- 使用DENSE_RANK()计算排名
SELECT
student_name,
score,
DENSE_RANK() OVER (ORDER BY score DESC) AS rank_num
FROM student_score;
-- 使用ROW_NUMBER()计算排名
SELECT
student_name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS rank_num
FROM student_score;
三个查询的执行结果分别和之前的嵌套查询对应,其中RANK()的结果和第一个嵌套查询完全一致,DENSE_RANK()和第二个嵌套查询完全一致。窗口函数的语法中,OVER子句用来定义窗口的规则,ORDER BY指定排序的字段和方向。
窗口函数计算分组排名
如果需要按班级分组计算每个学生在本班的排名,只需要在OVER子句中添加PARTITION BY指定分组字段即可,嵌套查询实现这个功能会复杂很多:
-- 先添加班级字段并更新数据
ALTER TABLE student_score ADD class VARCHAR(20);
UPDATE student_score SET class = '一班' WHERE id IN (1,2,3);
UPDATE student_score SET class = '二班' WHERE id IN (4,5);
-- 按班级分组计算排名
SELECT
student_name,
class,
score,
RANK() OVER (PARTITION BY class ORDER BY score DESC) AS class_rank
FROM student_score;
嵌套查询与窗口函数的对比
| 对比维度 | 嵌套查询 | 窗口函数 |
|---|---|---|
| 语法复杂度 | 高,不同规则需要不同逻辑 | 低,统一函数调用,规则清晰 |
| 执行性能 | 差,数据量大时效率低 | 好,内置优化,执行效率高 |
| 功能丰富度 | 低,复杂场景实现困难 | 高,支持分组、滑动窗口等复杂场景 |
| 可维护性 | 差,代码可读性低 | 好,逻辑清晰易理解 |
总结
在MySQL 8.0中,虽然嵌套查询仍然可以实现排名计算,但窗口函数在性能、语法简洁度、功能丰富度上都有明显优势,是更优的选择。如果项目使用的是MySQL 8.0及以上版本,建议优先使用窗口函数实现排名计算;如果是更低的MySQL版本不支持窗口函数,再考虑使用嵌套查询实现。开发者可以根据实际的业务场景和数据库版本选择最合适的实现方式。