distinct对整行去重,非单字段;仅当select所有列值完全相同时才合并,否则视为不同行。

SELECT DISTINCT 能去重,但只对整行生效
很多人以为 DISTINCT 是按某个字段去重,其实它作用于整个 SELECT 列表。只要两行在所有选中的列上完全一致,才会被合并成一行。
比如:SELECT DISTINCT name, age FROM users,会把 (“张三”, 25) 和 (“张三”, 25) 视为重复;但如果有一行是 (“张三”, 26),哪怕 name 相同,也会保留——因为整行不重复。
- 想按单个字段去重并取其他关联字段?
DISTINCT不够用,得配合GROUP BY或窗口函数 -
DISTINCT不能加条件(比如“只对 status=1 的记录去重”),需先WHERE再DISTINCT - 性能上,
DISTINCT本质是隐式GROUP BY,大数据量时可能触发临时表或文件排序
DISTINCT 和 GROUP BY 去重效果一样吗?
纯去重场景下,SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b 返回结果通常一致,但语义和约束不同:
-
DISTINCT是结果集操作,不关心聚合逻辑;GROUP BY是分组操作,后续可接MAX()、COUNT()等聚合函数 - 如果写
SELECT DISTINCT a, b, c FROM t,而c实际上依赖于a(比如c是a的描述字段),数据库可能报错(如 MySQL 5.7+ 严格模式),因为c不在DISTINCT键中且未聚合 - PostgreSQL 允许
DISTINCT ON (a)语法,能按某列“取第一行”,这是DISTINCT的扩展用法,但不是标准 SQL
去重后还要取最新/最全的一条记录,DISTINCT 不行
常见需求:每个用户只取一条记录,但要的是注册时间最新的那条。这时 DISTINCT 完全无能为力,因为它不支持“按某字段排序后取首行”。
正确做法是用窗口函数:
SELECT user_id, email, created_at
FROM (
SELECT user_id, email, created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM users
) ranked
WHERE rn = 1;
-
ROW_NUMBER()比RANK()更常用,避免并列排名带来的多条命中 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;老版本 MySQL 需用自连接或变量模拟
- 注意
PARTITION BY字段必须是你要“去重”的维度,ORDER BY决定哪条算“最新”
DISTINCT 在 JOIN 后容易误用导致结果膨胀或缩水
JOIN 多表后直接套 DISTINCT 是常见补救手段,但往往掩盖了真正问题:
- 如果主表一行关联从表多行,
DISTINCT会把本该展开的多行强行压成一行,丢失数据细节 - 反过来,如果 JOIN 条件松散(比如漏了
ON中的关键字段),DISTINCT可能掩盖笛卡尔积,让结果看起来“正常”但实际错误 - 更稳妥的做法:先确认 JOIN 逻辑是否正确,用
COUNT(*)和COUNT(DISTINCT 主键)对比,看是否存在一对多未预期膨胀
去重不是万能胶,尤其是涉及关联、排序、业务规则时,DISTINCT 往往只是表象,背后需要厘清数据关系和业务语义。










