distinct必须紧随select后作用于整行,不可修饰单字段或用于子句;去重基于字段组合全匹配,null视为相等;不能与聚合函数混用,order by字段须在select列表中;语义上不同于group by,且去重发生在最终结果集。

DISTINCT 只能放在 SELECT 后紧跟着,不能用在子句或表达式中间
DISTINCT 不是函数,也不是修饰某个字段的开关,它作用于整行结果。写成 SELECT name, DISTINCT age 或 SELECT DISTINCT(name) 都会报错——MySQL 会提示 ERROR 1064,PostgreSQL 直接拒绝解析。
正确写法只有一种:把 DISTINCT 紧跟在 SELECT 后面,后面接你要查的字段列表:
SELECT DISTINCT country, city FROM users;
这表示“返回所有唯一的 country+city 组合”,哪怕两个用户城市相同但国家不同,也会被当作不同行保留。
- 如果只查一个字段(如
SELECT DISTINCT status),它去重的是该字段值本身 - 如果查多个字段,去重逻辑基于字段组合的“全匹配”,不是单个字段独立去重
- ORDER BY 中引用的字段必须出现在 SELECT 列表里(尤其在启用了
ONLY_FULL_GROUP_BY的 MySQL 8.0+ 中)
用 GROUP BY 替代 DISTINCT 时要注意语义和性能差异
很多人发现 SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b 返回结果一样,就以为可以互换。其实不然:
-
DISTINCT是纯去重操作,不保证顺序,也不允许在 SELECT 中出现未分组字段(比如SELECT DISTINCT a, b, c FROM t会报错,除非c是确定值) -
GROUP BY是分组聚合的基础,后续可接MIN()、COUNT()等聚合函数;而DISTINCT后不能跟聚合(SELECT DISTINCT COUNT(*)是非法语法) - 某些场景下
GROUP BY比DISTINCT更慢,因为优化器可能强制排序;但也有反例——当有合适索引时,MySQL 对GROUP BY的松散索引扫描优化可能比DISTINCT更高效
带 WHERE 或 JOIN 时,DISTINCT 去重发生在最终结果集上
这是最容易误解的一点:DISTINCT 不会提前过滤掉“JOIN 过程中产生的重复中间行”,而是等所有 JOIN、WHERE 执行完,生成完整结果集之后才去重。
比如你查订单和用户关联数据:
SELECT DISTINCT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id;如果某个用户下了 5 单,这条语句仍会先生成 5 行(每行 u.id/u.name 相同),再合并成 1 行。这不是低效,而是设计如此。
- 若想减少中间膨胀,优先在 JOIN 条件或 WHERE 中加限制(例如
WHERE o.status = 'paid') - 若只是要用户列表,且不关心订单细节,考虑用
EXISTS替代 JOIN:SELECT id, name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id),通常更快也更直观 - 注意 NULL 值:所有 NULL 被视为相等,
DISTINCT会把多行NULL合并为一行
在窗口函数或子查询里不能直接用 DISTINCT 修饰部分列
有人想“只对某几列去重,其他列取任意值”,比如“每个部门取薪资最高的员工姓名”。这时不能写 SELECT DISTINCT dept, MAX(salary), name——语法错误,且语义不清。
正确解法取决于需求:
- 要最新一条记录?用
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY hire_date DESC)+ 外层过滤rn = 1 - 要聚合后附带某字段?必须明确规则,比如“取 salary 最高者中 id 最小的那条”:
SELECT dept, MAX(salary), MIN(CASE WHEN salary = MAX(salary) THEN id END)(需配合 GROUP BY) - 某些数据库支持
DISTINCT ON(PostgreSQL):SELECT DISTINCT ON (dept) dept, salary, name FROM employees ORDER BY dept, salary DESC,但这是 PostgreSQL 特有语法,不可移植
真正难的不是写对 DISTINCT,而是想清楚“你到底想保留哪一行”——这个逻辑一旦模糊,加再多 DISTINCT 也没用。











