子查询无法直接实现“每个部门工资前三名”,需用窗口函数row_number()配合partition by department和order by salary desc,mysql 8.0+等主流数据库支持,旧版本需变量或自连接替代。

子查询无法直接实现“每个部门工资前三名”
直接用普通子查询(比如 WHERE salary > (SELECT ...))没法解决这个问题。因为“每个部门前三”是分组内的排名问题,需要按部门分别排序并截取前3条,而传统子查询不具备“对每组独立排序+编号”的能力。
真正可行的是窗口函数——它能为每组数据生成序号,且不破坏原表结构。MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;如果你用的是 MySQL 5.7 或更早版本,才需要考虑变量模拟或自连接等兼容方案。
推荐写法:用 ROW_NUMBER() 窗口函数
这是最清晰、可读性最强、性能也相对可控的方式。关键点在于:PARTITION BY department 划分部门,ORDER BY salary DESC 决定排序方向,再用外层过滤 rn 。
SELECT department, name, salary
FROM (
SELECT department, name, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
) ranked
WHERE rn
-
ROW_NUMBER()保证严格递增编号,相同工资也会被排不同名次(比如 1、2、3) - 如果要允许并列(如两个第1名,下一个为第3名),改用
RANK();若要并列后跳过(两个第1名,下一个为第2名),用DENSE_RANK() - 注意
ORDER BY必须明确写在OVER里,否则语法报错 - 某些旧版 PostgreSQL 要求子查询必须有别名(如上面的
ranked),漏掉会报错
MySQL 5.7 怎么办?用变量模拟排名
没有窗口函数时,得靠用户变量逐行计算序号。但要注意:MySQL 不保证变量赋值顺序,必须配合 ORDER BY 强制排序,且不能在同一个查询中既赋值又引用。
SELECT department, name, salary
FROM (
SELECT department, name, salary,
@rn := IF(@dept = department, @rn + 1, 1) AS rn,
@dept := department
FROM employees
CROSS JOIN (SELECT @rn := 0, @dept := '') AS init
ORDER BY department, salary DESC
) ranked
WHERE rn
- 必须在子查询内做
ORDER BY department, salary DESC,否则变量计数错乱 -
CROSS JOIN初始化变量是必要步骤,漏掉会导致@rn为 NULL - 该写法在 MySQL 8.0 中已被标记为不可靠(官方文档明确说变量行为不保证),仅作降级兼容用
- 性能比窗口函数差,数据量大时明显变慢
为什么不用自连接或相关子查询?
有人尝试用 “统计比当前工资高的人数 (SELECT COUNT(*) FROM employees e2 WHERE e2.department = e1.department AND e2.salary > e1.salary) 。这种思路理论上可行,但实际踩坑多:
- 相同工资会被重复计入,导致“前三”变成“前三不同值”,比如工资 [10000, 9000, 9000, 8000],可能只返回 10000 和 9000 两条
- 没有
ORDER BY时结果顺序不确定,可能漏掉同薪但应入选的记录 - 嵌套子查询执行效率低,部门多、人数多时响应明显变慢
- 无法控制并列情况下的名额分配逻辑(比如是否允许两个第2名占满3个名额)
真要用,至少得加 DISTINCT 和额外排序,反而更难维护。不如直接上窗口函数。
窗口函数那行 OVER (PARTITION BY ... ORDER BY ...) 是核心,漏掉任意一部分都会失效。变量方案看着短,但初始化和排序顺序稍一错,结果就全偏了——这点最容易被忽略。











