题目
编写一个 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
解题思路
方法一
首先看到这道题,主要需要我们做的时挨个比较每个分数,然后排序,如果有相同的分数,排序不增加,继续下面的排序,想到了 @,:= 符号的作用,设置变量并赋初始值。具体思路如下:
1.有点类似Java的自增,设置一个变量并赋初值,循环一次自增加1,从而实现排序;
2.MySQL是先将数据查询出来并按照需要排序字段按升序 ASC 或者降序 DESC 排序,设置好排序的初始值为0;
3.将已经排序好的数据从第一条依次取出,取一条就自增加1,实现1到最后的排名;
4.当出现相同的排名时,排名不变,则需要再设置一个变量,用来记录上一条数据的值,跟当前的数据进行比较,如果相同则排名不变,不相同则排名加1。
解题逻辑相对复杂,直接上代码吧。
SELECT
s.Score AS Score,
CASE
WHEN @rowtotal = s.Score THEN
@rownum
WHEN @rowtotal := s.Score THEN
@rownum :=@rownum + 1
WHEN @rowtotal = 0 THEN
@rownum :=@rownum + 1
END AS Rank
FROM
(
SELECT
Score
FROM scores
ORDER BY Score DESC
) AS s,
(SELECT @rownum := 0,@rowtotal := NULL) r
方法二
上面方法比较复杂,理解起来也是挺让人头秃的,有一个更好理解的,来,直接上代码:
SELECT
s.Score Score,
(
SELECT COUNT(DISTINCT sc.Score)
FROM scores sc
WHERE sc.Score >= s.Score
) AS `Rank`
FROM scores s
ORDER BY `Rank` ASC
分析SQL
先看里面的一层SQL,
SELECT COUNT(DISTINCT sc.Score)
FROM scores sc
WHERE sc.Score >= s.Score
先是查询所有分数,按照结果顺序依次跟每一个分数比较,计算大于等于该分数的分数,每次比较的结果分为一组,然后对每组的结果分数去重求总数,就是它的排名
| Score |
+-------+
| 4.00 |
| 4.00 |
| 3.85 |
| 3.65 |
| 3.65 |
| 3.50 |
+-------+
最后对排名进行升序排序得出最后结果,这种方法类似Java的双层嵌套循环,遍历数据比较大小求和,理解起来还是很容易的
方法三
上面都是用MySQL解题的,其实使用Sql Server去解决是最简单的,一句SQL就完事儿,上代码:
select Score,dense_rank() over(order by Score DESC
) as Rank from Scores
主要用到的是dense_rank()函数,用于解决这道题再合适不过了,Sql Server还有其他集中排名函数,分别是 ROW_NUMBER(),RANK(),NTILE()
1.ROW_NUMBER() 的用法是先通过 OVER 实现排序,然后按照这个顺序生成排序号,可以用于分页,例如:
select ROW_NUMBER() OVER(order by SubTime desc) as row_num,* from Order
2.RANK() 函数与row_number函数不同的是,rank函数考虑到了over子句中排序字段值相同的情况,如果使用rank函数来生成序号,over子句中排序字段值相同的序号是一样的,后面字段值不相同的序号将跳过相同的排名号排下一个,也就是相关行之前的排名数加一,代码如下:
select RANK() OVER(order by UserId) as rank,* from Order
查询结果如下图:
| rank | userId |
+-------+------+
| 1 | 1 |
| 1 | 1 |
| 3 | 2 |
| 4 | 3 |
| 4 | 3 |
| 6 | 4 |
+-------+------+
拓展
既然已经提到第一种方法了,这里就拓展一下,SQL的几种排名方法,主要讲MySQL数据语言的。
(1)依次递增排名
要用到 @,:= ,给 rownum 赋值,逻辑前面已经提到过,可以参考方法一的第3点,直接上代码:
SELECT
s.Score AS Score,
@rownum := @rownum + 1 AS Rank
FROM
(
SELECT
Score
FROM scores
ORDER BY Score DESC
) AS s,
(SELECT @rownum := 0) r
查询结果如下,
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 2 |
| 3.85 | 3 |
| 3.65 | 4 |
| 3.65 | 5 |
| 3.50 | 6 |
+-------+------+
(2)数据相同,排名相同,排名无间隔
这种排名方法,就是方法一的排名方法,可以直接参考,这里不再赘述
(3)数据相同,排名相同,排名有间隔
先上查询结果,展示下效果,
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 3 |
| 3.65 | 4 |
| 3.65 | 4 |
| 3.50 | 6 |
+-------+------+
这种方法是在在第二种方法的基础,还要多一个变量记录排序的号码 incrnum,直接看代码,
SELECT
obj_new.Score,
obj_new.Rank
FROM
(
SELECT
obj.Score,
@rownum := @rownum + 1 AS num_tmp,
@incrnum := CASE
WHEN @rowtotal = obj.score THEN
@incrnum
WHEN @rowtotal := obj.score THEN
@rownum
END AS Rank
FROM
(
SELECT score
FROM scores
ORDER BY score DESC
) AS obj,
(
SELECT @rownum := 0 ,@rowtotal := NULL ,@incrnum := 0
) r
) AS obj_new
END
以上就是今天的SQL应用题啦,关于SQL排名的测试题,有不对的地方,望指正