题目描述

✅ 185. 部门工资前三高的所有员工

image-20260929090841804

image-20260929090841945

image-20260929090842009

题意分析

对每个部门分别找出最高的三种不同工资,返回工资属于这三档的全部员工,并输出部门名、员工名和工资。

同一档工资可以有多名员工,都必须保留,所以每个部门的结果可能超过三行;部门不足三档工资时,全部员工都符合条件。

解法:部门分区排名 + 外层筛选

核心思路

[!blue]

给每名员工计算其工资在本部门属于第几档,再筛选档位不超过 3 的行。这里需要 DENSE_RANK():工资相同的员工名次相同,遇到下一种更低工资时,名次只增加一,不受前一档有多少员工影响。

PARTITION BY e.departmentId 让排名在每个部门内独立计算,ORDER BY e.salary DESC 让最高工资得到名次 1。按部门编号分区能区分不同部门,即使部门名称相同也不会混在一起。

窗口函数为每一行员工增加排名,不会像聚合分组那样把同档员工合并。因此同薪员工的姓名等明细仍然保留,外层筛选 rk <= 3 就能一次选出前三档的所有人。

MySQL 的同层 WHERE 在窗口结果产生之前处理,不能直接用它过滤刚计算出的排名。先把连接和排名放进派生表 ranked,再在外层将 rk 当作普通列筛选。员工表与部门表通过 departmentId = id 连接,负责补齐输出需要的部门名称。

解题步骤

  1. 将 Employee 与 Department 按部门编号连接,取得部门名、员工名和工资。
  2. 按部门编号分区,在各分区内按工资降序计算 DENSE_RANK(),将结果命名为 rk。
  3. 将这些带排名的员工行作为派生表 ranked。
  4. 外层保留 rk <= 3 的行,只输出题目要求的 Department、Employee、Salary 三列,结果顺序不限。

代码实现

SELECT Department, Employee, Salary
FROM (
    SELECT
        d.name AS Department,
        e.name AS Employee,
        e.salary AS Salary,
        -- 按部门分区、按薪水降序,DENSE_RANK 保证并列同名且名次连续。
        DENSE_RANK() OVER (PARTITION BY e.departmentId ORDER BY e.salary DESC) AS rk
    FROM Employee e
    JOIN Department d
    ON e.departmentId = d.id
) AS ranked
-- 窗口函数不能写在同层 WHERE 中,先放进派生表再过滤。
WHERE rk <= 3;

复杂度分析

设员工数量为 E。SQL 的实际开销取决于索引和执行计划,下面只估算窗口排名部分。

  • 时间复杂度:各部门排序的总开销通常可按 $O(E\log(E+1))$ 估算;连接员工与部门的成本另由连接方式决定。
  • 空间复杂度:窗口排序和结果缓冲通常需要 $O(E)$ 量级的辅助存储,具体是否使用临时表由执行计划决定。

关键点总结

[!green]

  • 题目取的是前三种不同工资,DENSE_RANK 对应连续的工资档位。
  • 按部门身份分别排名,窗口计算保留每名员工的明细行。
  • 先在内层产生排名,再在外层筛选排名。

易错点总结

[!yellow]

  • 使用 ROW_NUMBER():同薪员工也会得到不同编号,无法表达工资档位。
  • 使用 RANK():并列人数会使后续名次跳号,第三档工资可能因此被排除。
  • 漏掉部门分区:会变成全公司统一排名,而不是每个部门分别选前三档。
  • 按部门名称分区:名字相同的不同部门会被混为一组,应使用部门编号。
  • 在同层 WHERE 中过滤窗口结果:此时排名尚未计算,应先放入派生表再筛选。

相似题目

题目 难度 关联与区别
184. 部门工资最高的员工 中等 把每部门最高工资扩展到前三种不同工资,重复薪资仍应共享名次。
178. 分数排名 中等 按部门分区使用密集排名,再筛选排名不超过3,可以表达本题要求。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/20308131
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!