SQL小菜之TOP N查找问题

前言

SQL的编写是后端面试中非常常见,其中TOP N查找问题也是高频出现的问题,今天我们来看两道SQL TOPN问题。

问题

我们有一张雇员表Employee:

sql 复制代码
CREATE TABLE `Employee` (
  `id` int DEFAULT NULL,
  `salary` int DEFAULT NULL,
  `department` varchar(16) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

Question1:

sql 复制代码
查询并返回 Employee 表中第二高的 不同 薪水 。如果不存在第二高的薪水,
查询应该返回 null。

查询结果如下例所示。

示例1:
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
| 2  | 200    |
| 3  | 300    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200                 |
+---------------------+

示例2:
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| null                |
+---------------------+

这就是最典型一道TOP N查找问题,本题要求我们查找第二高薪水的记录,我们来将问题进行拆解:

如果要查找第二高的记录,那是不是找到第一高的记录,然后在小于该记录的结果集中,找到最大的那一个,就是我们想要的结果。

OK,思路明确,开始写SQL,先找到第一高那条记录:

sql 复制代码
select max(distinct(salary)) from Employee

因为题干中没有强调记录不重复,因此需要进行去重,接下来第二步,从比它小的结果集中找到最大的那个,就是我们想要的答案:

sql 复制代码
select max(distinct(salary)) from Employee 
where salary < (select max(distinct(salary)) from Employee)

我们同时要考虑一些异常的情况,例如表中如果仅有一条记录,那么结果需要输出null:

sql 复制代码
select ifNull((max(distinct(salary))), null) as 'SecondHighestSalary' from Employee 
where salary < (select max(distinct(salary)) from Employee);

针对这道题,这个SQL可以解决问题,那么题目换一下,如果不是找第二高的记录呢?如果找第三高,怎么办?

上面的SQL就不能满足我们的需求了,换一个思路,如果要找第N高的记录,我们是不是可以对结果集进行排序,然后把前面的记录丢弃,剩下的结果集取第一个,就是我们想要的结果了?诶?这不就是limit offset么?

sql 复制代码
select ifNull(
  (select distinct(salary) from Employee order by salary desc limit 1,1), null) 
as SecondHighestSalary;

使用limit offset的方式,可以解决大部分简单的TOP N问题了,那么如果修改一下题目,再加入部门的维度,希望找到每个部门中,薪水第二高的记录,上面的解法,似乎就没有办法做到了,那么是否还有其他的解决方法呢?

MySQL在8.0版本中加入了窗口函数,专门为了解决排行问题,来看一下dense_rank()的使用语法:

sql 复制代码
DENSE_RANK() OVER (
    [PARTITION BY column1, column2, ...]
    ORDER BY column1 [ASC|DESC], column2 [ASC|DESC]
)

看起来有点乱哈,没关系,我们来看个示例来帮你理解dense_rank()。

上面的问题,如果用dense_rank()解决:

sql 复制代码
select (
	select distinct salary from (
    	select dense_rank() over ( order by salary desc) as rn, u.* from Employee u
    ) t where t.rn = 2) as SecondHighestSalary;

单看上面的SQL你可能还是会一脸懵逼,没关系,让我们来拆解一下SQL,先看最中间的核心子查询:

sql 复制代码
select dense_rank() over ( order by salary desc) as rn, u.* from Employee u;

+----+------+--------+------------+
| rn | id   | salary | department |
+----+------+--------+------------+
|  1 |    4 |    400 | 二部       |
|  2 |    3 |    300 | 二部       |
|  3 |    2 |    200 | 一部       |
|  3 |    2 |    200 | 一部       |
|  4 |    1 |    100 | 一部       |
+----+------+--------+------------+

可以发现dense_rank()对结果集进行了排序处理,并输出了根据目标字段排序的记录的排名序号,那么再回头看上面的SQL就非常好理解了,我们需要找到排名第二的记录,通过where限定,即可达到预期结果。

再回到上上个问题,如果我们希望查找每个部门中,薪水第二高的记录,怎么做?

聪明的你肯定想到,对部门字段进行group by,再找到第二高的记录,幸运的是,dense_rank()提供了根据字段进行分组的功能,就是PARTITION BY参数,那么来看一下SQL该怎么写:

sql 复制代码
SELECT t.department, t.salary FROM (
    SELECT 
        salary,
        department,
        DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rn
    FROM Employee
) t 
WHERE rn = 2;

DENSE_RANK()、ROW_NUMBER() 和 RANK()对比

最后,我们来看一下MySQL中关于处理排名相关的几个函数,DENSE_RANK、ROW_NUMBER 和 RANK 是 SQL 中三种不同的窗口排名函数,它们的主要区别在于处理相同值(并列)的方式:

  1. ROW_NUMBER()
    • 为每一行分配唯一的序号,从 1 开始递增
    • 对于相同的值,按照它们在结果集中出现的顺序分配不同的序号
    • 结果总是连续的数字,不会出现间隔
  2. RANK()
    • 为每一行分配排名,从 1 开始
    • 对于相同的值,分配相同的排名
    • 排名可能会有间隔,因为相同值后的下一个排名会跳过已用的编号
    • 例如:1, 2, 2, 4, 5...(注意跳过了 3)
  3. DENSE_RANK()
    • 为每一行分配"密集排名",从 1 开始
    • 对于相同的值,分配相同的排名
    • 排名始终是连续的,不会有间隔
    • 例如:1, 2, 2, 3, 4...(无跳过)

结语

本篇,我们简单的探讨了SQL问题中非常经典的TOP N查找问题,学习了关于该种问题的几种解法,希望对你有所帮助。

相关推荐
API_technology20 分钟前
淘宝API+爬虫混合方案:合规采集历史价格与评价数据
前端·数据库·爬虫·数据挖掘
2401_8319433224 分钟前
【SQL性能优化】预编译SQL:从注入防御到性能飞跃
数据库·sql·性能优化
阿黄学技术1 小时前
Redis场景问题2:缓存击穿
java·数据库·redis·缓存
昔我往昔1 小时前
Redis的缓存雪崩和缓存穿透的理解和如何避免
数据库·redis·缓存
shaoweijava2 小时前
基于SpringBoot的美食设计网站(源码+数据库)
数据库·spring boot·mysql·mybatis
网络风云3 小时前
Flask(六)数据库与模型操作
数据库·python·flask
三氧化真5 小时前
使用FastExcel时的单个和批量插入的问题
java·数据库·mybatis
蓝色之鹰6 小时前
高级SQL技巧
sql
程序猿阿伟6 小时前
《探秘SQL的BETWEEN:解锁数据范围查询的深度奥秘》
大数据·数据库·sql
ManageEngine卓豪6 小时前
什么是SQL作业
数据库·数据库性能·sql作业