文章目录
一、题目
编写一个SQL查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
例如,根据上述给定的Scores
表,你的查询应该返回(按分数从高到低排列):
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
重要提示:对于 MySQL 解决方案,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号。例如`Rank`
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rank-scores
二、解答
1. 创建示例表
mysql> use db4_leetcode;
Database changed
mysql> Create table If Not Exists Scores (Id int, Score DECIMAL(3,2));
Query OK, 0 rows affected (0.02 sec)
mysql> Truncate table Scores;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into Scores (Id, Score) values ('1', '3.5');
Query OK, 1 row affected (0.00 sec)
mysql> insert into Scores (Id, Score) values ('2', '3.65');
Query OK, 1 row affected (0.01 sec)
mysql> insert into Scores (Id, Score) values ('3', '4.0');
Query OK, 1 row affected (0.01 sec)
mysql> insert into Scores (Id, Score) values ('4', '3.85');
Query OK, 1 row affected (0.00 sec)
mysql> insert into Scores (Id, Score) values ('5', '4.0');
Query OK, 1 row affected (0.00 sec)
mysql> insert into Scores (Id, Score) values ('6', '3.65');
Query OK, 1 row affected (0.00 sec)
2. 编写查询语句
a. 解法一:使用窗口函数
由于此题要求实现排名输出,且要求分数一样时排名连续,因此最简单的方式就是使用MySQL 8.0以后引入的窗口函数dense_rank()
。
(1) dense_rank()
函数简介
dense_rank()
函数可以为一个查询分区或结果集中的每一行分配一个排名(rank),且排名之间无间隔(这即dense的含义)。
dense_rank()
函数的一般语法为:
DENSE_RANK() OVER (
PARTITION BY <expression>[{,<expression>...}]
ORDER BY <expression> [ASC|DESC], [{,<expression>...}]
)
其中:
- 首先,
PARTITION BY
语句将由FROM
语句产生的结果集分为不同的分区,然后dense_rank()
函数应用于每一个分区; - 其次,
ORDER BY
语句指明每一个分区中每一列的排序。
(2) dense_rank()
使用示例
现在假设通过下列语句先建了一个sales
数据表:
CREATE TABLE sales(
sales_employee VARCHAR(50) NOT NULL,
fiscal_year INT NOT NULL,
sale DECIMAL(14, 2) NOT NULL,
PRIMARY KEY(sales_employee, fiscal_year)
);
然后使用下列数据插入语句向sales
数据表中插入示例数据:
INSERT INTO sales(sales_employee,fiscal_year,sale)
VALUES('Bob',2016,100),
('Bob',2017,150),
('Bob',2018,200),
('Alice',2016,150),
('Alice',2017,100),
('Alice',2018,200),
('John',2016,200),
('John',2017,150),
('John',2018,250);
上述数据表中内容如下:
mysql> select * from sales;
+----------------+-------------+--------+
| sales_employee | fiscal_year | sale |
+----------------+-------------+--------+
| Alice | 2016 | 150.00 |
| Alice | 2017 | 100.00 |
| Alice | 2018 | 200.00 |
| Bob | 2016 | 100.00 |
| Bob | 2017 | 150.00 |
| Bob | 2018 | 200.00 |
| John | 2016 | 200.00 |
| John | 2017 | 150.00 |
| John | 2018 | 250.00 |
+----------------+-------------+--------+
9 rows in set (0.00 sec)
那么,如果希望了解每一财年中各个销售员的排名情况,且要求销售额相同排名一样,不同销售员之间排名连续,则可以使用dense_rank()
函数来实现:
mysql> SELECT
-> sales_employee,
-> fiscal_year,
-> sale,
-> DENSE_RANK() OVER (PARTITION BY fiscal_year ORDER BY sale DESC ) AS sales_rank
-> FROM
-> sales;
+----------------+-------------+--------+------------+
| sales_employee | fiscal_year | sale | sales_rank |
+----------------+-------------+--------+------------+
| John | 2016 | 200.00 | 1 |
| Alice | 2016 | 150.00 | 2 |
| Bob | 2016 | 100.00 | 3 |
| Bob | 2017 | 150.00 | 1 |
| John | 2017 | 150.00 | 1 |
| Alice | 2017 | 100.00 | 2 |
| John | 2018 | 250.00 | 1 |
| Alice | 2018 | 200.00 | 2 |
| Bob | 2018 | 200.00 | 2 |
+----------------+-------------+--------+------------+
9 rows in set (0.00 sec)
(3) 使用dense_rank()
解题
基于上述对于dense_rank()函数的学习,下面使用该函数解答本题就相当容易了:
mysql> SELECT
-> Score,
-> dense_rank() over ( ORDER BY Score DESC ) AS 'Rank'
-> FROM
-> Scores;
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
6 rows in set (0.00 sec)
b. 解法二:自定义变量
SELECT
Score,
`Rank`
FROM
(
SELECT
Score,
@curRank :=
IF
( @prevRank = Score, @curRank + 0, @curRank := @curRank + 1 ) AS `Rank`,
@prevRank := Score
FROM
( SELECT * FROM Scores ORDER BY Score DESC ) ordered_scores,
( SELECT @curRank := 0, @prevRank := NULL ) init
) s;
为便于理解上述代码,请观察下列语句的输出,下列是上述语句的一个子查询:
mysql> SELECT
-> Score,
-> @curRank := IF ( @prevRank = Score, @curRank + 0, @curRank := @curRank + 1 ),
-> @prevRank := Score
-> FROM
-> ( SELECT * FROM SCORES ORDER BY Score DESC ) ordered_scores,
-> ( SELECT @curRank := 0, @prevRank := NULL ) init;
+-------+------------------------------------------------------------------------------+--------------------+
| Score | @curRank := IF ( @prevRank = Score, @curRank + 0, @curRank := @curRank + 1 ) | @prevRank := Score |
+-------+------------------------------------------------------------------------------+--------------------+
| 4.00 | 1 | 4.00 |
| 4.00 | 1 | 4.00 |
| 3.85 | 2 | 3.85 |
| 3.65 | 3 | 3.65 |
| 3.65 | 3 | 3.65 |
| 3.50 | 4 | 3.50 |
+-------+------------------------------------------------------------------------------+--------------------+
6 rows in set, 5 warnings (0.00 sec)