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 反馈 — 无需本地设置。
此课程中的所有课时
- SELECT DISTINCT 基础
- 多列上的 DISTINCT
- PostgreSQL DISTINCT ON
- 统计不重复值