如何用SQL语句过滤、删除表中的重复记录?

来源:前端技术作者:孙悟空头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何用SQL语句过滤、删除表中的重复记录?》,敬请观看详情。一张用户表中混进了多条完全一样的注册数据,不仅让统计失真,还可能导致接口返回异常结果。处理这类问题不能靠手工翻库,得用稳定的SQL逻辑。常见做法是借助主键或唯一标识配合分组查询,先找出冗余行,再执行精准删除。比如在MySQL里用DELETE JOIN锁定多余副本,或在PostgreSQL中用ROW_NUMBER开窗函数打标后清理。不同数据库语法略有差异,但核心思路都是先定位重复键、保留一行、剔除其余。理清GROUP BY与窗口函数的适用边界,才能避免误删有效数据。

在业务数据库的日常运转中,表中出现重复记录是十分常见的数据质量问题。重复可能来自程序bug、多次导入、缺乏唯一约束等。要解决它,核心目标只有两个:一是把重复数据查出来,也就是过滤;二是把多余的副本删掉,也就是删除。下面我们以几种主流数据库为例,详细拆解具体操作。

如何用SQL语句过滤、删除表中的重复记录?

一、什么是表中的重复记录

通常所说的重复记录,是指表中若干行在业务意义上完全一样,或者在某些关键列(如用户名、邮箱、订单号)上取值相同,而数据库因为没有唯一索引允许它们共存。例如下面这张用户表,name和email都相同,但id不同,这就是典型的重复:

CREATE TABLE user_info (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  email VARCHAR(100)
);

INSERT INTO user_info VALUES
(1, '张三', 'zhangsan@ipipp.com'),
(2, '张三', 'zhangsan@ipipp.com'),
(3, '李四', 'lisi@ipipp.com'),
(4, '李四', 'lisi@ipipp.com');

上面的数据中,id为1和2的行是重复,id为3和4的行也是重复。我们需要保留其中一行,删除另一行。如果不过滤和清理,后续做COUNT统计或关联查询时就会出现数据翻倍。

需要注意的是,重复记录的判定维度由业务决定。有时整行一致才算重复,有时仅某几列一致就算重复。因此在写SQL前,必须先明确重复的定义,否则容易误删有效数据。

二、使用GROUP BY过滤重复记录

过滤重复最直观的办法是使用GROUP BY配合聚合函数,把重复键聚合起来,只展示每组的一条。以下语句可以查出每个name和email组合中最小的id,也就是我们想保留的那一行:

SELECT MIN(id) AS keep_id, name, email
FROM user_info
GROUP BY name, email;

这条语句不会改动表,只是把重复组压缩成一行,常用于先确认重复范围。如果你想看到哪些id是多余的,可以用NOT IN反向查询:

SELECT *
FROM user_info
WHERE id NOT IN (
  SELECT MIN(id)
  FROM user_info
  GROUP BY name, email
);

上面这个结果集就是所有应该被删除的重复副本。GROUP BY方式简单易懂,适合数据量不大、重复逻辑清晰的场景。但它的局限在于只能保留聚合出来的那一行,无法在删除时灵活控制保留哪一行(比如保留时间最新的)。

三、在MySQL中删除重复记录

MySQL不支持直接在子查询里DELETE同一张表,因此常用DELETE JOIN来绕过限制。下面语句会删除掉每组重复中id不是最小的那一行:

DELETE u1
FROM user_info u1
JOIN user_info u2
  ON u1.name = u2.name
 AND u1.email = u2.email
 AND u1.id > u2.id;

逻辑上,u2取每组中id最小的那条,u1是其余id更大的副本,通过JOIN把副本找出来并删除。执行后表中每个name和email只留最小id的一条。

这种写法效率较高,因为利用了等值连接。但如果表很大,建议先给name和email加联合索引,否则JOIN会做全表扫描。另外,操作前务必用前面的SELECT语句备份或确认待删数据,防止误删。

四、使用窗口函数删除重复记录

PostgreSQL、SQL Server、Oracle等支持窗口函数,可以用ROW_NUMBER给同组记录编号,再删掉编号大于1的。示例如下:

DELETE FROM user_info
WHERE id IN (
  SELECT id FROM (
    SELECT id,
           ROW_NUMBER() OVER (
             PARTITION BY name, email
             ORDER BY id
           ) AS rn
    FROM user_info
  ) t
  WHERE rn > 1
);

内层查询按name和email分区,同组内按id排序,rn=1是保留行,rn>1是重复副本。外层DELETE把这些副本删掉。窗口函数比GROUP BY灵活,ORDER BY可以换成创建时间等字段,从而保留最新的一条。

这种写法逻辑清晰、可控性强,适合复杂重复规则。但要注意,部分旧版本MySQL不支持窗口函数,此时仍需用JOIN方案。无论哪种方式,删除前都应在事务里先SELECT验证。

五、防止重复记录再次产生

删除只是补救,根本解决是加约束。可以在表上建唯一索引,让数据库拒绝重复写入:

ALTER TABLE user_info
ADD CONSTRAINT uk_name_email UNIQUE (name, email);

有了唯一索引后,再次插入相同name和email会报错,从源头阻断重复。如果历史数据已存在重复,需先清理再建索引,否则建索引会失败。

此外,在应用层写入前做查询校验、使用UPSERT语法(如MySQL的INSERT ... ON DUPLICATE KEY UPDATE)也是常用手段。把数据库约束和应用逻辑结合起来,才能长期保持数据干净。

六、总结对比

不同方案各有适用面,可用下表快速对照:

方案适用库优点缺点
GROUP BY过滤所有SQL库简单直观仅查不删,删需配合子查询
DELETE JOINMySQL删除效率高语法不通用
窗口函数PG、SQL Server等灵活保留指定行旧MySQL不支持

实际处理时,先明确重复定义,再选过滤与删除语句,最后补上唯一约束,才能稳妥解决重复记录问题。

SQL重复记录DELETE修改时间:2026-08-04 11:15:31

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