优先用 distinct 去重;需聚合计算则必须用 group by;二者语义不同不可互换,混用易报错或漏数据;真正删重需用 delete 配合窗口函数或自连接。

重复数据到底该用 DISTINCT 还是 GROUP BY?
直接说结论:如果只是想查去重后的结果,优先用 DISTINCT;如果要对重复组做聚合计算(比如统计次数、取最新时间),必须用 GROUP BY。两者语义不同,不能互换,强行混用反而容易漏数据或报错。
常见错误是看到“有重复”就下意识加 GROUP BY,但没写聚合函数——MySQL 5.7+ 会直接报错 ERROR 1055,PostgreSQL 和 SQL Server 更严格,连语法都不通过。
-
DISTINCT是行级去重,作用于整个SELECT列表,比较的是所有选中字段的组合值 -
GROUP BY是分组操作,必须配合聚合函数(如COUNT()、MAX())使用,否则非分组字段无法确定取哪一行 - 性能上,
DISTINCT通常比GROUP BY轻量,尤其在无索引字段上,GROUP BY可能触发临时表和文件排序
用 GROUP BY 删除重复行时为什么不能只写 DELETE FROM t GROUP BY x?
因为标准 SQL 不支持直接对 GROUP BY 结果执行 DELETE。MySQL 虽然允许 DELETE ... JOIN 或子查询方式,但写错就会删掉全部或一条不剩。
安全做法是先定位保留的主键(比如每组留 MIN(id)),再删其余:
DELETE t1 FROM users t1 INNER JOIN users t2 WHERE t1.email = t2.email AND t1.id > t2.id;
这个写法依赖自连接,关键点:
- 条件
t1.id > t2.id确保只删“更大ID”的重复行,保留最小ID那条 - 务必给
email字段建索引,否则多表扫描极慢 - SQLite 不支持这种
DELETE ... JOIN语法,得改用ROWID子查询
DISTINCT 在多字段场景下容易误判“不重复”
DISTINCT 对 NULL 的处理常被忽略:在多数数据库中,NULL = NULL 为 false,但 DISTINCT 会把多个 NULL 当作相同值合并。这可能导致本该保留的记录被误去重。
例如:SELECT DISTINCT name, phone FROM customer,若两行都是 (‘张三’, NULL),会被当成重复删掉一条——但业务上可能希望保留所有 NULL 电话的记录。
- 解决方法:用
COALESCE(phone, CONCAT('null_', id))之类方式把NULL显式转成可区分值 - 或者换用
GROUP BY+ROW_NUMBER()窗口函数(需数据库支持),更可控 - 注意:MySQL 8.0+、PostgreSQL、SQL Server 支持窗口函数,但旧版 MySQL 只能靠变量模拟
真正要“删除物理重复行”,别只盯着 DISTINCT 和 GROUP BY
这两个都是查询级操作,不会改动原表。想真正删数据,得用 DELETE 配合子查询或 CTE,而子查询里是否加 DISTINCT 或 GROUP BY,取决于你如何定义“保留哪一条”。
最稳的通用方案(兼容 MySQL 8.0+/PostgreSQL):
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY updated_at DESC) rn FROM users ) DELETE FROM users WHERE id IN ( SELECT id FROM ranked WHERE rn > 1 );
这里的关键不是 DISTINCT 或 GROUP BY,而是用窗口函数明确指定“每组按更新时间倒序,只留第一条”。实际执行前,一定先用 SELECT 把 ranked CTE 查出来看一眼,确认删的是对的。
没有窗口函数的老版本 MySQL,就得靠自连接或临时表,逻辑更绕,出错概率更高——这时候花十分钟升级数据库,可能比调两小时 SQL 更省事。










