必须用 PARTITION BY department_id 分组 + ORDER BY salary DESC 排序,否则 ROW_NUMBER() 将全表视为一组;需子查询或 CTE 先计算序号再过滤,且相同薪水时 ROW_NUMBER() 强制不并列。

用 ROW_NUMBER() 实现部门内薪水 Top 3 的核心逻辑
直接结论:必须配合 PARTITION BY department_id 分组 + ORDER BY salary DESC 排序,否则 ROW_NUMBER() 会把全表当一个组编号,得不到每个部门独立的前三。
常见错误是只写 ORDER BY salary DESC 忘了 PARTITION BY,结果编号从 1 排到 N,所有员工挤在一个序列里。
-
ROW_NUMBER()是窗口函数,不带PARTITION BY就等价于PARTITION BY ()(整个结果集为一组) - 部门字段名实际可能是
dept_name、department或关联表中的别名,需按真实 schema 调整 - 如果薪水相同,
ROW_NUMBER()会强制分出 1/2/3(比如 15000→1,15000→2),需要并列不跳号得换RANK()
完整可执行 SQL 示例(含子查询封装)
不能在 WHERE 中直接用 ROW_NUMBER(),因为窗口函数不能出现在 WHERE 阶段——必须先在子查询或 CTE 中算出序号,再外层过滤。
SELECT department_id, employee_name, salary
FROM (
SELECT
department_id,
employee_name,
salary,
ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY salary DESC, employee_id ASC
) AS rn
FROM employees
) ranked
WHERE rn <= 3;
说明:
-
ORDER BY salary DESC, employee_id ASC解决同薪时排序不确定问题,加employee_id是为了结果稳定(避免每次执行顺序不同) - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持该写法;SQLite 3.25+ 也支持,但旧版不支持窗口函数
- 如果表数据量大,
department_id和salary字段建议有联合索引,否则排序开销高
遇到 NULL 部门或 NULL 薪水怎么办
NULL 值默认排在排序结果末尾(ORDER BY ... DESC 时在最后),但 PARTITION BY department_id 会让所有 department_id IS NULL 的行被分进同一组——这组也会单独产生自己的 “前三”,可能不是你想要的。
- 若要排除无部门员工:在子查询中加
WHERE department_id IS NOT NULL - 若要排除薪水为空的员工:加
AND salary IS NOT NULL,否则ROW_NUMBER()仍会分配序号,但NULL参与排序位置不可控 - 某些数据库(如 PostgreSQL)允许用
ORDER BY salary DESC NULLS LAST显式控制,但 MySQL 不支持NULLS LAST语法
和 RANK()、DENSE_RANK() 的关键区别在哪
当两个员工薪水同为 20000 并列第一时:
-
ROW_NUMBER()→ 编号为 1 和 2(强行错开) -
RANK()→ 编号为 1 和 1,下一个 15000 是第 3 名(跳过 2) -
DENSE_RANK()→ 编号为 1 和 1,下一个 15000 是第 2 名(不跳)
业务上“前三”是否允许并列,决定了该选哪个函数。HR 系统常要求严格取三条记录,就用 ROW_NUMBER();若要显示“并列第一、唯一第三”,就得用 RANK() 并配 WHERE rank ——这时实际返回可能多于 3 行。
真正容易被忽略的是:窗口函数的执行时机晚于 GROUP BY 和 WHERE,早于 ORDER BY 和 LIMIT,这个顺序一旦错乱,要么报错,要么结果错得无声无息。

















