SQL排名问题,100% leetcode答案大公开!
(首先原谅我最近新番看多了,起了一个中二的名字)
最近在找实习,所以打算系统总结(复习)一下sql中经常遇到问题。不管是刷leetcode还是牛客的sql题,有一个问题总是绕不开的,那就是排名问题。其实对于MySql8.0以上版本来说,排名问题已经很容易解决了。因为MySql8.0之后开始支持三个窗口函数,分别是rank()
,dense_rank()
以及row_number()
。这三个窗口函数对应了排名问题中最常见的三种情况。而对于之前的版本,则需要模拟这几个函数。
网上也有很多相关的文章,但实际上他们给出的代码都无法100%通过leetcode的样例。于是就想在这里重新总结一下sql中的排名问题。我们以分数排名为例,假设有一个数据表scores包括两个字段id和score,需要对score进行排名。
id | score |
---|---|
1 | 0 |
2 | 15 |
3 | 15 |
4 | 15 |
5 | 17 |
6 | 17 |
7 | 20 |
不同的排名需求适合不同的场景,但为了方便比较,就不分来举例了。对于排名的结果,一般会包括以下几种情况:
1. 同样的分数不同的名次,且排名连续
如果是这样的排名需求,排名的结果应该是:
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 5 |
4 | 15 | 6 |
1 | 0 | 7 |
窗口函数 row_number()
SELECT id, score, row_number() over (order by score desc) as 'rank'
FROM Scores;
模拟窗口函数
SET @curRank = 0;
SELECT id, Score, (@currank := @currank + 1) As 'rank'
From Scores
ORDER BY score DESC;
自行模拟窗口函数的关键就在于要设置一个变量来保存当前的排名。
2. 同样的分数相同的名次,且排名连续(Leetcode 178)
如果是这样的排名需求,排名的结果应该是:
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 4 |
4 | 15 | 4 |
1 | 0 | 5 |
使用窗口函数 row_number()
SELECT id, score, dense_rank() over (order by score desc) as 'rank'
FROM Scores;
使用变量模拟窗口函数
对于这种排名,网上很多这种做法给出的代码其实是这样的:
SELECT tmp.score,
@ranking := case
when @lastscore = tmp.score then @ranking
when @lastscore := tmp.score then @ranking +1
end as 'rank'
FROM (select * from scores order by score desc) tmp,
(select @ranking := 0, @lastscore := null) r;
这短代码可能在一般的数据表中都没问题,但在leetcode上是不能完全ac的。主要有两个问题:
- 返回的rank排名是字符串,而不是数字,会导致样例失败
- 如果表中score有值为0,排名结果就会是null。
这种做法的思想是:
- 如果当前的score跟上一行score(@lastscore)相等,则@ranking不增加(@ranking := @ranking);
- 否则就 @lastscore := tmp.score (这一句在score不为0的时候永远为真),给 @lastscore 赋新值的同时,@ranking增加1(@ranking := @ranking + 1)。
但是当score为0时,两个when中的表达式都是False,所以什么都不执行,导致0值的结果为null。
针对这种情况,可以稍微改进一下:
SELECT tmp.score,
@ranking := case
when @lastscore = tmp.score then @ranking +0
when @lastscore := tmp.score then @ranking +1
else @ranking := @ranking + 1
end as 'rank'
FROM (select * from scores order by score desc) tmp,
(select @ranking := 0, @lastscore := null) r;
- 第一点是在第一个when处增加了 +0 ,进行类型转换
- 第二点是增加了 else @ranking := @ranking + 1 ,保证出现0值的时候,仍然可以进行排名。
这样的写法是可以通过leetcode的所有样例的。
使用联结模拟窗口函数
SET @currank := 0;
Select os.id, r.Score, r.rank
From Scores os
LEFT JOIN (SELECT *, (@currank := @currank + 1) As 'rank'
From (Select *
From Scores
Group BY Score
ORDER BY score DESC
) AS ra
) AS r
ON os.score = r.score
ORDER BY r.score DESC;
这种查询其实可以看做两步,第一步是使用Group BY分组,进行了一个无重复值的排名(其实就是第一种情况),之后再把这个排名表Left Join到原始的表中,然后对组合表在进行一次排名。
3. 同样的分数相同的名次,且排名不连续
这种情况应该是最符合现实中分数排名的。排名结果如下:
id | score | rank |
---|---|---|
7 | 20 | 1 |
6 | 18 | 2 |
5 | 17 | 3 |
2 | 15 | 4 |
3 | 15 | 4 |
4 | 15 | 4 |
1 | 0 | 7 |
使用窗口函数rank()
SELECT id, score, rank() over (order by score desc) as 'rank'
FROM Scores;
使用变量模拟窗口函数
SELECT temp.id, tmp2.score, tmp2.ranking AS 'rank'
FROM (SELECT
tmp.*,
@rownum := @rownum+1 AS rownum,
@ranking := case
when @lastscore = tmp.score then @ranking + 0
when @lastscore := tmp.score then @rownum + 0
else @rownum + 0
end as ranking
FROM
(select * from scores order by score desc) tmp,
(select @rownum := 0, @lastscore := null, @ranking := 0) r) AS tmp2
这里的做法其实相当于第一种情况和第二种情况的结合,用两个变量分别存储排名和当前的行数。
- 如果当前的score跟上一行score(@lastscore)相等,则@ranking不增加(@ranking := @ranking +0 );
- 否则就 @lastscore := tmp.score ,这里跟第二种情况不同在于@ranking不再是增加1,而是赋值为行数(@rownum)。
以上就是遇到比较多的排名问题的解法啦。关于窗口函数,其实还有更多的用途,比如分组排名。后面可能专门写一篇来介绍这几个排名窗口函数。
最后惯例:
能力有限,如有错漏,多多包涵,欢迎指正!
SQL排名问题,100% leetcode答案大公开!的更多相关文章
- 【转】GitHub 排名前 100 的安卓、iOS项目简介
GitHub Android Libraries Top 100 简介 排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果, 然后过滤了跟 Android 不 ...
- 2016年GitHub 排名前 100 的安卓、iOS项目简介(收藏)
排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果, 然后过滤了跟 Android 不相关的项目, 所以排名并不具备任何官方效力, 仅供参考学习, 方便初学者 ...
- GitHub上排名前100的Android开源库介绍(来自github)
本项目主要对目前 GitHub 上排名前 100 的 Android 开源库进行简单的介绍,至于排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果,然后过滤了 ...
- sql 经典面试题及答案(选课表)
SQL数据库面试题以及答案 Student(Sno,Sname,Sage,Ssex) 学生表 Sno:学号:Sname:学生姓名:Sage:学生年龄:Ssex:学生性别Course(Cno ...
- 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍
GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...
- GitHub上排名前100的Android开源库介绍
GitHub上排名前100的Android开源库介绍 文章来源: http://www.open-open.com/news/view/1587067#6734290-qzone-1-31660-bf ...
- GitHub 上排名前 100 的 Objective-C 项目简介
主要对当前 GitHub 排名前 100 的项目做一个简单的简介, 方便初学者快速了解到当前 Objective-C 在 GitHub 的情况. 项目名称 项目信息 1. AFNetworking ...
- GitHub上排名前100的iOS开源库介绍(来自github)
主要对当前 GitHub 排名前 100 的项目做一个简单的简介,方便初学者快速了解到当前 Objective-C 在 GitHub 的情况. 若有任何疑问可通过微博@李锦发联系我 项目名称 项目信息 ...
- 使用PL/SQL删除百万条记录的大表
使用PL/SQL删除百万条记录的大表: 最近开发人员对测试环境数据库进行了压力测试,数据库中产生了大量的脏数据.有几张大表里数据量均在百万甚至千万条的记录数.开发人员现提出需求如下: 只清理其中的部分 ...
随机推荐
- window.ShadyCSS
window.ShadyCSS Web Components # install $ yarn add @webcomponents/shadycss@1.7.1 # OR $ npm i @webc ...
- brew & apply2files bug
brew & apply2files bug Error: Permission denied @ apply2files - /usr/local/lib/node_modules/npm/ ...
- js trigger click event & dispatchEvent & svg element
js trigger click event & dispatchEvent & svg element but svg element not support trigger cli ...
- USDN代币发行 关于USDN代币
"稳定币"是数字货币的一种,但与主流币存在的差异是,它可以通过锚定法币和加密资产等手段来维持币价的相对稳定.提及稳定币,一般会先介绍三种模式: 法币托管模式.数字资产抵押模式和无抵 ...
- NGK治理机制研究
治理机制是区块链项目的重要设计.随着项目的运行,生态中的参与者需要根据实际运行情况对项目进行必要的更新和升级,以使项目持续良性发展.治理机制的作用是使不同参与者最终达成共识.治理机制直接决定这个网络生 ...
- Renice INC:法国葡萄酒为什么独占世界鳌头?
提起葡萄酒,许多人首先想到的就是法国.法国有着悠久的酿酒历史和精湛工艺,"82年的拉菲"几乎成了大众认识葡萄酒的代名词.市面上的进口葡萄酒琳琅满目,原产国众多,意大利.西班牙.美国 ...
- C++算法代码——求数列[coci2014/2015 contest #1]
题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1815 题目描述 Mirko在数学课上以一种有趣的方式操作数列,首先,他写下一个数列A ...
- JVM Attach实现原理剖析
本文转载自JVM Attach实现原理剖析 前言 本文旨在从理论上分析JVM 在 Linux 环境下 Attach 操作的前因后果,以及 JVM 为此而设计并实现的解决方案,通过本文,我希望能够讲述清 ...
- 别再人云亦云了!!!你真的搞懂了RDD、DF、DS的区别吗?
几年前,包括最近,我看了各种书籍.教程.官网.但是真正能够把RDD.DataFrame.DataSet解释得清楚一点的.论据多一点少之又少,甚至有的人号称Spark专家,但在这一块根本说不清楚.还有国 ...
- 用铁电存储器FRAM让穿戴式设备更省电
可穿戴设备应用中的显示屏消耗了大部分电池电力.解决方法之一是直接提高电池容量,但是大容量电池会加大尺寸和重量,对可穿戴设备不合适,尤其是在市场不断追求更小型化的新款产品时更是如此.更具挑战性的是电池技 ...