0Pricing
Coding Interview Prep · 课时

保留每个键对应的最新行

掌握“每位客户保留最新记录”的模式:按键分区、按日期排序

保留每个键对应的最新行 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

每个键对应最新记录的问题

“返回每位客户最近的一笔订单。”“获取每台设备的最新状态。”这种每个键对应最新行的问题,是 SQL 面试中出现频率最高的任务之一,因为它在实际分析工作中极其常见。

这是一种专门的每组取第一条问题:按键进行分区,按时间戳降序排列,然后保留第一行。本课将详细讲解这一模式及其替代方案。

为什么单独使用 MAX 不够

一个很容易想到的初始答案是按客户分组并使用 MAX(order_date)。这只能得到最新的日期,而无法得到该订单的其余列,例如订单 id、金额或状态。

如果面试官要求返回完整的最新行,那么使用 MAX 和 GROUP BY 时,还需要根据键和最大日期再次连接回原表。这种写法冗长,而且在出现并列时可能出错。窗口函数更加简洁。

-- Gives the date, not the full row
SELECT customer_id, MAX(order_date) AS last_order
FROM orders
GROUP BY customer_id;

ROW_NUMBER 模式

按键进行分区,按时间戳降序排列,最新的行就会获得 rn = 1。只保留这些行,就能得到每个键对应的完整最新记录。

这是首选答案。即使时间戳相同,它也会为每个键恰好返回一行,这通常正是“最新一行”所表达的含义。

WITH ranked AS (
  SELECT *,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id
      ORDER BY order_date DESC
    ) AS rn
  FROM orders
)
SELECT customer_id, order_id, order_date, amount
FROM ranked
WHERE rn = 1;

处理时间戳并列

同一客户的两笔订单可能拥有相同的 order_date(日期相同,或时间戳完全相同)。如果没有并列决胜字段,哪一行会成为 rn = 1 就是任意的,并且可能在不同运行之间发生变化。

请加入唯一的次级键,例如 order_id DESC,让最新行的选择具有确定性。面试官尤其会考查您是否注意到了这个边界情况。

ROW_NUMBER() OVER (
  PARTITION BY customer_id
  ORDER BY order_date DESC, order_id DESC
) AS rn

最新一行还是所有并列行

当时间戳相同时,请先确定“最新”的含义:

  • 如果希望每个键恰好返回一行,请使用带并列决胜字段的 ROW_NUMBER。
  • 如果希望返回所有共享最大时间戳的行,请改用 RANK() = 1,它会返回每一行并列的最新记录。

提出这个澄清问题,能够表明您理解其中的语义,而不仅仅是记住语法。

WITH ranked AS (
  SELECT *,
    RANK() OVER (
      PARTITION BY customer_id ORDER BY order_date DESC
    ) AS rnk
  FROM orders
)
SELECT * FROM ranked WHERE rnk = 1;

相关子查询替代方案

在窗口函数尚未普及之前,每个键对应最新行的问题通常使用相关子查询解决:只有当同一键下不存在日期更晚的其他行时,才保留当前行。

这种方法可以工作,但会针对每一行运行内部查询,因此在大表上速度较慢,而且处理并列情况很别扭。您可以提及它来展示知识面,但出于性能考虑,应优先使用窗口函数方案。

SELECT o.*
FROM orders o
WHERE o.order_date = (
  SELECT MAX(o2.order_date)
  FROM orders o2
  WHERE o2.customer_id = o.customer_id
);

PostgreSQL 的 DISTINCT ON 快捷写法

PostgreSQL 提供了一种简洁的写法:DISTINCT ON (key) 会根据 ORDER BY 为每个键保留第一行。ORDER BY 必须先列出相同的键列,然后再列出并列决胜字段或时间戳。

这种写法在 PostgreSQL 中简洁且快速,但不具备跨数据库兼容性。您可以将其作为特定 SQL 方言的加分项,同时将 ROW_NUMBER 作为默认的通用写法。

SELECT DISTINCT ON (customer_id)
  customer_id, order_id, order_date, amount
FROM orders
ORDER BY customer_id, order_date DESC, order_id DESC;

带条件的最新行

实际问题通常会增加筛选条件,例如“每位客户最近的一笔已完成订单”。请在排名之前应用筛选条件,以便只对符合条件的行编号。

请将条件放在内部查询的 WHERE 中(它会在窗口函数之前执行),然后在外层查询中取 rn = 1。在排名之后筛选会得到错误的行。

WITH ranked AS (
  SELECT *,
    ROW_NUMBER() OVER (
      PARTITION BY customer_id ORDER BY order_date DESC, order_id DESC
    ) AS rn
  FROM orders
  WHERE status = 'completed'
)
SELECT * FROM ranked WHERE rn = 1;

示例:最新设备状态

status_log 表记录了 device_id、status 和 logged_at。要获取每台设备的当前状态,请按 device_id 进行分区,按 logged_at DESC 排序,并保留 rn = 1。

许多实体的“当前状态”仪表板正是以仅追加事件日志为基础构建的。同一模式也适用于最新价格、最新位置和最新版本查询。

WITH latest AS (
  SELECT device_id, status, logged_at,
    ROW_NUMBER() OVER (
      PARTITION BY device_id ORDER BY logged_at DESC
    ) AS rn
  FROM status_log
)
SELECT device_id, status, logged_at
FROM latest
WHERE rn = 1;

性能说明

以下要点有助于体现高级水平:

  • 在 (customer_id, order_date DESC) 上建立索引,可以让数据库引擎高效读取每个键对应的最新行。
  • 窗口函数方案只扫描一次表,而相关子查询不是这样。
  • PostgreSQL 中的 DISTINCT ON 可以使用同一个索引,通常是单表查询中最快的选择。
  • 对于以追加为主的事件日志,可以考虑创建一张物化的“最新记录”表,并进行增量刷新。

常见错误

请注意以下问题:

  • 使用 MAX(date),却只返回日期而不是完整行。
  • 忘记加入并列决胜字段,导致日期并列时结果不具确定性。
  • 在排名之后才根据条件筛选,这可能会选中本应排除的行。
  • 混淆“最新的一行”(ROW_NUMBER)与“所有并列的最新行”(RANK)。

快速检查

请选择正确的每个键对应最新行的查询。

回顾:每个键对应最新行

模式是:PARTITION BY 键,ORDER BY 时间戳 DESC(加上唯一的并列决胜字段),保留 rn = 1。

  • MAX(date) 返回的是日期,而不是完整行。
  • 请始终加入并列决胜字段,以确保结果具有确定性。
  • 如果希望返回所有在最新时间戳上并列的行,请使用 RANK() = 1。
  • 筛选条件应放在内部查询中,并在排名之前应用。
  • PostgreSQL 的 DISTINCT ON 是一种简洁、快速且特定于 SQL 方言的替代方案。

常见问题解答

「保留每个键对应的最新行」课时是免费的吗?

是的 — 「保留每个键对应的最新行」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「保留每个键对应的最新行」这节课中我会学到什么?

掌握“每位客户保留最新记录”的模式:按键分区、按日期排序 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「保留每个键对应的最新行」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 ROW_NUMBER 获取各组前 N 行
  2. 处理前 N 名中的并列值
  3. 安全地去除重复行
  4. 保留每个键对应的最新行
← 返回 Coding Interview Prep