导读:本期聚焦于张立峰创作的《如何处理SQL重复数据删除_巧用DISTINCT与GROUP BY语句》,敬请观看详情。数据库表里出现重复数据是再常见不过的问题,可能是重复导入、并发写入或者缺少唯一约束导致的。本文围绕SQL去重的两个核心手段展开:DISTINCT关键字适合快速去掉查询结果中的重复行,GROUP BY则更适合需要配合聚合统计的场景。文章会详细讲解两者的语法差异、执行原理、性能对比,并结合MySQL、SQL Server等常见数据库,给出保留一条记录删除其余重复数据的实战写法,包括利用自增主键、窗口函数ROW_NUMBER以及临时表等多种方案,帮助你在不同业务场景下选对去重方法。

数据表里冒出重复记录,几乎是每个写SQL的人都绕不开的麻烦。原因五花八门:批量导入时没做校验、并发环境下两个请求同时插入、表设计时忘了加唯一索引,都可能让同一份数据在表里躺上两三遍。重复数据不仅占存储,更麻烦的是会让统计结果失真——比如统计用户数时一个人被算了三次。本文就来系统梳理SQL中处理重复数据的思路,重点讲清楚DISTINCT和GROUP BY这两个去重利器的用法区别,以及如何真正从物理上删除表里的重复行。

如何处理SQL重复数据删除_巧用DISTINCT与GROUP BY语句

一、DISTINCT关键字:最直接的去重方式

DISTINCT是SQL中专门用来消除查询结果重复行的关键字,写在SELECT后面。它的语义很直白:对结果集中完全相同的行只保留一条。这里的“完全相同”指的是SELECT列表中所有列的组合都一样,只要有任何一个列的值不同,就不算重复。

基础用法很简单,比如查询所有不重复的客户姓名:

SELECT DISTINCT customer_name
FROM orders;

DISTINCT也可以作用于多列,此时去重的粒度是这些列的组合。例如下面的语句返回所有不重复的“客户+城市”组合:

SELECT DISTINCT customer_name, city
FROM orders;

这里有个非常常见的误区需要提醒:DISTINCT并不是只对它紧跟的那一列生效,而是作用于整个SELECT列表。很多人以为SELECT DISTINCT customer_name, city只对customer_name去重,city随便取,这是错的。数据库会把两列拼在一起判断是否重复。如果确实只想对某一列去重而其他列随意取值,DISTINCT满足不了需求,需要改用GROUP BY加聚合函数,或者使用窗口函数。

从执行原理上看,大多数数据库会对DISTINCT查询进行排序或者哈希操作来识别重复行。当SELECT的列比较多、结果集很大时,这个排序或哈希的开销不小,性能上要有心理准备。可以观察执行计划,MySQL里通常会出现Using temporary或者Using filesort的字样,说明数据库创建了中间结构来辅助去重。

二、GROUP BY语句:去重兼统计的全能手

GROUP BY的本职工作是把数据按指定列分组,分组之后每组自然只剩一份,所以它天然具备去重效果。单纯从去重角度看,下面两条语句返回的行数是一样的:

SELECT customer_name FROM orders GROUP BY customer_name;
SELECT DISTINCT customer_name FROM orders;

但GROUP BY真正的价值在于分组之后可以做聚合统计。比如统计每个客户的订单数量和总金额,这是DISTINCT做不到的:

SELECT customer_name, COUNT(*) AS order_count, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_name;

配合HAVING子句,GROUP BY还能反过来筛出重复数据。这是排查重复问题的经典写法,强烈建议记住:

SELECT customer_name, COUNT(*) AS cnt
FROM orders
GROUP BY customer_name
HAVING COUNT(*) > 1;

这条语句把所有出现了不止一次的客户名列出来,并给出重复次数,是数据清洗前的第一步摸底操作。先知道重复有多少、分布如何,再决定怎么删,比上来就动手删数据稳妥得多。

关于GROUP BY还有一个老生常谈的问题:ONLY_FULL_GROUP_BY模式。在严格模式下,SELECT中出现的非聚合列必须出现在GROUP BY子句中,否则会直接报错。MySQL 5.7以后默认开启这个模式,所以从旧库迁移过来的SQL经常报SQLSTATE 42000一类的错误。遇到这种情况不要急着关掉严格模式,正确的做法是把列补进GROUP BY,或者用ANY_VALUE()函数显式声明取任意值,或者改用窗口函数来处理。

性能方面,GROUP BY和DISTINCT在单纯去重场景下的开销接近,很多优化器甚至会把DISTINCT查询改写成GROUP BY来执行。选择时主要看业务需求:只去重用DISTINCT语义更清晰,去重同时要统计就用GROUP BY。

三、实战进阶:删除表中的重复数据并保留一条

前面讲的都是查询层面的去重,但很多时候我们要的是把表里的重复行真正删掉。假设有一张用户表users,由于没有唯一约束,user_name出现了重复,目标是对每个user_name只保留id最小的一条,删掉其余。下面给出三种常用方案。

方案一:利用自增主键加子查询。重复记录的id一定不是该组中最小的id,用子查询把要删的id找出来:

DELETE FROM users
WHERE id NOT IN (
    SELECT * FROM (
        SELECT MIN(id) FROM users GROUP BY user_name
    ) AS t
);

注意这里套了一层衍生表,因为MySQL不允许在DELETE的子查询中直接引用正在删除的同一张表,包一层临时结果集就能绕过这个限制。SQL Server没有这个限制,可以直接写。另外如果数据量很大,NOT IN加子查询的效率会明显下降,可以改写成NOT EXISTS或JOIN的写法。

方案二:用JOIN连接删除,效率通常比NOT IN好:

DELETE u1 FROM users u1
JOIN users u2
  ON u1.user_name = u2.user_name
 AND u1.id > u2.id;

这条语句的思路是让每条重复记录与组内id更小的记录配对,凡是能配上对的(说明存在比自己id小的同类记录)就删除。逻辑清晰,执行计划走索引的话速度很快。

方案三:窗口函数ROW_NUMBER,适用于MySQL 8.0、SQL Server、PostgreSQL等支持窗口函数的数据库,也是最灵活的方案:

WITH ranked AS (
    SELECT id, user_name,
           ROW_NUMBER() OVER (PARTITION BY user_name ORDER BY id) AS rn
    FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);

ROW_NUMBER给每组重复记录按id编号,保留rn等于1的那条,删除其余。它的优势在于排序规则可以自由定制,比如每组保留创建时间最新的一条,把ORDER BY改成id DESC对应的字段即可,也可以扩展到按多列组合去重,只需要把PARTITION BY的列写全。SQL Server里CTE配合DELETE的语法略有不同,需要写成DELETE FROM ranked WHERE rn > 1的形式直接删CTE。

删除完成后别忘了收尾:给去重的列加上唯一索引,从根源上杜绝再次产生重复数据。比如ALTER TABLE users ADD UNIQUE KEY uk_user_name (user_name)。如果没有这一步,用不了多久重复数据又会悄悄回来,到时候又得重复一遍清洗流程。

四、方案选择与注意事项

整理一下选择思路。如果只是查询时想去掉重复行,列比较少就DISTINCT,需要聚合统计就GROUP BY;如果要物理删除重复数据,小表用NOT IN子查询最省事,大表优先JOIN或ROW_NUMBER方案,窗口函数在需要灵活控制保留规则时几乎是唯一选择。

还有几个坑值得提防。第一,删除前一定先备份,或者先用SELECT跑一遍删除条件,确认命中的就是要删的数据,再改成DELETE执行。第二,判断重复的列组合要和业务确认清楚,比如user_name加phone一起算重复和只看user_name,清洗结果完全不同。第三,大表删除建议分批进行,一次删几十万行可能锁表时间过长,影响线上业务,可以按主键范围分批提交。第四,如果数据库支持,用事务包裹删除语句,出错时还能回滚,给自己留条后路。

数据清洗从来不是一次性工作,把去重逻辑固化成定时任务或者直接上唯一约束,才是让表长期保持干净的根本办法。掌握DISTINCT和GROUP BY这对组合拳,再配上窗口函数这个进阶武器,面对重复数据基本可以从容应对了。

SQL去重DISTINCTGROUP BY修改时间:2026-09-16 22:25:11

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0916/58227.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。