> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> DISTINCT 子句文档

# DISTINCT

如果指定了 `SELECT DISTINCT`，查询结果中将只保留唯一的行。也就是说，对于结果中所有完全相同的行，每组最终只会保留一行。

你可以指定哪些列的值必须唯一：`SELECT DISTINCT ON (column1, column2,...)`。如果未指定列，则会将所有列都纳入考虑。

考虑下表：

```text theme={null}
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 2 │ 2 │ 2 │
│ 1 │ 1 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
```

不指定列时使用 `DISTINCT`：

```sql theme={null}
SELECT DISTINCT * FROM t1;
```

```text theme={null}
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 1 │ 1 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
```

在指定列上使用 `DISTINCT`：

```sql theme={null}
SELECT DISTINCT ON (a,b) * FROM t1;
```

```text theme={null}
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
```

## DISTINCT 和 ORDER BY

ClickHouse 支持在同一个查询中，对不同的列分别使用 `DISTINCT` 和 `ORDER BY` 子句。`DISTINCT` 子句会在 `ORDER BY` 子句之前执行。

考虑下列表：

```text theme={null}
┌─a─┬─b─┐
│ 2 │ 1 │
│ 1 │ 2 │
│ 3 │ 3 │
│ 2 │ 4 │
└───┴───┘
```

查询数据：

```sql theme={null}
SELECT DISTINCT a FROM t1 ORDER BY b ASC;
```

```text theme={null}
┌─a─┐
│ 2 │
│ 1 │
│ 3 │
└───┘
```

按不同的排序方向选择数据：

```sql theme={null}
SELECT DISTINCT a FROM t1 ORDER BY b DESC;
```

```text theme={null}
┌─a─┐
│ 3 │
│ 1 │
│ 2 │
└───┘
```

行 `2, 4` 在排序前被截断。

编写查询时，请将这一实现上的特性考虑在内。

## NULL 处理

`DISTINCT` 对 [NULL](/zh/reference/syntax#null) 的处理方式相当于将 `NULL` 视为一个特定值，且 `NULL==NULL`。换句话说，在 `DISTINCT` 的结果中，包含 `NULL` 的不同组合都只会出现一次。这与 `NULL` 在大多数其他场景下的处理方式不同。

## 替代方案

也可以不使用任何聚合函数，而是对 `SELECT` 子句中指定的同一组值应用 [GROUP BY](/zh/reference/statements/select/group-by)，从而获得相同的结果。不过，与 `GROUP BY` 的做法相比，还是有几点不同：

* `DISTINCT` 可以与 `GROUP BY` 一起使用。
* 在外部执行开始之前，不带 [ORDER BY](/zh/reference/statements/select/order-by) 的查询一旦读到足够数量的不同行以满足 [LIMIT](/zh/reference/statements/select/limit)，就会停止执行。
* 在外部执行开始之前且省略 `ORDER BY` 时，不带 `ALL` 的 [`LIMIT ... AFTER ... UNTIL`](/zh/reference/statements/select/limit#limit-after-until) 范围同样可以在该范围结束后让查询停止执行。
* 在外部执行开始之前，数据块会在处理过程中直接输出。

## 外部内存中的 DISTINCT

`DISTINCT` 可以将临时数据写入磁盘，以处理过大、无法全部驻留在内存中的唯一值集合。这会带来额外的磁盘 I/O，并可能导致查询变慢。

有两个设置控制何时开始落盘：

* `max_bytes_before_external_distinct` 以字节为单位设置查询总内存的阈值，默认值为 `0` (禁用) 。
* `max_bytes_ratio_before_external_distinct` 设置 server 或 user 限制下可用内存的占比，该值在执行开始时测得。默认值为 `0.5`；若两种限制均不适用，则该设置不生效。

当两个阈值同时适用时，取较小者。将这两个设置都设为 `0` 即可禁用落盘。

`max_memory_usage` 不会影响该比例。若要相对于查询内存限制来配置落盘，请设置一个低于该限制的绝对阈值。例如，以下查询在 256 MiB 查询内存限制下使用 16 MiB 的落盘阈值：

```sql theme={null}
SELECT DISTINCT number % 1000000 AS id
FROM numbers(2000000)
SETTINGS
    max_bytes_before_external_distinct = 16777216,
    max_bytes_ratio_before_external_distinct = 0,
    max_memory_usage = 268435456;
```

这些阈值并不会限制内存使用量。请为其他查询处理以及落盘本身预留空间。在内存压力较大时，落盘也可能提前开始。

落盘之前即可返回行，若在此阶段已满足 `LIMIT`，查询可提前结束。
一旦开始落盘，就必须读完其余输入，才能返回剩下的结果。
如果查询包含 `ORDER BY`，这些结果将按请求的顺序返回。

当 `DISTINCT` 的输入已按其键的前缀排序时，不会发生落盘。但前缀相同的行若数量很大，仍可能占用大量内存。

与 `optimize_distinct_in_order` 一样，落盘可能会将二进制表示不同但比较结果相等的浮点值去重，包括 `0.0` 和 `-0.0`，或载荷不同的 `NaN` 值。
