题目
- 部门工资前三高的所有员工
SQL架构
Employee 表包含所有员工信息,每个员工有其对应的工号 Id,姓名 Name,工资 Salary 和部门编号 DepartmentId 。
±—±------±-------±-------------+
| Id | Name | Salary | DepartmentId |
±—±------±-------±-------------+
| 1 | Joe | 85000 | 1 |
| 2 | Henry | 80000 | 2 |
| 3 | Sam | 60000 | 2 |
| 4 | Max | 90000 | 1 |
| 5 | Janet | 69000 | 1 |
| 6 | Randy | 85000 | 1 |
| 7 | Will | 70000 | 1 |
±—±------±-------±-------------+
Department 表包含公司所有部门的信息。
±—±---------+
| Id | Name |
±—±---------+
| 1 | IT |
| 2 | Sales |
±—±---------+
编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。例如,根据上述给定的表,查询结果应返回:
±-----------±---------±-------+
| Department | Employee | Salary |
±-----------±---------±-------+
| IT | Max | 90000 |
| IT | Randy | 85000 |
| IT | Joe | 85000 |
| IT | Will | 70000 |
| Sales | Henry | 80000 |
| Sales | Sam | 60000 |
±-----------±---------±-------+
解释:
IT 部门中,Max 获得了最高的工资,Randy 和 Joe 都拿到了第二高的工资,Will 的工资排第三。销售部门(Sales)只有两名员工,Henry 的工资最高,Sam 的工资排第二。
思路
看到返回每个部门的条数不唯一的,首先就会想到分析函数类型中的窗口函数。
再根据示例的结果集,里面的“前三”的概念和我们通常说的不太一样。一般来说,类似成绩排名,如果第K名是有两人并列,那再往后的人应该是排K+2,而这里的结果是K+1。所以对应的窗口函数应该是dense_rank() (与之相对应的是,我们通常说的成绩排名这种搞法是rank(),当然,还有一种更为常见的是row_number(),这个函数是无视“并列”的情况的。)
这类函数的一般用法是
select xxx, dense_rank() over (partition by 分组字段 order by 组内排序字段) rank_index from ...
根据前面的分析,要按部门分组,故分组字段是Employee.DepartmentId,而取工资前三高的员工,排序字段就是Employee.Salary,注意要降序。最后用where条件筛选出题目意义的top3。
编程
两表间的关联直接用内连接即可,没什么特别。
因为最后结果列不包含排名,所以把思路一节里的筛选sql结果做一个子查询,外面包一层,选择显示的结果列,以及对rank_index限定<=3的条件。
PS:如果是写OLTP的系统,比如这段sql嵌在mybatis中,返回给java应用端映射成一个DTO,那这个子查询外面包一层就是多此一举。一般会视前端页面需要与否,在DTO转VO的时候,去掉这个rank_index字段而不是在sql里面处理。
代码
/* Write your PL/SQL query statement below */
select t2name as "Department", t1name as "Employee", Salary
from (select t1.Name as t1name, t2.Name as t2name, t1.Salary, dense_rank() over (partition by DepartmentId order by Salary desc) rank_index
from Employee t1 join Department t2 on t1.DepartmentId = t2.ID
)t
where t.rank_index <= 3
扩展
如果不用分析函数写该怎么写?