【LeetCode数据库】——分数排名(178)

一、题目

编写一个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)

三、参考

猜你喜欢

转载自blog.csdn.net/weixin_37780776/article/details/107901903