最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在SQL中如何通过JOIN高效查询每个部门薪资最高的前三名核心员工
时间:2026-07-12 09:41:52 编辑:袖梨 来源:一聚教程网
最直接解法是用窗口函数 ROW_NUMBER() 配合 PARTITION BY,在 JOIN 后加子查询或 CTE,对每个部门按薪水降序编号,再筛选 rn=1 的记录。
用窗口函数 ROW_NUMBER() 配合 PARTITION BY 是最直接解法
直接在 JOIN 后加子查询或 CTE,对每个部门按薪水降序编号,再筛选 rn 。别用 <code>GROUP BY + MAX(),那只能查出最高薪数值,查不到对应员工信息。
常见错误是写成:SELECT dept, MAX(salary) FROM emp GROUP BY dept —— 这根本拿不到“前三名员工”的姓名、ID 等字段,属于典型语义误用。
实操建议:
- 必须用
ROW_NUMBER()(不是RANK()或DENSE_RANK()),除非你明确需要并列时跳号(比如两个 20000 并列第一,下一个就是第三名) -
PARTITION BY department_id要和 JOIN 的部门字段严格一致,注意别漏掉表别名,比如写成PARTITION BY d.id却忘了d是部门表别名 - 排序用
ORDER BY salary DESC, employee_id ASC,避免薪水相同时结果不稳定(尤其分页或多次执行)
JOIN 顺序和过滤时机决定性能关键
先关联再排序编号,比先筛再 JOIN 更安全。如果先用子查询把每个部门前 3 名算出来,再 JOIN 部门表,能避免全表扫描;反过来,如果先 JOIN 再 ROW_NUMBER(),数据量大时内存压力明显。
示例结构(以 PostgreSQL/MySQL 8.0+ 为例):
WITH ranked AS ( SELECT e.employee_id, e.name, e.salary, e.department_id, ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC, e.employee_id ASC) AS rn FROM employees e)SELECT r.*, d.name AS dept_nameFROM ranked rJOIN departments d ON r.department_id = d.idWHERE r.rn <= 3;
注意:WHERE r.rn 必须放在最外层,不能写在 CTE 里——否则优化器可能无法下推过滤条件,导致计算全部行的序号。
MySQL 5.7 或 SQLite 等不支持窗口函数?改用相关子查询
这类数据库没法用 ROW_NUMBER(),但硬要实现“每个部门前三”,就得靠 (SELECT COUNT(*) ...) 统计同部门更高薪人数。性能差,只适合小表(
典型写法:
SELECT e1.name, e1.salary, e1.department_idFROM employees e1WHERE ( SELECT COUNT(*) FROM employees e2 WHERE e2.department_id = e1.department_id AND e2.salary > e1.salary) < 3;
陷阱:
- 没有
ORDER BY时,相同薪水员工可能被随机截断,实际返回不固定三人 - 索引必须包含
(department_id, salary),否则子查询会全表扫描,慢到不可接受 - 如果某部门只有两人,这条语句仍正确返回两人;但若用
LIMIT 3就完全不对——LIMIT是全局限制,不是每组限制
别忽略 NULL 和重复薪水带来的语义偏差
薪水字段为 NULL 时,ORDER BY salary DESC 默认把 NULL 排最后(标准 SQL 行为),但某些旧版 MySQL 可能排最前,导致 ROW_NUMBER() 编号错乱。显式写成 ORDER BY salary DESC NULLS LAST 更稳妥(PostgreSQL 支持,MySQL 不支持,需用 IFNULL(salary, 0) 替代)。
重复薪水场景下:ROW_NUMBER() 强制唯一编号,RANK() 会产生并列(如:20000、20000、19000 → 1、1、3),DENSE_RANK() 是 1、1、2。选哪个取决于业务定义——“前三名”是否允许并列。
最容易被忽略的是:部门表里存在但员工表无记录的空部门(比如刚建的部门还没招人),此时 LEFT JOIN 会返回 NULL 员工行。如果需求是“只查有员工的部门”,就用 INNER JOIN;如果必须列出所有部门(含空部门),就得额外处理 rn 字段的 NULL 情况。