1. 我的答案
这个 SQL 的核心思路是对的:题目里"前三档"对应的是不同工资档位,而不是前三个人,所以用 DENSE_RANK() ... PARTITION BY department_id ORDER BY salary DESC 再筛 <= 3,是标准解法,并列工资会自动一起带出。它需要补的是边界条件:salary 或 department_id 为 NULL 时的行为(在 PostgreSQL / Oracle 里 ORDER BY salary DESC 默认把 NULL 排在最前,NULL 会被当成"最高档"),以及窗口函数对数据库版本的要求。说明文字里那段排名示例写错了。
2. 逐条核对
- 用
DENSE_RANK() 而不是 RANK() / ROW_NUMBER() — holds。"前三档"(tier/档)在中文语境下指工资的不同档位,"工资一样的要一起列出来"进一步排除了 ROW_NUMBER();RANK() 虽然并列也一起出,但会跳号,会把"第 4 档"漏掉或错当成第 4 名。答案的选择和理由都对。
- CTE +
PARTITION BY department_id ORDER BY salary DESC 的结构 — holds。语法正确,分区内降序排名,筛选 salary_rank <= 3,语义与需求一致。
WHERE salary_rank <= 3 的作用 — holds。CTE 里别名可用、外层引用正确,没有在同一个 SELECT 的 WHERE 里引用窗口别名的常见错误。
ORDER BY department_id, salary DESC — holds(只是同一部门同一工资内部顺序不确定,见第 3 节)。
- 示例里的排名解释 — wrong。原文:"如果工资最高的是 10000,第二高的是 9000,但有两个人的工资都是 9000,那么这两个人都会被认为是排名第 2,下一个排名将是第 3(而不是跳过第 2,直接到第 3)。"括号里那句是错的/不通的:以 10000、9000、9000、8000 为例,
DENSE_RANK() 给出 1、2、2、3;而 RANK() 给出 1、2、2、4(跳的是第 3 档,不是"跳过第 2")。前面"下一个排名将是第 3"本身没错,错的是把它对比成了"跳过第 2"。
3. 漏掉的内容
- NULL 工资的坑(最容易出错的一点):
ORDER BY salary DESC 的 NULL 位置是方言相关的——PostgreSQL 和 Oracle 默认 NULLS FIRST(DESC 时 NULL 排最前),NULL 会被当成第 1 档直接入选;MySQL、SQL Server 默认把 NULL 排在最后,通常不会入选。如果 salary 可为空且不希望 NULL 参与,需要显式处理:PostgreSQL / Oracle 用 ORDER BY salary DESC NULLS LAST,MySQL 没有 NULLS LAST 语法,可用 ORDER BY (salary IS NULL), salary DESC。
department_id 为 NULL:会自成一个分区,单独输出一组"部门"记录。如果有这种脏数据,通常要加 WHERE department_id IS NOT NULL。
- 方言与版本限制没有提:窗口函数需要 MySQL 8.0+ / MariaDB 10.2+ / SQLite 3.25+ / PostgreSQL / Oracle / SQL Server。老版本 MySQL 5.7 只能改用相关子查询或自连接,例如
SELECT * FROM employees e WHERE (SELECT COUNT(DISTINCT salary) FROM employees x WHERE x.department_id <=> e.department_id AND x.salary > e.salary) < 3(注意 <=> 处理 NULL,且需显式排除 NULL 工资)。
- 结果顺序不确定:同部门同工资的人之间没有稳定的排序键,若要求可复现,加
, id。
- 语义歧义未声明:如果业务真实口径是"每部门工资最高的 3 个人(含并列)",那应该用
RANK() <= 3。答案默认了"三档"= 三个不同工资档位,这个假设合理,但值得说明一句。
- (可选)性能:
(department_id, salary) 上的索引对 PARTITION BY department_id ORDER BY salary DESC 有帮助。
4. 修正后的答案
SQL 主体保持不变,只补 NULL 与方言处理:
WITH RankedEmployees AS (
SELECT
id,
name,
department_id,
salary,
DENSE_RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS salary_rank
FROM employees
WHERE salary IS NOT NULL -- 若业务上允许 NULL 工资,删掉这行并改用下面的 NULLS 处理
AND department_id IS NOT NULL
)
SELECT id, name, department_id, salary
FROM RankedEmployees
WHERE salary_rank <= 3
ORDER BY department_id, salary DESC, id;
如果必须保留 NULL 工资但又不想让它们占据榜单:
- PostgreSQL / Oracle:
ORDER BY salary DESC NULLS LAST
- MySQL:
ORDER BY (salary IS NULL), salary DESC
解释部分只改那一句示例:工资 10000、9000、9000、8000 时,DENSE_RANK() 得到 1、2、2、3(不跳号,所以 8000 是第 3 档);RANK() 得到 1、2、2、4(跳过第 3 档);ROW_NUMBER() 得到 1、2、3、4(并列被打散)。其余关于 CTE、PARTITION BY、以及为什么不用 RANK() / ROW_NUMBER() 的说明都是正确的,可以照用。