第二高薪资的五种写法
比较子查询、LIMIT/OFFSET 和窗口函数解决方案
第二高薪资的五种写法 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。
人人都会遇到的问题
“找出第二高的薪资”是 SQL 面试中最常被问到的问题。面试官喜欢它,因为它有许多正确答案,也有几个容易忽略的陷阱。
假设有一张 employee 表,其中包含 id 和 salary 列。您的任务是返回第二高的不同薪资值。
- 如果薪资为 300、200、200、100,答案是200,而不是第二行。
- 如果不存在第二个不同的薪资值,通常应返回
NULL。
在接下来的几个场景中,我们将用五种不同方法解决这个问题,并讨论每种方法的优势。
CREATE TABLE employee (
id INT PRIMARY KEY,
salary INT
);方法一:低于 MAX 的值中的最大值
最直观的解决方案是:第二高的薪资,就是严格小于整体最大薪资的所有薪资中最大的那个。
这种写法几乎像英语一样直观,而且适用于所有 SQL 方言。内部子查询找出最高值,外层的 MAX 找出低于该值的最大值。
额外优点:如果不存在第二个不同的薪资值,外层 MAX 会对零行进行聚合,并自动返回 NULL。这个无需额外处理的 NULL 正是面试官希望看到的结果。
SELECT MAX(salary) AS second_highest
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);子查询如何处理重复项
请注意,在方式 1 中我们从未使用 DISTINCT,但重复项仍得到了正确处理。
如果有三个人的薪资都是 200,而最高薪资是 300,内层查询会返回 300。外层过滤会保留所有低于 300 的行,而这些行的 MAX 无论存在多少个 200,结果都是 200。
关键洞察是:聚合函数会替您合并重复项。许多候选人会在聚合函数已经能正确处理的情况下,仍使用 DISTINCT 进行过度设计。
方式 2:使用 LIMIT 和 OFFSET
在 MySQL 和 PostgreSQL 中,您可以按薪资降序排列不重复的薪资,并跳过第一个。
OFFSET 1会跳过最高薪资。LIMIT 1只保留下一个。
DISTINCT 在这里至关重要,否则最高薪资的重复项会使 OFFSET 1 落到最大值的重复项上,而不是实际的第二高薪资。
陷阱:如果不存在第二个不重复值,这会返回零行,而不是 NULL。我们将在第 4 课修复这个边界情况。
SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;方式 3:SQL Server 和 Oracle 使用 FETCH
SQL Server 和现代版 Oracle 不支持 LIMIT ... OFFSET。它们使用 ANSI 标准的 OFFSET ... FETCH 语法。
其逻辑与方式 2 完全相同:按薪资降序排列不重复的薪资,跳过一行,再提取一行。了解不同数据库方言中的这种写法,会向面试官展现您具备真实世界的经验。
SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY;方式 4:DENSE_RANK 窗口函数
现代且可扩展的方法是使用窗口函数。DENSE_RANK 为最高薪资分配排名 1,为下一个不重复薪资分配排名 2,并为并列薪资分配相同的排名且不产生间隔。
我们在子查询中计算排名,然后在外层查询中过滤排名 2。请记住:您不能直接在 WHERE 中过滤窗口函数,因此子查询这一层是必需的。
SELECT salary AS second_highest
FROM (
SELECT salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employee
) ranked
WHERE rnk = 2;为什么使用 DENSE_RANK,而不是 RANK 或 ROW_NUMBER
对于“去重”语义,排名函数的选择很重要:
ROW_NUMBER为每一行分配唯一编号,因此两个薪资为 300 的人会分别成为第 1 行和第 2 行,排名 2 就会再次得到最高薪资。错误。RANK会在并列之后留下间隔:两个薪资为 300 的人排名都是 1,接下来的薪资会跳到排名 3。因此在排名 2 处会漏掉它。错误。DENSE_RANK为并列值分配相同的排名且不留下间隔,因此排名 2 始终是第二个不重复薪资。正确。
方式 5:相关子查询计数
这是窗口函数出现之前的经典技巧:如果严格高于某个薪资的不重复薪资恰好有 N - 1 个,那么该薪资就是第 N 高。
对于第二高薪资,我们需要它上方恰好有一个不重复薪资。这种方法很简洁,但在大型表上可能较慢,因为内层计数会针对外层的每一行运行。
只需将计数改为 N - 1,它就可以很好地推广到第 N 高,这也是面试官喜欢考察它的原因。
SELECT salary AS second_highest
FROM employee e
WHERE 1 = (
SELECT COUNT(DISTINCT e2.salary)
FROM employee e2
WHERE e2.salary > e.salary
);一个完整的示例
假设薪资为:500、500、350、350、100。
- 方式 1:
MAX是 500,低于 500 的最大值是 350。答案是 350。 - 方式 4(DENSE_RANK):500 -> 排名 1,350 -> 排名 2,100 -> 排名 3。排名 2 对应 350。
- 方式 5:对于薪资 350,严格高于它的不重复薪资恰好有一个(500)。符合条件。答案是 350。
五种方法都得出相同结论:即使存在重复项,第二高的不重复薪资仍然是 350。
应该选择哪一种
面试建议:
- 先明确问题:“您需要的是不重复薪资吗?如果不存在,是否要返回 NULL?”主动澄清需求会为您加分。
- DENSE_RANK 是最稳妥的默认答案;它可以自然地推广到第 N 高和按组处理。
- MAX 以下的 MAX 是最好的单行写法,并且可以直接返回 NULL。
- LIMIT/OFFSET 简洁,但依赖数据库方言,并且在边界情况下不返回任何行。
能够主动说出权衡之处,是中级水平的回答区别于初级回答的关键。
需要避免的常见错误
请注意面试官设置的这些陷阱:
- 使用
ROW_NUMBER而不是DENSE_RANK,结果将最高薪资返回两次。 - 在最高值存在重复项时,忘记在 LIMIT/OFFSET 版本中使用
DISTINCT。 - 误以为
ORDER BY salary DESC LIMIT 1,1会返回不重复值(实际上不会)。 - 返回第二个行,而不是第二个值。
快速测验
测试您对排名函数选择的理解。
总结
现在您已经掌握了查找第二高薪资的五种方法:
- MAX 以下的 MAX——可跨数据库使用,并且可以直接返回 NULL。
- LIMIT/OFFSET 和 OFFSET/FETCH——简洁,但依赖数据库方言。
- DENSE_RANK——可扩展的默认方案,能够正确处理并列。
- 相关计数——写法简洁,并且可以推广到第 N 高。
要点是:先确认是否需要不重复值;处理并列时优先选择 DENSE_RANK;当第二个值不存在时,记住哪些方法返回 NULL,哪些方法不返回任何行。
常见问题解答
「第二高薪资的五种写法」课时是免费的吗?
是的 — 「第二高薪资的五种写法」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。
「第二高薪资的五种写法」这节课中我会学到什么?
比较子查询、LIMIT/OFFSET 和窗口函数解决方案 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Coding Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「第二高薪资的五种写法」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Coding Interview Prep 课中编写并运行代码吗?
能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。