# 图解面试题：如何查找工资前三高的员工

![](/files/-MV5ms0dJum47vXhL-L5)

**【题目】**

“成绩表” 记录了学生的学号，学生选修的课程，以及对应课程的成绩。

为了对学生成绩进行考核，现需要查询每门课程的前 3 高成绩。

注意：如果出现并列第一的情况，则同为第一名。

![](/files/-MV5mx-Qn8VAPPcEnSuN)

**【解题思路】**

题目要求找出每个课程获得前三高成绩的所有学生。难点在于每个课程前 3 高成绩。

前 3 高的成绩意味着要对成绩排名。

这种题类型其实是 “分组排名”，遇到这类型题就要想到用[《猴子 从零学会 sql》](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649247566\&idx=2\&sn=5af748b677eb72028764dde0577675fb\&chksm=835fc77eb4284e68e8cfe3f08c5a671b9e080b2651f20b40b1c793ffda4042ae43ad8f35a755\&scene=21#wechat_redirect)里讲过的窗口函数。

专用窗口函数 rank, dense\_rank, row\_number 有什么区别呢？

它们的区别我举个例子，你们一下就能看懂：

```sql
select *,
   rank() over (order by 成绩 desc) as ranking,
   dense_rank() over (order by 成绩 desc) as dese_rank,
   row_number() over (order by 成绩 desc) as row_num
from 班级;
```

得到结果：

![](/files/-MV5n-Lo3puDtSyM1zXA)

从上面的结果可以看出：

> rank 函数：这个例子中是 5 位，5 位，5 位，8 位，也就是如果有并列名次的行，会占用下一名次的位置。比如正常排名是 1，2，3，4，但是现在前 3 名是并列的名次，结果是：**1，1，1**，4。
>
> dense\_rank 函数：这个例子中是 5 位，5 位，5 位，6 位，也就是如果有并列名次的行，不占用下一名次的位置。比如正常排名是 1，2，3，4，但是现在前 3 名是并列的名次，结果是：**1，1，1**，2。
>
> row\_number 函数：这个例子中是 5 位，6 位，7 位，8 位，也就是不考虑并列名次的情况。比如前 3 名是并列的名次，排名是正常的 **1，2，3**，4。
>
> 这三个函数的区别如下：

![](/files/-MV5n2EMF7TyaRHHISc1)

题目要求 “如果出现并列第一的情况，则同为第一名”。所以，我们使用窗口函数 dense\_rank。

步骤一：按课程分组 (partiotion by 课程号)，并按成绩降序排列 (order by 成绩 desc)，套入窗口函数的语法，就是下面的 sql 语句：

```sql
select *,
     dense_rank() over(partition by 课程号
                       order by 成绩 desc) as排名
from 成绩表;
```

运行结果如下：

![](/files/-MV5n9gD-ExwLnxQkWN0)

步骤二：筛选出前 3 高的成绩，所以我们在上一步基础上加入一个 where 字句来筛选出符合条件的数据。（where 排名 <=3）

```sql
select 课程号,学号,成绩,排名 from
(select *,
     dense_rank() over (partition by 课程号
                  order by 成绩 desc) as 排名
from 成绩表) as aa
where 排名 <=3;
```

**【本题考点】**

1）考察如何使用窗口函数及专用窗口函数排名的区别：rank, dense\_rank, row\_number

2）经典 topN 问题：每组最大的 N 条记录。这类问题涉及到 “既要分组，又要排序” 的情况，要能想到用窗口函数来实现。

```sql
# topN问题 sql模板
select *
from (
   select *, 
          row_number() over (partition by 要分组的列名
                       order by 要排序的列名 desc) as 排名
   from 表名) as a
where 排名 <= N;
```

**【举一反三】**

Employee 表包含所有员工信息，每个员工有其对应的工号 (Id)，姓名 (Name)，工资 (Salary) 和部门编号 ( DepartmentId) 。

查找每个部门前三高工资的员工。例如，根据上述给定的表，查询结果应返回：

![](/files/-MV5nD1AQA_jePhuFIr_)

参考答案：

```sql
select DepartmentId,Name,Salary
from (
   select *, 
          dense_rank() over (partition by DepartmentId
                       order by Salary desc) as ranking
   from Employee) as a
where ranking <= 3;
```

推荐：[如何从零学会 sql？](http://mp.weixin.qq.com/s?__biz=MzAxMTMwNTMxMQ==\&mid=2649247566\&idx=2\&sn=5af748b677eb72028764dde0577675fb\&chksm=835fc77eb4284e68e8cfe3f08c5a671b9e080b2651f20b40b1c793ffda4042ae43ad8f35a755\&scene=21#wechat_redirect)

![](/files/-MV5nGuiBG2i9Zwa2jTs)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://houzidata.gitbook.io/sql/di-4-zhang/tu-jie-mian-shi-ti-ru-he-cha-zhao-gong-zi-qian-san-gao-de-yuan-gong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
