题目描述
编写一个 SQL 查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
表1:Score (Primary Key: Id)
Id | Salary |
---|---|
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 |
相关知识
-
sql中的变量
-
声明变量:
-
set方式
SET @variable_name = value; SET @ := value;
-
select方式
SELECT @variable_name := value;
-
-
变量赋值:set @value_name = something,可以用set赋值,也可以用select赋值
-
变量使用:@variable_name
-
在常规SQL语句中,使用变量离不开
@
,在存储例程(routine)中无需@
-
实现序号的功能
SELECT score,(@rowno := @rowno + 1) AS rowno FROM SCORE,(SELECT @rowno :=0) r;
-
解题过程
-
初步思考
- 先对分数进行降序排列作为表A
- 对表A进行去重后作为表B
- 从表A获取分数,从表B获取行号
-
编写sql,结果略有差错
-
sql
WITH CTE AS (SELECT Score FROM Scores ORDER BY Score DESC) SELECT s.Score, `Rank` FROM CTE s, (SELECT Score, (@Rank := @Rank+1) AS `Rank` FROM (SELECT DISTINCT Score FROM CTE) rs,(SELECT @Rank :=0) rr) r WHERE s.Score=r.Score
-
my output
{ "headers":[ "Score", "Rank" ], "values":[ [ 4.00, 1.0 ], [ 4.00, 1.0 ], [ 3.85, 2.0 ], [ 3.65, 3.0 ], [ 3.65, 3.0 ], [ 3.50, 4.0 ] ] }
-
standard output
{ "headers":[ "Score", "Rank" ], "values":[ [ 4.00, 1 ], [ 4.00, 1 ], [ 3.85, 2 ], [ 3.65, 3.0 ], [ 3.65, 3.0 ], [ 3.50, 4.0 ] ] }
-
可以看出是因为我的行号有小数位,应该是变量类型的问题
-
-
使用CAST()函数将float转换为int类型,即SIGNED类型
WITH CTE AS (SELECT Score FROM Scores ORDER BY Score DESC) SELECT s.Score, CAST(`Rank` AS SIGNED) AS `Rank` FROM CTE s, (SELECT Score, (@Rank := (@Rank+1)) AS `Rank` FROM (SELECT DISTINCT Score FROM CTE) rs,(SELECT @Rank :=0 ) rr) r WHERE s.Score=r.Score
-
提交,答案正确
学习总结
-
CAST(value AS type)
type 备注 DATE 日期,格式YYYY-MM-DD DATETIME 日期加具体时间,格式YYYY-MM-DD HH:MM:SS TIME 时间,格式HH:MM:SS CHAR 字符型 SIGNED 有符号int型 UNSIGNED 无符号int型 BINARY 二进制型 DECIMAL float类型 -
简单排名(不考虑并列)
-
sql
SELECT s.Score AS Score,(@rank := @rank +1) AS `Rank` FROM (SELECT * FROM Score ORDER BY Score DESC) s,(SELECT @rank :=0 ) r;
-
上面这条语句中的Rank如果不加撇号会导致报语法错误,在Mysql中,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号
-
-
并列排名
-
并列但不占位(1,2,2,3…)
-
在简单排名的基础上,多创建一个变量,用来记录上一个人的分数,然后通过比较来判断名次是否需要增加
-
sql
SELECT Score, CASE WHEN @last_score = Score THEN @rank WHEN @last_score := Score THEN @rank := @rank + 1 END AS `Rank` FROM (SELECT Score From Scores ORDER BY Score DESC) u,(SELECT @rank :=0,@last_score:=NULL) r -- last_score的初值可以不为NULL,只需是可能分数之外的值
-
上面这个sql,当第一个元组的Score不是0.00时可以正常执行,但是当第一个元组的Score是0.00时,会返回NULL,这是因为两个WHEN的条件都不成立
-
my output
{ "headers":[ "Score", "Rank" ], "values":[ [ 0.00, null ] ] }
-
standard output
{ "headers":[ "Score", "Rank" ], "values":[ [ 0.00, 1 ] ] }
-
第一个元组执行CASE时,@last_score为NULL,还未被修改过
-
第一个WHEN:由于无论初始值是什么,它都一定是不会出现在成绩这一列中的值,所以进行逻辑判断后为假,所以这个分支进不去
-
第二个WHEN:将当前元组的Score赋值给@last_score,并用@last_score此时为0.00,进行逻辑判断,相当于假,所以这个分支也进不去
-
所以CASE语句的所有分支均未进入,因此返回值为NULL
-
-
修改sql,来避免第一个是0.00,且还需避免重复的0.00
-
当与上一个人分数一样,进入第一个分支,名次不用增加,直接返回;
-
当与上一个人分数不一样时,将当前元组的分数赋值给@last_score,如果不是0分,逻辑判断为真,名次加一后返回
-
当与上一个人分数不一样且当前元组的成绩为0分时,进入第三个分支,名次加一后返回
-
sql
SELECT Score, CAST( (CASE WHEN @last_score = Score THEN @rank WHEN @last_score := Score THEN @rank := @rank + 1 WHEN CAST(@last_score AS SIGNED) =0 THEN @rank :=@rank+1 END ) AS SIGNED ) AS `Rank` FROM (SELECT Score From Scores ORDER BY Score DESC) u,(SELECT @rank :=0,@last_score:=NULL) r
-
或者使用
OR
运算符拼接第二个和第三个分支,注意OR
的两侧分别要加括号,否则表达式会是@last_score := Score OR Score = 0.00将会被解析为将Score OR Score = 0.00的结果赋值给@last_score,之后用@last_score作为判断条件SELECT Score, CAST( (CASE WHEN @last_score = Score THEN @rank WHEN (@last_score := Score) OR (Score = 0.00) THEN @rank := @rank + 1 END) AS SIGNED ) AS `Rank` FROM (SELECT Score From Scores ORDER BY Score DESC) u,(SELECT @rank :=0,@last_score:=NULL) r
-
-
题解中还有非常精炼的解法
-
sql
SELECT Score,DENSE_RANK() OVER(ORDER BY Score DESC) AS `Rank` FROM Scores
-
-
-
并列且占位(1,2,2,4)
-
根据上面的学习,编写sql
SELECT Score, CAST(`Rank` AS SIGNED) AS `Rank` FROM ( SELECT Score, @rank := @rank+1, @last_rank:= CASE WHEN @last_score =Score THEN @last_rank WHEN (@last_score:= Score) or (Score =0.00) THEN @rank END AS `Rank` FROM (SELECT Score FROM Scores ORDER BY Score DESC) s, (SELECT @rank:=0,@last_score:=NULL,@last_rank:=0) r ) temp
-
-
SQL自带函数实现上述功能
-
ROW_NUMBER()在各个分区内连续排名,不论并列
-
DENSE_RANK()在各个分区排名,并列者同名次,且连续(1,2,2,3)
-
RANK()在各个分区排名,并列者同名次,但不连续(1,2,2,4)
-
语法
SELECT ROW_NUMBER()/DENSE_RANK()/RANK() OVER( PARTITION BY ... ORDER BY ... DESC/ASC .... )
-
如本题可以直接使用系统提供的函数,写法如下
SELECT Score, DENSE_RANK() OVER( ORDER BY Score DESC ) AS `Rank` FROM Scores
-
-