0Pricing
SQL Academy · 课时

PostgreSQL DISTINCT ON

每个分组选择一行

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

DISTINCT ON 是什么

PostgreSQL 提供了一个对标准 DISTINCT 关键字的强大扩展,称为 DISTINCT ON。普通的 DISTINCT 会移除完全重复的行,而 DISTINCT ON 则允许您根据所选的一列或多列,准确地从每个分组中选择一行。

您可以这样理解:“对于此列中的每个唯一值,给我一行。”当您想获取每位客户的最新订单、每位学生的最高分或每个类别的第一个事件时,这一功能非常实用。

DISTINCT ON 基础语法

语法会将 DISTINCT ON (column) 放在 SELECT 后面。括号中的列定义了分组——PostgreSQL 会为该列的每个唯一值返回一行。

下面的示例从订单表中为每个 customer_id 返回一行。PostgreSQL 会根据后面的 ORDER BY 子句决定返回哪一行。

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

设置示例表

让我们创建一个简单的 orders 表,并插入一些示例行,以便实际尝试 DISTINCT ON。我们有三位客户,每位客户在不同日期都有多笔订单。

CREATE TABLE orders (
  order_id      SERIAL PRIMARY KEY,
  customer_id   INT,
  order_date    DATE,
  total_amount  NUMERIC(10, 2)
);

INSERT INTO orders (customer_id, order_date, total_amount) VALUES
  (1, '2024-01-05', 120.00),
  (1, '2024-03-12', 85.50),
  (1, '2024-06-20', 200.00),
  (2, '2024-02-14', 45.00),
  (2, '2024-05-30', 310.00),
  (3, '2024-04-01', 75.00);

每位客户的最新订单

一个非常常见的应用场景是:找出每位客户的最新订单。在每个 customer_id 分组内按 order_date DESC 排序后,DISTINCT ON 会选择日期最新的那一行。

请注意,ORDER BY 子句必须以 DISTINCT ON 中列出的相同列(一个或多个)开头。这是 PostgreSQL 的要求。

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

每位客户最早的订单

如果要获取每位客户的第一笔(最早的)订单,只需将排序方向改为 ASC。唯一的差异是每个分组内行的顺序——DISTINCT ON 始终选择排序后的第一行。

SELECT DISTINCT ON (customer_id)
  customer_id,
  order_id,
  order_date,
  total_amount
FROM orders
ORDER BY customer_id, order_date ASC;

ORDER BY 规则

重要规则:使用 DISTINCT ON (col) 时,ORDER BY 子句必须以 DISTINCT ON 中列出的相同列(一个或多个)开头。如果不是这样,PostgreSQL 就会引发错误。

在分组列之后,您可以添加任意其他排序条件,以控制选择每个分组中的哪一行。

-- Correct: ORDER BY starts with the DISTINCT ON column
SELECT DISTINCT ON (customer_id)
  customer_id, order_date, total_amount
FROM orders
ORDER BY customer_id, total_amount DESC;

-- This would cause an error:
-- ORDER BY order_date DESC  (missing customer_id at the start)

每位学生的最高分

下面是使用 test_scores 表的另一个实用示例。我们想找出每位学生曾取得的最高分。在每个学生分组内按 score DESC 排序后,DISTINCT ON 只会返回每位学生分数最高的那一行。

CREATE TABLE test_scores (
  id         SERIAL PRIMARY KEY,
  student_id INT,
  subject    VARCHAR(50),
  score      INT,
  taken_on   DATE
);

INSERT INTO test_scores (student_id, subject, score, taken_on) VALUES
  (101, 'Math',    92, '2024-02-10'),
  (101, 'Math',    78, '2024-04-15'),
  (102, 'Math',    85, '2024-02-10'),
  (102, 'Math',    91, '2024-04-15'),
  (103, 'Math',    67, '2024-02-10');

SELECT DISTINCT ON (student_id)
  student_id, subject, score, taken_on
FROM test_scores
ORDER BY student_id, score DESC;

包含多列的 DISTINCT ON

您可以在 DISTINCT ON 中列出多列,从而按多列进行分组。这会为这些列的每一种唯一组合返回一行。

下面的示例会选出每位学生在每个科目中的最高分,并将每个(学生、科目)对视为独立分组。

INSERT INTO test_scores (student_id, subject, score, taken_on) VALUES
  (101, 'Science', 88, '2024-03-01'),
  (101, 'Science', 95, '2024-05-20'),
  (102, 'Science', 72, '2024-03-01');

SELECT DISTINCT ON (student_id, subject)
  student_id, subject, score, taken_on
FROM test_scores
ORDER BY student_id, subject, score DESC;

使用 WHERE 进行筛选

DISTINCT ON 可以自然地与 WHERE 子句配合使用。筛选条件会先应用,然后 DISTINCT ON 从筛选结果中为每个分组选择一行。

这里我们要找出每位客户的最新订单,但只考虑金额超过 100 的订单。

SELECT DISTINCT ON (customer_id)
  customer_id,
  order_id,
  order_date,
  total_amount
FROM orders
WHERE total_amount > 100
ORDER BY customer_id, order_date DESC;

DISTINCT ON 与 GROUP BY 的比较

DISTINCT ON 和 GROUP BY 都可以为每个分组生成一行,但用途不同:

  • GROUP BY 会合并行,并且对于未分组的列,需要使用聚合函数(SUM、MAX 等)。
  • DISTINCT ON 会保留实际存在的一行——无需聚合即可使用该行的所有列。

当您需要聚合值时,请使用 GROUP BY;当您需要每个分组中特定行的完整数据时,请使用 DISTINCT ON。

-- GROUP BY: only aggregated columns allowed
SELECT customer_id, MAX(order_date) AS latest_date
FROM orders
GROUP BY customer_id;

-- DISTINCT ON: returns the whole row for that latest date
SELECT DISTINCT ON (customer_id)
  customer_id, order_id, order_date, total_amount
FROM orders
ORDER BY customer_id, order_date DESC;

在子查询中使用 DISTINCT ON

有时您需要在 DISTINCT ON 的结果之上进一步筛选或排序。由于外层 ORDER BY 与分组列绑定,您可以将查询包裹在子查询(或 CTE)中,以便对最终输出应用不同的排序。

此示例先选择每位客户的最新订单,然后按 total_amount 降序排列最终结果。

SELECT *
FROM (
  SELECT DISTINCT ON (customer_id)
    customer_id,
    order_id,
    order_date,
    total_amount
  FROM orders
  ORDER BY customer_id, order_date DESC
) AS latest_orders
ORDER BY total_amount DESC;

快速检查

让我们检查您对 DISTINCT ON 的理解。请仔细阅读下面的查询,并选择最准确描述其返回结果的答案。

SELECT DISTINCT ON (department_id) department_id, employee_name, salary FROM employees ORDER BY department_id, salary DESC;

课程回顾

做得很好!以下是您对 PostgreSQL DISTINCT ON 的学习总结:

  • DISTINCT ON (col) 会为指定列(一个或多个)的每个唯一值恰好返回一行。
  • ORDER BY 子句必须以 DISTINCT ON 中列出的相同列开头——它决定从每个分组中选择哪一行。
  • 您可以使用多列:DISTINCT ON (col1, col2) 会按照两列的组合进行分组。
  • 与 GROUP BY 不同,DISTINCT ON 会返回一行真实存在且包含其所有原始列的数据——无需聚合。
  • 当您需要按其他列对最终输出排序时,请将查询包裹在子查询中。

DISTINCT ON 是 PostgreSQL 特有的功能,也是解决“每组选择一行”问题的最简洁方式之一。

常见问题解答

「PostgreSQL DISTINCT ON」课时是免费的吗?

是的 — 「PostgreSQL DISTINCT ON」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Academy 课程的其余内容,请升级到 CoddyKit PRO。 SQL Academy 课程共包含 4 节课。

「PostgreSQL DISTINCT ON」这节课中我会学到什么?

每个分组选择一行 你通过在浏览器中直接运行的动手代码来练习 SQL Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Academy 需要有经验吗?

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

「PostgreSQL DISTINCT ON」课时需要多长时间?

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

我能在这节 SQL Academy 课中编写并运行代码吗?

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

此课程中的所有课时

  1. SELECT DISTINCT 基础
  2. 多列上的 DISTINCT
  3. PostgreSQL DISTINCT ON
  4. 统计不重复值
← 返回 SQL Academy