
作者:Rebooter.S,SQLFlash 布道师。
原文:https://sqlflash.ai/blog/leetcode-185-has-problem/
爱可生开源社区翻译,本文约 1700 字,预计阅读需要 5 分钟。

LeetCode 最难的 SQL 题?
经常刷 LeetCode 的开发者,一定对《185: 部门工资前三高的所有员工》[1] 这道题不陌生,它是第一道且在很长时间里唯一的一道难度为 困难 的数据库类题目。目前已有 90 万次答案提交,通过率 58.3%。

访问 LeetCode 原题,请点击文末 原文链接
官方给出了两种参考答案在 LeetCode 评论区引起了广泛的讨论,那这到底是怎样的一道题呢?
185. 部门工资前三高的所有员工
提前准备库表及数据
Create tableIfNotExists Employee (idint, namevarchar(255), salary int, departmentId int)
CreatetableIfNotExists Department (idint, namevarchar(255))
Truncatetable Employee
insertinto Employee (id, name, salary, departmentId) values ('1', 'Joe', '85000', '1')
insertinto Employee (id, name, salary, departmentId) values ('2', 'Henry', '80000', '2')
insertinto Employee (id, name, salary, departmentId) values ('3', 'Sam', '60000', '2')
insertinto Employee (id, name, salary, departmentId) values ('4', 'Max', '90000', '1')
insertinto Employee (id, name, salary, departmentId) values ('5', 'Janet', '69000', '1')
insertinto Employee (id, name, salary, departmentId) values ('6', 'Randy', '85000', '1')
insertinto Employee (id, name, salary, departmentId) values ('7', 'Will', '70000', '1')
Truncatetable Department
insertinto Department (id, name) values ('1', 'IT')
insertinto Department (id, name) values ('2', 'Sales')
表:Employee
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| id | int |
| name | varchar |
| salary | int |
| departmentId | int |
+--------------+---------+
id
是该表的主键列(具有唯一值的列)。departmentId
是Department
表中ID
的外键(reference 列)。
该表的每一行都表示员工的 ID、姓名和工资。它还包含了他们部门的 ID。
表:Department
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| name | varchar |
+-------------+---------+
id
是该表的主键列(具有唯一值的列)。
该表的每一行表示部门 ID 和部门名。
公司的主管们感兴趣的是公司每个部门中谁赚的钱最多。一个部门的 高收入者 是指一个员工的工资在该部门的 不同 工资中 排名前三。
编写解决方案,找出每个部门中 收入高的员工。
以 任意顺序 返回结果表。
效果示例
返回结果格式如下所示。
输入:Employee
表和 Department
表
+----+-------+--------+--------------+
| 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 |
+----+-------+--------+--------------+
+----+-------+
| id | name |
+----+-------+
| 1 | IT |
| 2 | Sales |
+----+-------+
输出:
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Joe | 85000 |
| IT | Randy | 85000 |
| IT | Will | 70000 |
| Sales | Henry | 80000 |
| Sales | Sam | 60000 |
+------------+----------+--------+
解释:
在 IT 部门:
Max 的工资最高 Randy 和 Joe 都赚取第二高的独特的薪水 Will 的薪水是第三高的
在销售部:
Henry 的工资最高 Sam 的薪水第二高 没有第三高的工资,因为只有两名员工
提示:没有姓名、薪资和部门 完全 相同的员工。
官方答案对比
说明:本题选用 MySQL 实现。
方案一:子查询
SELECT d.name AS 'Department',
e1.name AS 'Employee',
e1.salary AS 'Salary'
FROM Employee e1
JOIN Department d
ON e1.departmentId = d.id
WHERE
3 > (SELECT COUNT(DISTINCT e2.salary)
FROM Employee e2
WHERE e2.salary > e1.salary AND e1.departmentId = e2.departmentId);
通过关联子查询逐行判断薪资排名,计算每个员工所在部门中薪资高于其的人数。若人数小于 3,则保留该记录。
方案二:窗口函数
WITH employee_department AS
(
SELECT d.id,
d.name AS Department,
salary AS Salary,
e.name AS Employee,
DENSE_RANK()OVER(PARTITIONBY d.id ORDERBY salary DESC) AS rnk
FROM Department d
JOIN Employee e
ON d.id = e.departmentId
)
SELECT Department, Employee, Salary
FROM employee_department
WHERE rnk <= 3
利用 DENSE_RANK()
窗口函数对每个部门员工按薪资降序排名,直接筛选排名前 3 的记录。
性能测试
我们来测试一下这两个答案的性能差异。
构建测试环境
让 AI 帮我们生成脚本(Prompt):
Table: Employee
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| id | int |
| name | varchar |
| salary | int |
| departmentId | int |
+--------------+---------+
id is the primary key (column withuniquevalues) for this table.
departmentId is a foreignkey (referencecolumn) of the IDfrom the Department table.
Eachrowof this table indicates the ID, name, and salary of an employee. It also contains the IDof their department.
Table: Department
+-------------+---------+
| ColumnName | Type |
+-------------+---------+
| id | int |
| name | varchar |
+-------------+---------+
idis the primary key (columnwithuniquevalues) for this table.
Eachrowof this table indicates the IDof a department and its name.
帮我把表结构生成出来,且给我一个存储过程造一些较多的数据。
然后我们得到了建表语句和存储过程。
-- 创建 Department 表
CREATETABLE Department (
idINT PRIMARY KEY,
nameVARCHAR(100)
);
-- 创建 Employee 表
CREATETABLE Employee (
idINT PRIMARY KEY,
nameVARCHAR(100),
salary INT,
departmentId INT,
FOREIGNKEY (departmentId) REFERENCES Department(id)
);
这个存储过程会创建 10 个部门,每个部门插入 1000 名员工,总共 10000 条 Employee 数据。
DELIMITER $$
CREATE PROCEDURE generate_test_data()
BEGIN
DECLARE dept_id INT DEFAULT 1;
DECLARE emp_id INT DEFAULT 1;
DECLARE i INT;
DECLARE j INT;
SET i = 1;
WHILE i <= 10 DO
INSERT INTO Department (id, name) VALUES (i, CONCAT('Dept_', i));
SET i = i + 1;
END WHILE;
SET i = 1;
WHILE i <= 10 DO
SET j = 1;
WHILE j <= 1000 DO
INSERT INTO Employee (id, name, salary, departmentId)
VALUES (emp_id, CONCAT('Employee_', emp_id), FLOOR(3000 + RAND() * 7000), i);
SET emp_id = emp_id + 1;
SET j = j + 1;
END WHILE;
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
执行存储过程:CALL generate_test_data();
性能测试对比

原来差异居然这么大,使用 DENSE_RANK()
窗口函数的 SQL 几乎毫秒级就执行完毕了,使用子查询的版本居然要 5s 多,让人无法接受。
为什么要使用窗口函数?
MySQL 从 8.0 版本开始引入了窗口函数(Window Functions),极大地增强了在 SQL 层面进行复杂分析的能力,如排名 RANK()
、累计和SUM()
over window、滑动平均等。在性能层面,窗口函数相比以往通过子查询或临时表实现类似功能,有显著提升。以下是一些常见的好处:
避免重复扫描与多次计算:传统方法如子查询或 JOIN,往往需要多次扫描数据表,尤其是在处理排名、累计和移动平均等计算时,可能导致性能瓶颈。而窗口函数通过一次扫描即可完成多种计算,避免了重复的数据访问; 减少中间结果和临时表的开销:使用子查询或 JOIN 通常会生成中间结果集,可能需要创建临时表或使用工作表,增加了内存和磁盘的使用; 避免不必要的物化:在传统方法中,可能需要多次计算相同的聚合值,导致性能下降。而窗口函数在相同的 PARTITION BY 和 ORDER BY 条件下,可以共享计算结果,避免重复计算。
总结与建议
优先选择窗口函数:在 MySQL 8.0+ 或支持窗口函数的数据库中,窗口函数是更优解。
我之前在 SQL Server 上也做过相关测试,更详细的分析可以参考这篇文章:《SQL Server Performance Optimization: Window Functions to Double Your Query Efficiency》[2]
试试 SQLFlash?
在 2025 年 7 月《大模型 SQL 能力排行榜》中 SQL 优化能力榜单,SQLFlash 以 88.5 分暂列第一。我们将尝试使用 SQLFlash 对这道题的方案一 SQL 进行优化。

可以看到 SQLFlash 也采用了窗口函数的方式进行了改写。虽然 SQL 结构与官方结果有一些差异,但结果是一样的,并且大大提升了查询性能。

SQLFlash[3] 轻松搞定了这次 SQL 优化!
No.185: https://leetcode.cn/problems/department-top-three-salaries/description/
[2]SQL Server 版本: https://sqlflash.ai/blog/sql-server-performance-optimization-window-functions/
[3]SQLFlash: https://sqlflash.ai/
本文关键字:#MySQL #Leetcode #窗口函数 #SQLFlash

《技术译文系列》

✨ Github:https://github.com/actiontech/sqle
📚 文档:https://actiontech.github.io/sqle-docs/
💻 官网:https://opensource.actionsky.com/sqle/
👥 微信群:请添加小助手加入 ActionOpenSource
🔗 商业支持:https://www.actionsky.com/sqle




