PostgreSQL 中 DISTINCT ON 的用法

FreeGuideOnline 最新 2026-07-06

sql SELECT DISTINCT ON (column1, column2, ...) column_list FROM table_name ORDER BY column1, column2, ..., criteria;


- `DISTINCT ON (表达式)` 中的括号不可省略。
- `ORDER BY` 必须以 `DISTINCT ON` 中出现的列作为开头,然后才能指定其他排序规则。
- 返回的行是每个分组中按 `ORDER BY` 排序后的第一行。

### 准备示例数据

假设有一个 `product_prices` 表,记录了商品在不同时间点的价格:

```sql
CREATE TABLE product_prices (
    id SERIAL PRIMARY KEY,
    product_name TEXT NOT NULL,
    price NUMERIC(10,2) NOT NULL,
    updated_at DATE NOT NULL
);

INSERT INTO product_prices (product_name, price, updated_at) VALUES
('Keyboard', 99.99, '2025-01-10'),
('Keyboard', 109.99, '2025-03-15'),
('Mouse', 49.99, '2025-02-01'),
('Mouse', 44.99, '2025-03-20'),
('Monitor', 299.99, '2025-01-25'),
('Monitor', 289.99, '2025-04-01');

获取每件商品的最新价格

我们希望查询每件商品最近一次更新的价格,也就是按 product_name 分组,每组取 updated_at 最新的那一行。

SELECT DISTINCT ON (product_name)
    product_name,
    price,
    updated_at
FROM product_prices
ORDER BY product_name, updated_at DESC;

结果:

product_name price updated_at
Keyboard 109.99 2025-03-15
Monitor 289.99 2025-04-01
Mouse 44.99 2025-03-20

观察 ORDER BY:先去重字段 product_name,再按 updated_at DESC 排序。每个 product_name 分组内,第一行就是最新日期的那条记录。

每个分组取多条记录?

DISTINCT ON 只保留每个分组的第一行。如果你需要每个分组的前 N 行,请使用窗口函数(如 ROW_NUMBER()),DISTINCT ON 无法直接实现。

必须遵循 ORDER BY 规则

DISTINCT ON 的结果高度依赖ORDER BY。如果省略 ORDER BY,或者 ORDER BY 不以去重列开头,查询会报错或产生不可预测的结果。

-- 错误:ORDER BY 不以 product_name 开头
SELECT DISTINCT ON (product_name) *
FROM product_prices
ORDER BY updated_at DESC;

PostgreSQL 会直接报错:

ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions

安全准则:始终让 ORDER BY 的开头列与 DISTINCT ON 中的列完全一致,并且顺序相同。然后你可以在后面添加任意排序字段来控制“哪一行胜出”。

省略 ORDER BY 虽然语法合法,但会随机返回每个分组的一行,结果不可靠,切勿在生产环境中使用。

高级技巧与场景

结合多个去重列

如果去重依据是多个列的组合,比如按 department_idjob_title 分组,取每个职位内工资最高的员工:

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

控制结果列的返回

DISTINCT ON 只影响行的选择,不会限制你在 SELECT 中写哪些列。被选中的行可以包含表中任何需要的字段。

与索引配合提升性能

如果查询频繁使用 DISTINCT ON 并按特定顺序排序,一个合适的索引可以显著加速。对于前文的商品价格查询:

CREATE INDEX ON product_prices (product_name, updated_at DESC);