编写一个 SQL查询来实现分数排名。如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
创建表和数据:
Create table If Not Exists Scores (Id int,Score DECIMAL(3,2)); Truncate table Scores; insert into Scores (Id, Score) values ('1','3.5'); insert into Scores (Id, Score) values ('2','3.65'); insert into Scores (Id, Score) values ('3','4.0'); insert into Scores (Id, Score) values ('4','3.85'); insert into Scores (Id, Score) values ('5','4.0'); insert into Scores (Id, Score) values ('6','3.65');
解法:
1.按Id和Score分组。每组中,大于等于每个Score的不同Score数目就是其排名。
select S1.Score,count(distinct S2.Score) as Rank from Scores as S1 join Scores as S2 on (S1.Score <= S2.Score) group by S1.Id,S1.Score order by S1.Score desc;
优化:
先将表中不同的score全部取出来,以子查询的方式完成。再与表连接,求 “大于等于每个Score的不同Score数目” 。依然要分组。
SELECT s1.Score, COUNT(s2.score) AS `Rank` FROM Scores s1 JOIN ( SELECT DISTINCT Score FROM Scores ORDER BY Score DESC ) AS s2 ON (s1.Score <= s2.Score) GROUP BY s1.Id,s1.Score ORDER BY s1.Score desc
注意:FROM子句中关系的属性都可以在HAVING和SELECT子句中用聚集运算,但是只有出现在GROUP BY子句中的属性,才能以不聚集的方式出现在HAVING和SELECT子句中。
因此,上面的S1.ID可以不出现在SELECT子句中,S1.SCORE可以出现在SELECT子句中。其它字段不能以非聚集的方式出现在SELECT子句中。比如,S2.Score。
2.将“大于等于每个Score的不同Score数目”在子查询中实现,并不再分组。
select S1.Score, (select count(distinct S2.Score) from Scores as S2 where S1.Score <= S2.Score ) as Rank from Scores as S1 order by S1.Score desc;
3.辅助变量
select score, @ran := @ran + (@pre <> (@pre := Score)) as rank from scores,(select @ran := 0, @pre := -1) order by score desc;
-
@ran 类似在 Oracle 中的rownum,可以在生成结果内附加上一列序列号,可以近似理解为查询后加上行号;
-
@ran := @ran + 1 实际上是赋值,旧值+1变为新值赋给@a;
-
实际上并没有直接 @ran+1 那么简单,还要先去判断分数是否与前一行相同,所以引入 @pre 来记录:
- 先将Score赋值给: @pre:= Score;
- 然后判断之前的 @pre 是否与赋值后的@pre 不相同 ==> (@pre <> (@pre := Score)) “<>” 就是 "!=" 的意思;
- 两者不同,判断结果为真,则取1; 两者相同,判断结果为假,取0, 最后再用 0或1 加上@ran 即为当前行分数的排名;
-
(select @ran:= 0, @pre := -1) t 为初始化 @ran 和 @pre 的开始值;
- @pre 初始值为 -1 为的是防止Score有 0 分的出现;
- @pre第一次比较Score值的时候,@pre 初始值肯定和Score不同, 所以第一次比较 @ran 必然会 +1,所以@ran从0开始;
-
最后以将结果根据Score进行倒序展示;