使用 DENSE_RANK 查找第 N 高值
推广到第 N 个不重复值,并处理重复项
使用 DENSE_RANK 查找第 N 高值 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。
推广到第 N 高
当您能够找到第二高薪资后,面试官通常会立即追问:“现在请给出第 N 高薪资。”最简洁、最有说服力的答案是使用 DENSE_RANK。
模式始终相同:按降序为不重复薪资排名,然后过滤出排名等于 N 的行。由于这个逻辑不会随 N 改变,因此同一种方法就能回答整类问题。
我们将逐步构建这种方法,处理并列和重复项,并讨论为什么对于“不重复值”语义,DENSE_RANK 是正确的排名函数。
核心模板
下面是可复用的“第 N 高”模板。将常量替换为面试官要求的 N。
您需要在内层查询中计算 DENSE_RANK(窗口函数不能写在 WHERE 中),然后在外层过滤 rnk = N。如果要查找第三高薪资,请将过滤条件设为 rnk = 3。
SELECT salary AS nth_highest
FROM (
SELECT salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employee
) ranked
WHERE rnk = 3;DENSE_RANK 如何为不重复值编号
DENSE_RANK 为相等的值分配相同排名,并且后续排名不会出现间隔。这正是面试官所说的“第 N 个不重复值”的定义。
对于薪资 800、800、600、600、400:
- 800 -> 排名 1
- 600 -> 排名 2
- 400 -> 排名 3
因此第三高薪资是 400,尽管总共有五行。重复项会自动合并到一个排名中。
为什么 RANK 会给出错误答案
如果换成 RANK,答案就会出错。RANK 留下的间隔大小取决于并列的数量。
对于 800、800、600、600、400:
- 800、800 -> 排名 1(共两个)
- 600、600 -> 排名 3(存在间隔,没有排名 2)
- 400 -> 排名 5
过滤 rnk = 3 会返回 600,而过滤 rnk = 2 则不会返回任何结果。除非面试官明确要求竞赛排名,否则对于“第 N 个不重复薪资”,正确的选择是 DENSE_RANK。
为什么 ROW_NUMBER 在这里也不正确
ROW_NUMBER 为每一行分配唯一编号,完全忽略并列。对于 800、800、600、600、400,它会生成 1、2、3、4、5。
因此 rn = 3 会返回 600,但 rn = 2 会返回重复的 800,而不是第二个不重复值。ROW_NUMBER 回答的是“第 N 个行”,而不是“第 N 个不重复值”。
只有当问题确实要求某个特定行时,才应使用 ROW_NUMBER,例如去重,或每组取前 N 个且每组只保留一行。
SELECT salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employee;安全地将 N 参数化
在实际代码中,您不会把排名写死。请将 N 作为参数传入并与之比较。窗口定义保持不变,只有外层过滤条件需要参数化。
这也让您可以返回排名为 N 的所有并列薪资:由于 DENSE_RANK 会为并列值共享同一个排名,如果多名员工的薪资并列为第 N 个不重复薪资,WHERE rnk = N 可能会返回多行,而这通常正是我们想要的结果。
SELECT id, salary
FROM (
SELECT id, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employee
) ranked
WHERE rnk = :n;相关计数的推广
窗口函数之前的方法同样可以推广:当严格高于某个薪资的不重复薪资恰好有 N - 1 个时,该薪资就是第 N 高的不重复薪资。
对于第三高薪资,要求严格高于它的不重复薪资恰好有 2 个。这种方法适用于不支持窗口函数的旧版数据库引擎,但扩展性较差,因为内层计数会针对每一行外层数据重新运行。
SELECT DISTINCT salary AS nth_highest
FROM employee e
WHERE (
SELECT COUNT(DISTINCT e2.salary)
FROM employee e2
WHERE e2.salary > e.salary
) = 2;面试官会要求的 MySQL 函数形式
LeetCode 风格的“第 N 高薪资”问题通常要求编写一个返回单个值的存储函数。函数体只是将 DENSE_RANK 模板包装起来,以返回一个薪资值。
面试时不必死记确切的函数语法,但了解在不重复薪资上使用 LIMIT N-1, 1 是 MySQL 中简洁的惯用写法,仍然很有价值。
SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 2; -- N = 3, so OFFSET N-1示例:第四高薪资
薪资:1000、900、900、700、500、500、300。
使用 DENSE_RANK 按降序排列不重复薪资:
- 1000 -> 1
- 900 -> 2
- 700 -> 3
- 500 -> 4
- 300 -> 5
第四高薪资是 500。请注意,两行 500 的排名都是 4,因此如果同时选择员工编号,过滤 rnk = 4 会返回两名薪资为 500 的员工。
性能注意事项
这些方法在数据量扩大时表现如何?
- DENSE_RANK:对数据进行一次排序,然后过滤。效率较高,并且查询规划器可以利用薪资列上的索引进行排序。
- 相关计数:可能达到 O(n²),因为内层聚合会针对每一行运行。在大型表上应避免使用。
- LIMIT/OFFSET:对于较小的 N 速度很快,但仍然必须排序;较大的偏移量会扫描并丢弃许多行。
优先使用 DENSE_RANK,通常不会出错。
需要提及的边界情况
优秀的候选人会在被问到之前主动指出边界情况:
- N 大于不重复薪资的数量:过滤条件匹配不到任何行,结果为空。第 4 课将介绍如何强制返回单个 NULL。
- 排名 N 处存在并列:DENSE_RANK 会返回所有并列的员工;请先确定这是否符合需求。
- N = 1:模板仍然有效,并会返回最大值。
快速测验
应用“第 N 高”模板。
总结
查找第 N 高薪资有一个首选答案:在子查询中使用 DENSE_RANK() OVER (ORDER BY salary DESC) 为不重复薪资排名,然后过滤 WHERE rnk = N。
- DENSE_RANK 表示“第 N 个不重复值”,并列值共享排名且没有间隔。
- RANK 会产生间隔;ROW_NUMBER 统计的是行,而不是值。
- 相关计数 = N-1 技巧表达了相同的思路,不使用窗口函数,但扩展性较差。
请始终指出“N 超出可用值数量”这一边界情况,下一节将解决它。
常见问题解答
「使用 DENSE_RANK 查找第 N 高值」课时是免费的吗?
是的 — 「使用 DENSE_RANK 查找第 N 高值」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。
「使用 DENSE_RANK 查找第 N 高值」这节课中我会学到什么?
推广到第 N 个不重复值,并处理重复项 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 SQL Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 DENSE_RANK 查找第 N 高值」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 SQL Interview Prep 课中编写并运行代码吗?
能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 第二高薪资的五种写法
- 使用 DENSE_RANK 查找第 N 高值
- 各部门最高薪资者
- 不存在第 N 个值时返回 NULL