在 SQL 中删除表中的重复记录是一个常见的数据清洗操作。由于不同数据库系统(如 MySQL、PostgreSQL、SQL Server、Oracle 等)语法略有差异,下面我会以通用方法为主,并针对常见数据库给出具体示例。
一、通用思路要删除重复记录,通常需要:
识别重复行(基于一个或多个字段)保留其中一行(通常是最早插入的或具有最小/最大主键的那一行)删除其余重复行二、方法分类与示例方法1:使用窗口函数(推荐,适用于支持窗口函数的数据库)示例表结构代码语言:sql复制CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);插入一些重复数据:
代码语言:sql复制INSERT INTO employees VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Bob', 'bob@example.com'),
(3, 'Alice', 'alice@example.com'), -- 重复
(4, 'Charlie', 'charlie@example.com'),
(5, 'Bob', 'bob@example.com'); -- 重复删除重复(保留最小 id 的记录)代码语言:sql复制-- PostgreSQL / SQL Server / Oracle / MySQL 8.0+
DELETE FROM employees
WHERE id NOT IN (
SELECT MIN(id)
FROM employees
GROUP BY name, email
);或者使用窗口函数(更高效):
代码语言:sql复制-- 使用 CTE + ROW_NUMBER()
WITH duplicates AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY name, email ORDER BY id) AS rn
FROM employees
)
DELETE FROM employees
WHERE id IN (
SELECT id FROM duplicates WHERE rn > 1
);注意:MySQL 早期版本(<8.0)不支持在 DELETE 中直接引用自身表的子查询,需用临时表绕过。
方法2:自连接删除(适用于 MySQL < 8.0)代码语言:sql复制-- 删除重复,保留较小 id 的记录
DELETE e1 FROM employees e1
INNER JOIN employees e2
WHERE e1.id > e2.id
AND e1.name = e2.name
AND e1.email = e2.email;此语法仅适用于 MySQL(使用别名进行自连接删除)。
方法3:创建新表(通用但耗资源)适用于任何数据库,适合一次性清理大量重复数据:
代码语言:sql复制-- 1. 创建无重复的新表
CREATE TABLE employees_clean AS
SELECT MIN(id) AS id, name, email
FROM employees
GROUP BY name, email;
-- 2. 删除原表并重命名(或 TRUNCATE + INSERT)
DROP TABLE employees;
ALTER TABLE employees_clean RENAME TO employees;注意:此方法会丢失原表的索引、约束等,需重新创建。
方法4:使用 EXISTS(通用)代码语言:sql复制DELETE FROM employees e1
WHERE EXISTS (
SELECT 1
FROM employees e2
WHERE e2.name = e1.name
AND e2.email = e1.email
AND e2.id < e1.id
);此写法在 PostgreSQL、SQL Server、Oracle 中有效;MySQL 需加别名或改写。
三、不同数据库注意事项数据库
是否支持 ROW_NUMBER()
自连接删除语法
子查询限制
MySQL 8.0+
✅
✅
较少
MySQL <8.0
❌
✅(特殊语法)
不能直接删自己
PostgreSQL
✅
✅
支持
SQL Server
✅
✅
支持
Oracle
✅
✅
支持
SQLite
✅(3.25+)
❌(不支持多表 DELETE)
需用临时表
SQLite 示例(只能通过重建表):CREATE TABLE employees_new AS
SELECT MIN(id) AS id, name, email
FROM employees
GROUP BY name, email;DROP TABLE employees;
ALTER TABLE employees_new RENAME TO employees;
四、小结:最佳实践建议有主键/唯一ID:优先用 ROW_NUMBER() 或 MIN(id) 分组保留。无主键:先加临时 ID 列,再去重。大数据量:考虑分批删除,避免锁表或事务过大。生产环境:先备份,再执行删除!