Leetcode177.第N高的薪水(中等)

说好的每日一题,但感觉leetcode上数据库的题有一百多道,每日一题进度有些慢,那就有时每日两三题吧。

而且好像在学习Leetcode176 第二高薪水的时候就可以把第N高薪水搞定了。

下面尝试一下:
题目
编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary) 。

+----+--------+
| Id | Salary |
+----+--------+
| 1  | 100    |
| 2  | 200    |
| 3  | 300    |
+----+--------+

例如上述 Employee 表,SQL查询应该返回 200 作为第二高的薪水。如果不存在第二高的薪水,那么查询应返回 null。

+---------------------+
| SecondHighestSalary |
+---------------------+
| 200                 |
+---------------------+

审题
好像没必要审题了,可能遇到的坑应该都很明确了。

自己的解答

SQL菜鸡表示还没在Mysql中写过定义函数

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      
  );
END

太年轻了。。。 先学习一下SQL中怎么创建函数吧

MySQL 创建函数(Function)

  • 语法
CREATE FUNCTION func_name ( [func_parameter] ) //括号是必须的,参数是可选的
RETURNS type
[ characteristic ...] routine_body
  • CREATE FUNCTION 用来创建函数的关键字;
  • func_name 表示函数的名称;
  • func_parameters为函数的参数列表,参数列表的形式为:[IN|OUT|INOUT] param_name type
IN:表示输入参数;
OUT:表示输出参数;
INOUT:表示既可以输入也可以输出;
param_name:表示参数的名称;
type:表示参数的类型,该类型可以是MySQL数据库中的任意类型;
  • RETURNS type:语句表示函数返回数据的类型;

  • characteristic: 指定存储函数的特性,取值与存储过程时相同,详细请访问-MySQL存储过程使用;

  • 在MySQL——函数的使用方法与MySQL内部函数的使用方法一样。

差不多了解了创建函数的语法,下面来自己写一下

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT IFNULL(
      (SELECT DISTINCT Salary  AS getNthHighestSalary
      FROM employee
      ORDER BY Salary DESC
      LIMIT 1 OFFSET N
      ),NULL)
  );
END

额。。 还以为写对了 仔细检查发现 取第N个应该去掉的是前N-1个
但是把N改成N-1 直接报错了

在评论区看到
因为 mysql 中,limit 后面不能带运算符,只能是常量

解决方法: SET N = N - 1;

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  SET N = N - 1;
  RETURN (
      # Write your MySQL query statement below.
      SELECT IFNULL(
      (SELECT DISTINCT Salary  
      FROM employee
      ORDER BY Salary DESC
      LIMIT 1 OFFSET N
      ),NULL) AS getNthHighestSalary
  );
END

通过了。

其他解法
6种方案诠释MySQL通用查询策略

值得一提的是:在Oracle等数据库中有窗口函数,可非常容易实现这些需求,而MySQL直到8.0版本也引入相关函数。当前MySQL OJ系统为5.7版本(可通过编码区——语言选择按钮——左侧的"i"查看环境信息),所以不能直接应用。

初学Mysql应用的都是5.0的版本,8.0后引入的新特性也要在后续进行学习。

这里把之前的思路,和没有想到过的思路(引用如上连接)进行一个总结

1.单表查询

由于本题不存在分组排序,只需返回全局第N高的一个,所以自然想到的想法是用order by排序加limit限制得到。需要注意两个细节:
同薪同名且不跳级的问题,解决办法是用group by按薪水分组后再order by
排名第N高意味着要跳过N-1个薪水,由于无法直接用limit N-1,所以需先在函数开头处理N为N=N-1。
:这里不能直接用limit N-1是因为limit和offset字段后面只接受正整数(意味着0、负数、小数都不行)或者单一变量(意味着不能用表达式),也就是说想取一条,limit 2-1、limit 1.1这类的写法都是报错的。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    SET N := N-1;
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
            salary
      FROM 
            employee
      GROUP BY 
            salary
      ORDER BY 
            salary DESC
      LIMIT N, 1
  );
END

去掉分组 用distinct去重也是可以的

2.子查询

1.排名第N的薪水意味着该表中存在N-1个比其更高的薪水
2.注意这里的N-1个更高的薪水是指去重后的N-1个,实际对应人数可能不止N-1个
3.最后返回的薪水也应该去重,因为可能不止一个薪水排名第N
4.由于对于每个薪水的where条件都要执行一遍子查询,注定其效率低下

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT e.salary
      FROM 
          employee e
      WHERE 
          (SELECT count(DISTINCT salary) FROM employee WHERE salary>e.salary) = N-1
  );
END

解释的挺好的,也很容易理解。

3.自连接
一般来说,能用子查询解决的问题也能用连接解决。

1.两表自连接,连接条件设定为表1的salary小于表2的salary
2.以表1的salary分组,统计表1中每个salary分组后对应表2中salary唯一值个数,即去重
3.限定步骤2中having 计数个数为N-1,即实现了该分组中表1salary排名为第N个
考虑N=1的特殊情形(特殊是因为N-1=0,计数要求为0),此时不存在满足条件的记录数,但仍需返回结果,所以连接用left join
4.如果仅查询薪水这一项值,那么不用left join当然也是可以的,只需把连接条件放宽至小于等于、同时查询个数设置为N即可。因为连接条件含等号,所以一定不为空,用join即可。

有点儿绕,但还是很好懂的。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT e1.salary
      FROM 
          employee e1 LEFT JOIN employee e2 ON e1.salary < e2.salary
      GROUP BY 
          e1.salary
      HAVING 
          count(DISTINCT e2.salary) = N-1
  );
END

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT e1.salary
      FROM 
          employee e1 JOIN employee e2 ON e1.salary <= e2.salary
      GROUP BY 
          e1.salary
      HAVING 
          count(DISTINCT e2.salary) = N
  );
END

思路4:笛卡尔乘积

我感觉这个思路好像就是,对于联结sql92语法和sql99语法的两种不同形式。

可以很容易将思路2中的代码改为笛卡尔积连接形式,其执行过程实际上一致的,甚至MySQL执行时可能会优化成相同的查询语句。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT e1.salary
      FROM 
          employee e1, employee e2 
      WHERE 
          e1.salary <= e2.salary
      GROUP BY 
          e1.salary
      HAVING 
          count(DISTINCT e2.salary) = N
  );
END

思路5:自定义变量
没接触过自定义变量呀
先学习一下 mysql用户变量和set语句

SET @var_name = expr [, @var_name = expr] ...
SELECT @var_name := expr [, @var_name = expr] ...

以上方法2-4中均存在两表关联的问题,表中记录数少时尚可接受,当记录数量较大且无法建立合适索引时,实测速度会比较慢,用算法复杂度来形容大概是O(n^2)量级(实际还与索引有关)。那么,用下面的自定义变量的方法可实现O(2*n)量级,速度会快得多,且与索引无关。

1.自定义变量实现按薪水降序后的数据排名,同薪同名不跳级,即3000、2000、2000、1000排名后为1、2、2、3;
2.对带有排名信息的临时表二次筛选,得到排名为N的薪水;
3.因为薪水排名为N的记录可能不止1个,用distinct去重

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT salary 
      FROM 
          (SELECT 
                salary, @r:=IF(@p=salary, @r, @r+1) AS rnk,  @p:= salary 
            FROM  
                employee, (SELECT @r:=0, @p:=NULL)init 
            ORDER BY 
                salary DESC) tmp
      WHERE rnk = N
  );
END

分解代码理解一下:

#@r:=IF(@p=salary, @r, @r+1) AS rnk判断p变量是否发生变化 变化则rnk+1 不变依然为rnk  
#@p:= salary 更新p变量
SELECT salary, @r:=IF(@p=salary, @r, @r+1) AS rnk,  @p:= salary 
#初始化(init) r为0 p为null
FROM employee, (SELECT @r:=0, @p:=NULL)init 

查询结果是什么呢?



由于数据是升序的,所以得到这个结果。所以需要对salary加一个降序排序。

SELECT salary, @r:=IF(@p=salary, @r, @r+1) AS rnk,  @p:= salary 
FROM employee, (SELECT @r:=0, @p:=NULL)init 
ORDER BY salary DESC

结果:



确实是我们想要的排序结果。
然后对这个结果进行子查询并去重,把rnk=N的选出来即可。

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
      SELECT 
          DISTINCT salary 
      FROM 
          (SELECT 
                salary, @r:=IF(@p=salary, @r, @r+1) AS rnk,  @p:= salary 
            FROM  
                employee, (SELECT @r:=0, @p:=NULL)init 
            ORDER BY 
                salary DESC) tmp
      WHERE rnk = N
  );
END

思路6:窗口函数
你咋又来了。。 那就看看吧

实际上,在mysql8.0中有相关的内置函数,而且考虑了各种排名问题:
row_number(): 同薪不同名,相当于行号,例如3000、2000、2000、1000排名后为1、2、3、4
rank(): 同薪同名,有跳级,例如3000、2000、2000、1000排名后为1、2、2、4
dense_rank(): 同薪同名,无跳级,例如3000、2000、2000、1000排名后为1、2、2、3
ntile(): 分桶排名,即首先按桶的个数分出第一二三桶,然后各桶内从1排名,实际不是很常用

另外这三个函数必须要要与其搭档over()配套使用,over()中的参数常见的有两个,分别是
partition by,按某字段切分
order by,与常规order by用法一致,也区分ASC(默认)和DESC,因为排名总得有个依据

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
  RETURN (
      # Write your MySQL query statement below.
        SELECT 
            DISTINCT salary
        FROM 
            (SELECT 
                salary, dense_rank() over(ORDER BY salary DESC) AS rnk
             FROM 
                employee) tmp
        WHERE rnk = N
  );
END

其实就相当于对内置函数的调用,想法与思路5是一致的。

..

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容