(首先原谅我最近新番看多了,起了一个中二的名字)

最近在找实习,所以打算系统总结(复习)一下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答案大公开!的更多相关文章

  1. 【转】GitHub 排名前 100 的安卓、iOS项目简介

    GitHub Android Libraries Top 100 简介 排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果, 然后过滤了跟 Android 不 ...

  2. 2016年GitHub 排名前 100 的安卓、iOS项目简介(收藏)

    排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果, 然后过滤了跟 Android 不相关的项目, 所以排名并不具备任何官方效力, 仅供参考学习, 方便初学者 ...

  3. GitHub上排名前100的Android开源库介绍(来自github)

    本项目主要对目前 GitHub 上排名前 100 的 Android 开源库进行简单的介绍,至于排名完全是根据 GitHub 搜索 Java 语言选择 (Best Match) 得到的结果,然后过滤了 ...

  4. sql 经典面试题及答案(选课表)

    SQL数据库面试题以及答案 Student(Sno,Sname,Sage,Ssex) 学生表       Sno:学号:Sname:学生姓名:Sage:学生年龄:Ssex:学生性别Course(Cno ...

  5. 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍

    GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...

  6. GitHub上排名前100的Android开源库介绍

    GitHub上排名前100的Android开源库介绍 文章来源: http://www.open-open.com/news/view/1587067#6734290-qzone-1-31660-bf ...

  7. GitHub 上排名前 100 的 Objective-C 项目简介

    主要对当前 GitHub 排名前 100 的项目做一个简单的简介, 方便初学者快速了解到当前 Objective-C 在 GitHub 的情况.   项目名称 项目信息 1. AFNetworking ...

  8. GitHub上排名前100的iOS开源库介绍(来自github)

    主要对当前 GitHub 排名前 100 的项目做一个简单的简介,方便初学者快速了解到当前 Objective-C 在 GitHub 的情况. 若有任何疑问可通过微博@李锦发联系我 项目名称 项目信息 ...

  9. 使用PL/SQL删除百万条记录的大表

    使用PL/SQL删除百万条记录的大表: 最近开发人员对测试环境数据库进行了压力测试,数据库中产生了大量的脏数据.有几张大表里数据量均在百万甚至千万条的记录数.开发人员现提出需求如下: 只清理其中的部分 ...

随机推荐

  1. 联合登录 & OAuth 2.0 & OpenID

    联合登录 & OAuth 2.0 & OpenID 第三方联合登录一般可以降低网站的获客成本,所以一般的网站都会做一些联合登录,常用的就是QQ.微信.微博; https://www.z ...

  2. Scratch 游戏开发

    Scratch 游戏开发 可视化少儿编程 https://scratch.mit.edu/ Scratch Desktop https://scratch.mit.edu/download https ...

  3. web effects collection

    web effects collection typewriter effect js 打字机效果 http://www.mattboldt.com/demos/typed-js/ https://g ...

  4. taro ENV & NODE_ENV & process.env

    taro ENV & NODE_ENV & process.env https://github.com/NervJS/taro-ui/blob/dev/src/common/util ...

  5. 软件工程中的CI&CD

    wiki 在软件工程中,CI/CD或CICD通常是指持续集成以及持续交付或持续部署的组合实践 持续集成 在软件工程中,持续集成(CI)是每天将所有开发人员的工作副本合并到共享主线中的一种做法.[1] ...

  6. 漫画 | C语言哭了,过年回家,只有我还没对象

    C语言回家过年,遇到不少小伙伴. 大家都在外地打拼,一年难得见面,聚到一起吃饭,都非常高兴. 听Java提到TIOBE, 正在喝酒的C语言激动起来. 自己常年在那里排名第二,人类用自己写的程序可真不少 ...

  7. 工具类:每次随机生成有销售库存有实际库存的1个店铺商品和对应的2个店铺商品sku

    # coding:utf-8 # @fileName :2.每次随机生成有销售库存有实际库存的1个店铺商品和对应的2个店铺商品sku.py # @createTime :2020/4/4 10:33 ...

  8. Java如何保证文件落盘?

    本文转载自Java如何保证文件落盘? 导语 在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失.JDK也封装了对 ...

  9. Gradle 差异化构建

    Compile 默认的依赖方式,任何情况下都会依赖. Provided 只提供编译时依赖,打包时不会添加进去. Apk 只在打包Apk包时依赖,这个应该是比较少用到的. TestCompile 只在测 ...

  10. LeetCode392. 判断子序列

    原题链接 1 class Solution: 2 def isSubsequence(self, s: str, t: str) -> bool: 3 lens,lent = len(s),le ...