导读:本期聚焦于高宇创作的《SQL 如何检测重复数据?常用方法与实战技巧详解》,敬请观看详情。数据表里出现重复记录是开发中经常遇到的问题,它可能导致统计结果偏差、存储浪费甚至业务逻辑错误。本文系统讲解SQL检测重复数据的几种常用手段,包括使用GROUP BY配合HAVING统计重复值的分布,利用COUNT窗口函数在不分组的前提下标记重复行,以及通过ROW_NUMBER为重复记录编号后筛选出需要保留或清理的数据。文章还结合员工表、订单表等实际场景给出可直接运行的SQL语句,并分析各方法在性能、可读性和后续去重操作上的差异,帮助你快速定位表中的重复数据,为下一步数据清洗打好基础。

在数据库的日常维护和业务开发中,重复数据是一个绕不开的话题。无论是因为程序bug、并发写入缺少约束,还是历史数据导入时没有做唯一性校验,重复记录一旦进入表里,轻则让统计报表出现偏差,重则破坏业务逻辑的正确性。想在清理之前先把这些问题数据找出来,就需要掌握几种靠谱的SQL检测手段。本文将从最常见的GROUP BY方案讲起,逐步延伸到窗口函数的用法,并给出不同场景下的选择建议。

SQL 如何检测重复数据?常用方法与实战技巧详解

一、使用 GROUP BY 加 HAVING 检测重复值

这是最经典也是最容易被理解的方案。思路很简单:按照可能重复的字段分组,统计每组的行数,凡是数量大于1的组,就说明该字段值出现了重复。HAVING子句在分组之后进行过滤,正好适合这种对聚合结果设置条件的场景。

假设有一张员工表employees,包含emp_noemp_nameemail等字段,我们怀疑邮箱存在重复录入,可以这样写:

SELECT email, COUNT(*) AS cnt
FROM employees
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY cnt DESC;

这条语句会列出所有出现次数大于1的邮箱以及各自的出现次数,按重复次数从高到低排列。如果想进一步查看这些重复邮箱对应的完整记录明细,可以用子查询或者内连接的方式把结果关联回原表:

SELECT e.*
FROM employees e
INNER JOIN (
    SELECT email
    FROM employees
    GROUP BY email
    HAVING COUNT(*) > 1
) t ON e.email = t.email
ORDER BY e.email;

需要注意的一点是,GROUP BY方案返回的是“哪些值重复了”,而不是“哪些行是多余的”。当你需要区分保留哪一条、删除哪一条时,它无法直接给出答案,这是它和后面要讲的窗口函数方案的核心区别。此外,如果要对多个字段的组合判断重复,比如同一个订单号加同一个商品号才算重复,只需要把这些字段都放进GROUP BY和SELECT里即可。

二、利用 COUNT 窗口函数标记每一行的重复情况

从MySQL 8.0、SQL Server等支持窗口函数的版本开始,可以用COUNT(*) OVER (PARTITION BY ...)来检测重复,它能保留明细行的同时给每一行打上重复计数标签,用起来更加直观。

还是以员工表为例,下面的语句会给每行计算同邮箱的记录总数:

SELECT
    emp_no,
    emp_name,
    email,
    COUNT(*) OVER (PARTITION BY email) AS email_cnt
FROM employees
ORDER BY email, emp_no;

结果中email_cnt大于1的行就是重复行。与GROUP BY相比,这种写法不需要关联原表就能拿到完整明细,一次查询就能看到重复记录的全貌,在排查问题时非常方便。如果想只看重复的行,把查询包装成子查询再加一层WHERE过滤即可:

SELECT *
FROM (
    SELECT
        e.*,
        COUNT(*) OVER (PARTITION BY email) AS email_cnt
    FROM employees e
) t
WHERE email_cnt > 1;

窗口函数的另一个优势是可以和ROW_NUMBER、RANK等函数组合使用,一次查询同时得到重复计数和行编号,为后续去重操作提供直接依据。不过要注意,窗口函数在MySQL 5.7及更早版本中不可用,如果项目还在使用旧版本数据库,只能退回到GROUP BY加自连接的方案。

三、用 ROW_NUMBER 精确定位需要处理的重复行

检测出重复值只是第一步,实际清洗数据时往往要回答“重复组里保留哪一条”的问题。ROW_NUMBER窗口函数可以在每个重复分组内按指定规则编号,编号大于1的行就是候选删除对象。

SELECT *
FROM (
    SELECT
        emp_no,
        emp_name,
        email,
        ROW_NUMBER() OVER (
            PARTITION BY email
            ORDER BY emp_no ASC
        ) AS rn
    FROM employees
) t
WHERE rn > 1;

这条语句按邮箱分组,组内按工号升序编号,工号最小的那条记录rn为1视为原始记录,其余行都是重复的、可以删除的。在SQL Server中可以直接借助CTE完成删除:

WITH dup AS (
    SELECT
        ROW_NUMBER() OVER (
            PARTITION BY email
            ORDER BY emp_no ASC
        ) AS rn
    FROM employees
)
DELETE FROM dup WHERE rn > 1;

MySQL中CTE不支持直接DELETE,可以建一张临时表保存要保留的主键,再按主键删除多余行。ROW_NUMBER方案的最大价值在于删除规则完全可控:想保留最新录入的记录,把ORDER BY改成按创建时间降序即可;想保留信息最完整的记录,可以按关键字段的非空程度排序。这种灵活性是单纯计数方案做不到的。

四、方法对比与预防重复的建议

三种方案各有适用场景。GROUP BY适合快速统计重复分布、输出汇总报告;COUNT窗口函数适合保留明细排查问题;ROW_NUMBER则面向需要精确去重的场景。下表做了简单对比:

方案能否保留明细能否定位多余行版本要求
GROUP BY + HAVING需关联原表不能所有版本
COUNT 窗口函数不能需支持窗口函数
ROW_NUMBER 窗口函数需支持窗口函数

除了学会检测,更重要的还是在源头预防重复。对于业务上必须唯一的字段,应该建立唯一索引或唯一约束,数据库会在写入时自动拦截重复值,这比事后清理可靠得多。同时,插入数据时优先使用带唯一约束冲突处理的写法,例如MySQL的INSERT ... ON DUPLICATE KEY UPDATEINSERT IGNORE,PostgreSQL的ON CONFLICT子句,都可以在并发场景下避免产生重复记录。

总结来说,检测重复数据并没有唯一的标准答案,关键是根据数据库版本、数据量和后续处理需求选择合适的工具。先通过GROUP BY了解重复的整体情况,再用ROW_NUMBER定位到具体行并制定保留规则,这套组合流程能够应对绝大多数数据清洗任务。清完之后别忘了加上唯一约束,让同类问题不再复发。

SQL重复数据GROUP BYROW_NUMBER修改时间:2026-09-01 18:46:32

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