distinct是select的修饰符,必须紧随其后且不加括号,作用于整行去重;错误写法如select distinct(name)会报语法错误;它不支持单列去重多列返回,null被视为相同值。

DISTINCT 只能放在 SELECT 后紧跟着,不能加括号
很多人写成 SELECT DISTINCT(name) 或 SELECT DISTINCT (id, name),这是错的。SQL 标准里 DISTINCT 是一个查询修饰符,不是函数,后面不跟括号,也不作用于单个字段。正确写法是 SELECT DISTINCT name 或 SELECT DISTINCT id, name —— 它对整行生效,只要组合值完全相同,就只保留一行。
常见错误现象:ERROR: syntax error at or near "("(PostgreSQL)或类似解析失败提示。
-
DISTINCT必须紧跟在SELECT后,中间不能有换行或空格干扰(某些旧版 MySQL 对空白敏感) - 如果想对单列去重但查多列,得用子查询或窗口函数,
DISTINCT本身不支持“按某列去重、返回其他列任意值”这种逻辑 - MySQL 5.7+ 默认开启
ONLY_FULL_GROUP_BY,此时SELECT DISTINCT a, b FROM t是合法的,但SELECT DISTINCT a, MAX(b) FROM t会报错 —— 因为MAX()是聚合,而DISTINCT不是聚合上下文
DISTINCT 和 GROUP BY 在语义和性能上并不等价
虽然 SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b 返回结果一样,但数据库执行方式可能不同。前者通常走哈希去重或排序去重;后者明确触发分组逻辑,可能提前应用索引或影响后续聚合。
使用场景差异:
- 纯去重选字段 → 优先用
DISTINCT,语义清晰,可读性强 - 后续要接
HAVING、聚合计算、或需要利用索引加速分组 → 用GROUP BY更可控 - 某些引擎(如 ClickHouse)对
GROUP BY优化更好,DISTINCT可能强制全量内存哈希,大数据量时 OOM 风险更高 - Oracle 中
DISTINCT内部常转为GROUP BY执行,但执行计划里仍显示为DISTINCT
带 ORDER BY 时,DISTINCT 的字段必须出现在 SELECT 列表中
这是 SQL 标准强制要求。比如 SELECT DISTINCT name FROM users ORDER BY created_at 会报错,因为 created_at 没出现在 SELECT 结果里,数据库无法确定每组重复行该按哪个 created_at 排序。
解决方法只有两个:
- 把排序字段也放进
SELECT DISTINCT:例如SELECT DISTINCT name, created_at FROM users ORDER BY created_at—— 但这改变了语义,变成按(name, created_at)组合去重 - 改用子查询或窗口函数:先去重,再对外层结果排序,例如
SELECT name FROM (SELECT DISTINCT name FROM users) t ORDER BY name - PostgreSQL 支持
DISTINCT ON(非标准),可以写SELECT DISTINCT ON (name) name, created_at FROM users ORDER BY name, created_at DESC,但仅限 PostgreSQL,其他数据库不兼容
NULL 值在 DISTINCT 中被视为相同值
所有主流 SQL 引擎(MySQL、PostgreSQL、SQL Server、Oracle)都把多个 NULL 当作相等来处理。所以 SELECT DISTINCT status FROM orders 中,哪怕有 100 行 status 是 NULL,结果里也只出现一个 NULL。
这点容易被忽略,尤其在统计“状态分布”时:
- 如果业务上想区分“未设置”和“明确为空”,就不能依赖字段是否为
NULL,得加额外标记字段 -
COUNT(DISTINCT status)会把所有NULL当作一个值计数,结果比实际非空状态数少 1(如果存在 NULL) - 想排除 NULL 再去重?得显式过滤:
SELECT DISTINCT status FROM orders WHERE status IS NOT NULL
DISTINCT 很难干净实现,得换思路。











