使用 ROW_NUMBER 获取各组前 N 行
掌握“每个类别取前 3 名”的经典分区与排名模式
使用 ROW_NUMBER 获取各组前 N 行 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。
每组前 N 名问题
最常见的 SQL 面试题之一听起来很简单:“返回每个部门中薪资最高的 3 名员工。” 如果候选人立即使用 LIMIT,就会答错,因为 LIMIT 限制的是整个结果集,而不是每个组。
面试官是在考察您是否了解窗口函数。标准答案是:先在每个组内为行编号,再保留编号小于等于 N 的行。本课将逐步构建这一模式。
为什么 LIMIT 无法解决这个问题
假设您编写了下面的查询。它只返回整张表中总计 3 行,而不是每个部门 3 行。
LIMIT(或 TOP,或 FETCH FIRST)作用于最终结果集。标准 SQL 中没有按组设置的 LIMIT。当面试官听到您针对每组问题建议使用 LIMIT 3 时,这表明您还没有真正理解分区。
-- WRONG: only 3 rows total, not 3 per department
SELECT department, name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;认识 ROW_NUMBER
ROW_NUMBER() 是一种窗口函数,会按照排序为每一行分配唯一且连续的整数。单独使用时,它会为整个结果集编号。
关键在于 PARTITION BY:它会为每个组重新从 1 开始编号。将 PARTITION BY department 与 ORDER BY salary DESC 结合后,每个部门都会按照薪资获得各自的 1、2、3……排名。
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS rn
FROM employees;阅读编号后的结果
运行上一个查询后,每一行都会带有一个 rn 值。在每个部门内,薪资最高的行获得 rn = 1,下一行获得 2,依此类推。遇到新的部门时,编号会重新回到 1。
- 销售部:Ana(1)、Bo(2)、Cal(3)、Dee(4)
- 工程部:Eve(1)、Fin(2)、Gus(3)
现在,“每个部门的前 3 名”就意味着“保留 rn <= 3 的行”。
不能在 WHERE 中筛选 rn
自然的下一步是使用 WHERE rn <= 3,但这样会失败。按照逻辑执行顺序,窗口函数是在 WHERE 子句之后计算的,因此执行 WHERE 时,别名 rn 还不存在。
面试官很喜欢考这个陷阱。解决方法是先在子查询或 CTE 中计算窗口函数,再在外层查询中筛选内层查询的结果。
-- ERROR: rn does not exist in WHERE
SELECT name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
WHERE rn <= 3;规范的 CTE 解决方案
将编号逻辑包装在名为 ranked 的 CTE 中,然后从该 CTE 查询,并在外层 WHERE 中进行筛选。这是面试官希望看到的答案,结构也很清晰。
请记住这个骨架:按组进行分区,按指标排序,在外层查询中筛选 rn ≤ N。只需修改一个数字,就能推广到每组前 1 名、前 5 名或任意 N 名。
WITH ranked AS (
SELECT
name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn <= 3
ORDER BY department, rn;子查询形式
如果面试官使用的是较旧的数据库方言,或者更喜欢子查询,那么同样的逻辑可以放入 FROM 中的派生表。请记住,派生表必须有别名(此处为 r),否则会出现语法错误。
CTE 和派生表形式对于这个问题是可以互换的。请选择面试官认为更易读的形式;两者都完全正确。
SELECT name, department, salary
FROM (
SELECT name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department ORDER BY salary DESC
) AS rn
FROM employees
) AS r
WHERE rn <= 3;每组第一名:每组的最佳者
“找出每个部门中薪资最高的员工”其实就是 N = 1。将筛选条件设为 rn = 1 即可。
为什么不使用 MAX(salary) 和 GROUP BY department?因为 MAX 只能给出薪资值,无法返回该员工所在行的其他信息(姓名、入职日期等)。ROW_NUMBER 会保留整个获胜行,而这通常才是题目真正需要的结果。
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY department ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT name, department, salary, hire_date
FROM ranked
WHERE rn = 1;添加确定性的并列处理键
ROW_NUMBER 始终会返回恰好 N 行,即使薪资相同也是如此。但是,如果不处理并列,哪一行获得 rn = 1 就是任意的。如果两个人的薪资都是 90000,而您只保留 rn = 1,那么每次运行所选中的人都可能不同。
请添加一个次要且唯一的排序键,例如 employee_id,以便让结果稳定且可复现。面试时,主动提到确定性的候选人通常会得到认可。
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary DESC, employee_id ASC
) AS rn一个具体的完整示例
给定一个包含 region、product 和 revenue 的 sales 表,请返回每个地区按收入排名前 2 的产品。使用相同的步骤:按 region 分区,按 revenue DESC 排序,保留 rn <= 2 的行。
请注意,只有分区列和指标列发生了变化。无论业务领域是什么,结构都完全相同。
WITH ranked AS (
SELECT region, product, revenue,
ROW_NUMBER() OVER (
PARTITION BY region ORDER BY revenue DESC, product
) AS rn
FROM sales
)
SELECT region, product, revenue
FROM ranked
WHERE rn <= 2
ORDER BY region, rn;性能与面试表达要点
如果想在答对之外进一步展示能力,可以提到:
- 在
(department, salary DESC)上建立索引,有助于数据库引擎高效地产生每个分区内已排序的行。 - 窗口函数方法只扫描一次表,比逐行运行的相关子查询高效得多。
- 对于非常大的每组前 1 名问题,某些数据库引擎支持
DISTINCT ON(PostgreSQL)作为快捷方式,但ROW_NUMBER才是可移植的标准写法。
请始终说明您使用的并列处理键,并确认题目要求的 N。
快速检查
检验您对每组前 N 名模式的掌握程度。
回顾:每组前 N 名
该模式可以概括为一句话:按组进行分区,按指标排序,分配 ROW_NUMBER,然后在外层查询中保留 rn ≤ N。
LIMIT限制整个结果集,从不按组限制。- 不能在
WHERE中筛选窗口函数别名;请将其包装在 CTE 或子查询中。 - 添加唯一的并列处理键,以获得确定性的结果。
- 每组第一名会保留完整的获胜行,不同于
MAX+GROUP BY。
只需修改一个数字,同一个查询就能解决每组前 1 名、前 5 名或任意 N 名的问题。
常见问题解答
「使用 ROW_NUMBER 获取各组前 N 行」课时是免费的吗?
是的 — 「使用 ROW_NUMBER 获取各组前 N 行」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。
「使用 ROW_NUMBER 获取各组前 N 行」这节课中我会学到什么?
掌握“每个类别取前 3 名”的经典分区与排名模式 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 SQL Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「使用 ROW_NUMBER 获取各组前 N 行」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 SQL Interview Prep 课中编写并运行代码吗?
能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 ROW_NUMBER 获取各组前 N 行
- 处理前 N 名中的并列值
- 安全地去除重复行
- 保留每个键对应的最新行