经典SQL题目-求第N高的薪水的解法汇总及知识点复习

这几天在看Leetcode的时候逐步开始留意SQL题目,不做不知道,一做才感觉自己的SQL太弱了,因此将一道经典题目:求第N高的薪水的解法进行汇总(MySQL)。相关解法的原文链接已标注在文末~

题目的链接为:第N高的薪水

一、题干

第N高的薪水:编写一个 SQL 查询,获取 Employee 表中第 n 高的薪水(Salary)。

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

例如上述 Employee 表,n = 2 时,应返回第二高的薪水 200。如果不存在第 n 高的薪水,那么查询应返回 null

+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200                    |
+------------------------+

初始化代码片段。

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

二、前置知识碎片

简单总结,有时间再详细补充知识点~

1. limit用法

limit子句用于限制查询结果返回的数量。

用法:[select * from tableName limit i,n]

参数

  • tableName : 为数据表;
  • i : 为查询结果的索引值(默认从0开始);
  • n : 为查询结果返回的数量

2. order by

order by为排序,ASC(默认升序)和DESC`(降序)

适用于单列升序单列降序多列排序,示例:

SELECT * FROM Websites
ORDER BY alexa DESC;

教程链接

3. declare 和 set

[declare 字段名 字段类型]

[set 赋值表达式]

4. if和ifnull

f(true,a,b),if(false,a,b) 这个就是第一个如果是true,就等于afalse就等于b,有点像三元表达式;

ifnull(a, b) ifnull里有两个值,如果a不是null,则返回a, 如果a=null,则返回b

5. 窗口函数

实际上,在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(降序)

三、解法

1. 解法一:set赋值+distinct去重+limit取值的单表查询

题目要求

显示没有固定的N高薪水,N的确定由自定义函数传入。

解题思路

  • 使用limit
  • limit start,count。其中start的显示值是从start+1开始的。但limit后面输入不能是计算式,比如:N-1
  • 第N高的N,是通过自定义函数getNthHighestSalary(N INT)中N传入。start必须是从N-1开始,才能显示符合题目要求的结果。比如第N=2高,如果直接用N值到limitlimit 2,1,意为从第3行开始,显示一行。所以要用N-1=1,才能表示从第二行开始。
  • 这时,应通过一个替代参数实现。MySQL自定函数中的参数是静态参数,即要先定义后使用。先用declare定义类型,后通过set进行赋值。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    declare m INT;
    set m=N-1; 
    RETURN (
        # Write your MySQL query statement below.
        select ifnull(
          (
            select distinct Salary 
            from Employee 
            order by Salary 
            desc limit m,1
          ),
          null
        )
    );
END

注意:此处的set,可以直接set N=N-1,而不用declare m,但声明m会更加明晰一些~

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 N,1
          ),
          null
        )
    );
END

2. 解法二:set赋值+group by去重 + limit取值

主体思路同方法一,group by同样可以起到分组去重的效果,用以代替distinct

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    set N=N-1; 
    RETURN (
        # Write your MySQL query statement below.
        select ifnull(
          (
            select Salary 
            from Employee 
            group by Salary
            order by Salary 
            desc limit N,1
          ),
          null
        )
    );
END

解法一和解法二最为简洁直观,但仅适用于查询全局排名问题,如果要求各分组的每个第N名,则该方法不适用;而且也不能处理存在重复值的情况。

3. 解法三:连续排名解法

  • 计算出每一个薪水的连续排名
  • 找到排名等于N的薪水,并输出
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        select min(a.salary)
        from employee a 
        where (
            select count(*)+1 
            from  (select distinct salary from employee) b 
            where b.salary>a.salary
        )=N
    );
END

4. 解法四:使用子查询&笛卡尔积

  • 排名第N的薪水意味着该表中存在N-1个比其更高的薪水
  • 注意这里的N-1个更高的薪水是指去重后的N-1个,实际对应人数可能不止N-1
  • 最后返回的薪水也应该去重,因为可能不止一个薪水排名第N
  • 由于对于每个薪水的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

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

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

5. 解法五:使用自连接

一般来说,能用子查询解决的问题也能用连接解决。具体到本题:

  • 两表自连接,连接条件设定为表1的salary小于表2的salary
  • 以表1的salary分组,统计表1中每个salary分组后对应表2中salary唯一值个数,即去重
  • 限定步骤2中having 计数个数为N-1,即实现了该分组中表1salary排名为第N个
  • 考虑N=1的特殊情形(特殊是因为N-1=0,计数要求为0),此时不存在满足条件的记录数,但仍需返回结果,所以连接用left join
  • 如果仅查询薪水这一项值,那么不用left join当然也是可以的,只需把连接条件放宽至小于等于、同时查询个数设置为N即可。因为连接条件含等号,所以一定不为空,用join即可。
  • 注:个人认为无需考虑N<=0的情形,毕竟无实际意义。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
    RETURN (
        # Write your MySQL query statement below.
        SELECT e1.salary
        FROM  employee e1 JOIN employee e2 
        ON e1.salary <= e2.salary
        GROUP BY e1.salary
        HAVING count(DISTINCT e2.salary) = N
    );
END

但需要注意的是在题目测试时候,这种解法户报错,因为是自连接两个数比较。如果求第一个数,其中有一个必为空,会出错 ~

6. 解法六:使用窗口函数

窗口函数用法具体见2.5

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

四、总结

至此,可以总结MySQL查询的一般性思路是:

  • 能用单表优先用单表,即便是需要用group byorder bylimit等,效率一般也比多表高
  • 不能用单表时优先用连接,连接是SQL中非常强大的用法,小表驱动大表+建立合适索引+合理运用连接条件,基本上连接可以解决绝大部分问题。但join级数不宜过多,毕竟是一个接近指数级增长的关联效果
  • 能不用子查询、笛卡尔积尽量不用,虽然很多情况下MySQL优化器会将其优化成连接方式的执行过程,但效率仍然难以保证
  • 如果MySQL版本允许,某些带聚合功能的查询需求应用窗口函数是一个最优选择。除了经典的获取3种排名信息,还有聚合函数向前向后取值百分位等,具体可参考官方指南。以下是官方给出的几个窗口函数的介绍:

MySQL窗口函数

五、参考链接

  • https://leetcode-cn.com/problems/nth-highest-salary/solution/177-di-ngao-xin-shui-by-cloudwaterbears/
  • https://leetcode-cn.com/problems/nth-highest-salary/solution/mysql-zi-ding-yi-bian-liang-by-luanz/
  • https://leetcode-cn.com/problems/nth-highest-salary/solution/dan-biao-cha-xun-zi-cha-xun-by-zhouzihon-v98v/
  • https://leetcode-cn.com/problems/nth-highest-salary/solution/lian-xu-pai-ming-densejie-fa-by-xi-feng-f96ns/
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
当涉及到SQL Server的快速复习知识点时,以下是一些重要的方面和概念: 1. 数据库SQL Server是一个关系型数据库管理系统(RDBMS),用于存储和管理结构化数据。它支持多个数据库,并使用T-SQL(Transact-SQL)作为查询和管理语言。 2. 表和列:SQL Server中的数据组织在表中,每个表由列组成。列定义了表中存储的数据类型和约束。 3. 查询语言:使用T-SQL编写查询语句来检索、插入、更新和删除数据。常见的查询语句包括SELECT、INSERT、UPDATE和DELETE。 4. 索引:索引是一种用于加快数据检索速度的结构。在SQL Server中,可以在表的列上创建索引,以提查询性能。 5. 视图:视图是基于一个或多个表的查询结果集。它们提供了一个虚拟表,可以像查询表一样使用。 6. 存储过程:存储过程是一组预编译的SQL语句,可以作为一个单元进行执行。它们可以接受参数,执行复杂的逻辑,并返回结果。 7. 触发器:触发器是与表相关联的特殊类型的存储过程。它们在特定事件(如INSERT、UPDATE或DELETE)发生时自动触发。 8. 事务管理:SQL Server支持事务,用于确保数据库操作的一致性和完整性。事务可以被提交或回滚,以保持数据的一致性。 9. 安全性:SQL Server提供了许多安全功能,如用户和角色管理、权限控制和数据加密,以确保数据库的机密性和安全性。 这些是SQL Server中的一些重要概念和功能。如果你有具体的问题或需要更深入的了解,请告诉我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlexGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值