SQL 删除重复行完全指南

重复数据会通过数据导入、应用程序错误或缺少约束等方式潜入数据库。清理重复数据是每个数据专业人员必备的关键技能。

本指南涵盖多种方法,从识别重复数据到安全地删除它们。

步骤 1:查找重复数据

在删除任何数据之前,让我们先识别问题所在。

使用 GROUP BY 统计重复数据

SQL 查询

sql 复制代码
-- 查找哪些邮箱有重复
SELECT 
  email,
  COUNT(*) as occurrence_count
FROM users_duplicates
GROUP BY email
HAVING COUNT(*) > 1;

查询结果示例

email occurrence_count
alice@example.com 2
bob@example.com 3

现在我们知道 alice@example.com 出现了 2 次,bob@example.com 出现了 3 次。

步骤 2:查看所有重复行

使用子查询获取完整的详细信息:

SQL 查询

sql 复制代码
-- 显示所有有重复的行
SELECT * 
FROM users_duplicates2
WHERE email IN (
  SELECT email 
  FROM users_duplicates2
  GROUP BY email
  HAVING COUNT(*) > 1
);

这个查询会返回所有重复记录的完整信息,帮助你决定保留哪一条。

策略 1:保留第一条(或最后一条)记录

最常见的方法是使用 ROW_NUMBER() 来标记要保留的重复记录。

使用 ROW_NUMBER 识别重复数据

SQL 查询

sql 复制代码
-- 标记重复:row_num > 1 表示重复
SELECT 
  *,
  ROW_NUMBER() OVER (
    PARTITION BY email 
    ORDER BY created_at
  ) as row_num
FROM users;

查询结果示例

id email name created_at row_num
1 alice@example.com Alice 2026-01-01 1
2 alice@example.com Alice 2026-01-02 2
3 bob@example.com Bob 2026-01-01 1
4 bob@example.com Bob 2026-01-02 2
5 bob@example.com Bob 2026-01-03 3

说明

  • row_num = 1:第一条记录(保留这条)
  • row_num > 1:重复记录(删除这些)

使用 CTE 删除重复数据

现在我们可以删除重复数据了:

sql 复制代码
-- SQLite 语法删除重复数据
DELETE FROM users
WHERE id IN (
  SELECT id FROM (
    SELECT 
      id,
      ROW_NUMBER() OVER (
        PARTITION BY email 
        ORDER BY created_at
      ) as row_num
    FROM users
  )
  WHERE row_num > 1
);

提示 :将 ORDER BY created_at 改为 ORDER BY created_at DESC 可以保留最新的记录而不是最早的。

策略 2:保留数据最完整的记录

有时你想保留"最完整"的记录:

sql 复制代码
-- 保留名字最长的记录(数据最多)
SELECT id FROM (
  SELECT 
    id,
    ROW_NUMBER() OVER (
      PARTITION BY email 
      ORDER BY LENGTH(name) DESC
    ) as row_num
  FROM users
)
WHERE row_num > 1;

这个方法适用于某些字段可能为空或不完整的情况。

策略 3:SELECT DISTINCT 到新表

对于重复数据较多的表,创建一个干净的副本通常更容易:

sql 复制代码
-- 创建只包含唯一记录的新表
CREATE TABLE users_clean AS
SELECT DISTINCT email, name
FROM users;

-- 或者保留每个重复组的第一条
CREATE TABLE users_clean AS
SELECT * FROM (
  SELECT 
    *,
    ROW_NUMBER() OVER (
      PARTITION BY email 
      ORDER BY created_at
    ) as rn
  FROM users
)
WHERE rn = 1;

优点

  • 不会影响原表
  • 可以先验证结果
  • 适合大量重复数据的场景

策略 4:聚合重复数据

有时重复数据有不同的值需要合并:

SQL 查询

sql 复制代码
-- 通过求和金额合并重复订单
SELECT 
  customer_id,
  order_date,
  SUM(amount) as total_amount,
  COUNT(*) as merged_count
FROM orders_duplicates
GROUP BY customer_id, order_date;

示例数据

原始数据(orders_duplicates):

customer_id order_date amount
101 2026-01-15 50
101 2026-01-15 30
102 2026-01-16 100

查询结果:

customer_id order_date total_amount merged_count
101 2026-01-15 80 2
102 2026-01-16 100 1

这种方法适用于需要保留数值总和或其他聚合信息的场景。

防止未来出现重复数据

添加 UNIQUE 约束

sql 复制代码
-- 防止邮箱重复
CREATE UNIQUE INDEX idx_unique_email ON users(email);

-- 或在创建表时添加
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT UNIQUE,
  name TEXT
);

使用 INSERT OR IGNORE

sql 复制代码
-- 跳过会创建重复的插入操作
INSERT OR IGNORE INTO users (email, name)
VALUES ('alice@example.com', 'Alice');

这个语句会在遇到重复值时静默跳过,而不是报错。

快速参考:选择哪种策略?

场景 最佳方法
保留最早的记录 ROW_NUMBER() ORDER BY created_at
保留最新的记录 ROW_NUMBER() ORDER BY created_at DESC
保留最完整的记录 ROW_NUMBER() ORDER BY LENGTH(field) DESC
合并数值 GROUP BY 配合 SUM/MAX
大量重复数据 创建新的干净表
防止未来重复 UNIQUE 约束

安全提示

  1. 始终先备份 :在运行 DELETE 之前先运行 SELECT 验证将要删除的内容。

  2. 使用事务 :将删除操作包装在 BEGIN/COMMIT 中,这样如果需要可以 ROLLBACK

sql 复制代码
BEGIN TRANSACTION;
-- 你的删除操作
DELETE FROM users WHERE ...;
-- 检查结果
SELECT COUNT(*) FROM users;
-- 如果正确就提交,否则回滚
COMMIT;
-- ROLLBACK;
  1. 检查外键:删除一行可能会破坏其他表中的引用关系。

  2. 记录已删除的行:在删除之前将它们插入到归档表中。

sql 复制代码
-- 先归档
INSERT INTO users_archive SELECT * FROM users WHERE ...;
-- 再删除
DELETE FROM users WHERE ...;

总结

删除重复数据是一个多步骤的过程:

  1. 识别 重复数据:使用 GROUP BY HAVING COUNT(*) > 1
  2. 检查它们以决定保留哪条
  3. 标记 重复数据:使用 ROW_NUMBER() OVER (PARTITION BY...)
  4. 删除 row_num > 1 的行
  5. 防止 未来重复:使用 UNIQUE 约束

掌握这些模式,你就能处理任何数据去重挑战。


相关文章推荐


本文转载自 www.hisqlboy.com

原文标题:Removing Duplicate Rows in SQL: A Complete Guide

原文链接:https://www.hisqlboy.com/blog/removing-duplicate-rows-sql

原作者:SQL Boy Team

转载日期:2026-02-08

相关推荐
工业甲酰苯胺2 小时前
spring-事务管理
数据库·sql·spring
全栈前端老曹2 小时前
【Redis】Redis 持久化机制 RDB 与 AOF
前端·javascript·数据库·redis·缓存·node.js·全栈
李慕婉学姐2 小时前
Springboot平安超市商品管理系统6sytj3w6(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
Elastic 中国社区官方博客2 小时前
易捷问数(NewmindExAI)平台解决 ES 升级后 AI 助手与 Attack Discovery 不正常问题
大数据·运维·数据库·人工智能·elasticsearch·搜索引擎·ai
瀚高PG实验室2 小时前
数据库意外中止,无法启动
数据库·瀚高数据库
Amarantine、沐风倩✨3 小时前
列表接口严禁嵌套 LISTAGG + REGEXP:一次 mission_label 性能事故复盘
java·数据库·sql
好好研究3 小时前
MyBatis - Plus(二)常见注解 + 常见配置
数据库·spring boot·mybatis·mybatis plus
PD我是你的真爱粉3 小时前
MySQL基础-DQL语句与多表查询
数据库·mysql
C#程序员一枚4 小时前
SqlServer如何创建全文索引
数据库·sqlserver