# DISTINCT

> 去除重复行，并用 DISTINCT ON 为每组选择一行。

---

LLMS 索引： [llms.txt](/llms.txt)

---

`DISTINCT` 去除结果集中的重复行。去重时比较的是 SELECT 输出的整行：`DISTINCT a, b` 的含义是“`(a, b)` 的非重复组合”，而不是“非重复的 `a`，再随便取个 `b`”。PostgreSQL 还提供了它更强大的兄弟 `DISTINCT ON (expr)`，为 `expr` 的每个非重复值保留一行，很适合“每个 Y 对应的最新 X”这类查询。

## 本章要点 {#learn}

- 用 `DISTINCT` 去除重复项
- `DISTINCT` 如何作用于 SELECT 选择列表中的所有列
- PostgreSQL 特有的 `DISTINCT ON (expr)`
- `DISTINCT` 如何处理 NULL
- 何时应该改用 `GROUP BY`

## 示例 {#example}

```sql {num="8.1" caption="DISTINCT"}
-- Unique values in a column
SELECT DISTINCT name FROM fruits ORDER BY name LIMIT 5;

-- Distinct combinations of multiple columns
SELECT DISTINCT id, name FROM fruits ORDER BY id LIMIT 3;

-- DISTINCT ON: keep one row per distinct expression value.
-- Here: one row per first letter of the name, picking the smallest id.
SELECT DISTINCT ON (substring(name, 1, 1)) substring(name, 1, 1) AS letter, id, name
FROM fruits
ORDER BY substring(name, 1, 1), id;

-- DISTINCT treats NULL as a single value
SELECT DISTINCT n FROM (VALUES (1), (NULL), (NULL), (2)) AS t(n) ORDER BY n;
```

在我们的 `fruits` 表中，每个名称本来就不重复，因此 `DISTINCT name` 与 `SELECT name` 返回相同的行。有多列时，只有列出的值*全部*相同，`DISTINCT` 才会把两行视为重复行。

要查询“每组的第一行”，`DISTINCT ON (substring(name, 1, 1))` 是 PostgreSQL 的拿手好戏。`ON (...)` 中的表达式定义分组，`ORDER BY` 决定各组保留哪一行，也就是排序后的第一行。关键在于，`ON` 中的表达式必须出现在 `ORDER BY` 的开头，否则 PostgreSQL 无法保证你说的“第一行”是哪一行。

处理空值时，`DISTINCT` 把两个 NULL 视为相等。考虑到 `NULL = NULL` 的结果是未知，这有些违反直觉，但 SQL 标准就是这样规定的。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/distinct.sql postgres
    name
------------
 apple
 banana
 cherry
 date
 elderberry
(5 rows)
 ...
```

## 常见陷阱 {#pitfalls}

- `SELECT DISTINCT a, b FROM t` 与 `SELECT DISTINCT a, (b) FROM t` 含义相同，都是对 `(a, b)` 组合去重。给 `b` 加上括号，并不会让去重只作用于 `a`。
- `DISTINCT` 与 `ORDER BY` 会相互影响：`ORDER BY` 中的任何列如果不在 `DISTINCT` 列表中，就会报错（不能按一个在同一去重分组内可能有多个值的列来排序）。
- `DISTINCT ON` 不是标准 SQL。需要可移植性的代码应改用窗口函数（`ROW_NUMBER() OVER (PARTITION BY ...)`），后面的章节会介绍。
- 在大表上使用 `SELECT DISTINCT` 必须做排序或哈希；如果只想知道“是否有重复项”，可用 `GROUP BY ... HAVING COUNT(*) > 1` 或聚合。

> [!TIP] 提示
> `count(DISTINCT col)` 统计 `col` 中互不重复的非空值数量。因为要去重，它比 `count(*)` 慢，但无需子查询就能回答“有多少种不同的 X”。

> [!NOTE] 动手练习
> 再插入一行，名称使用已有的值（例如 `INSERT INTO fruits (id, name) VALUES (10, 'apple');`），然后运行 `SELECT DISTINCT name FROM fruits ORDER BY name;`，返回的行数应该不变。接着用 `DISTINCT ON (name)` 保留每个名称中 id *最大*的行（提示：`ORDER BY name, id DESC`）。

## 下载 {#downloads}

SQL 文件： [distinct.sql](/examples/source/distinct.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
