DISTINCT对整行生效,去重的是所有SELECT字段组成的元组而非单个字段;它不支持分组优先级或聚合逻辑,需用GROUP BY或窗口函数实现Top-N需求。

SELECT DISTINCT 对整行生效,不是单个字段
DISTINCT 作用于 SELECT 后面的整个字段列表,它去重的是「元组」(即这一行所有列组成的组合),不是对某一个字段单独去重。比如 SELECT DISTINCT a, b FROM t 返回的是所有不重复的 (a, b) 对,哪怕 a 值大量重复,只要搭配的 b 不同,就算不同行。
常见误解是以为 DISTINCT a, b 等价于 “先去重 a,再在每个 a 下去重 b”,实际完全不是——它不做分组、不保留任意“代表性”值,只筛掉完全相同的行。
实操建议:
- 如果只想看
a有哪些唯一值,写成SELECT DISTINCT a FROM t,别多带b - 如果需要
a唯一且附带某个b(比如最新时间对应的b),DISTINCT无能为力,得用GROUP BY+ 聚合函数或窗口函数 - 注意
NULL:在多数数据库中,NULL = NULL判定为 false,但DISTINCT把所有NULL视为相同值,会合并
想保留某字段唯一 + 关联其他字段?别硬套 DISTINCT
典型需求如:“每个用户只取一条记录,优先取 status=1 的,没有就取 status=0 的”,或者“每个 category 取 price 最高的商品”。这类问题本质是「按某字段分组后取 Top-N」,DISTINCT 无法表达优先级或聚合逻辑。
正确路径取决于数据库能力:
- 支持窗口函数(PostgreSQL / SQL Server / MySQL 8.0+):用
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC),外层筛选rn = 1 - MySQL 5.7 或 SQLite:用相关子查询或
GROUP BY+MAX()配合JOIN回原表(需确保price不重复,否则可能漏行) - 避免写
SELECT DISTINCT ON (category) *(PostgreSQL 特有)当跨库迁移时——它不可移植,且语义易被误读为“按 category 去重”,实际是“每组取排序后第一行”
DISTINCT 和 GROUP BY 在语义与性能上不等价
DISTINCT 是结果集操作,GROUP BY 是分组聚合操作。即使二者查出相同结果(如 SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b),底层执行计划、索引利用、内存消耗都可能不同。
关键差异点:
-
GROUP BY允许跟聚合函数(COUNT(*),AVG(x)),DISTINCT不允许 - 某些数据库(如旧版 MySQL)在
GROUP BY中未出现在SELECT列表里的非聚合字段会报错;DISTINCT没这限制 - 有合适复合索引时,
GROUP BY a, b可能走索引扫描,而DISTINCT a, b可能触发临时表 + 文件排序(尤其数据量大时) -
SELECT DISTINCT a FROM t WHERE b > 10可能用到(b, a)索引;但SELECT DISTINCT a, b FROM t WHERE b > 10就不一定能跳过排序阶段
ORDER BY 和 DISTINCT 共用时的隐含约束
加 ORDER BY 不影响 DISTINCT 的去重逻辑,但数据库要求 ORDER BY 的字段必须出现在 SELECT 列表中(标准 SQL 要求),否则报错,例如:
SELECT DISTINCT a FROM t ORDER BY b; -- 错误:b 不在 SELECT 列表
这是因为去重后结果集已不含 b,无法据此排序。解决方法只有两个:
- 把
b加进SELECT DISTINCT(但会改变去重粒度) - 改用子查询:
SELECT a FROM (SELECT DISTINCT a, b FROM t) AS tmp ORDER BY b(注意:此时b可能有多个值对应同一a,排序结果不确定)
真正可控的排序 + 去重组合,往往得靠 GROUP BY 显式聚合出排序依据字段,比如 MAX(updated_at),再按它排序。
最常被忽略的一点:DISTINCT 不保证返回顺序,即使表有主键或索引,也不代表结果按插入顺序或主键顺序排列——显式 ORDER BY 才是唯一可靠手段。

















