删除 MySQL 表上的重复项是一个常见问题,这通常是缺少约束的结果,以避免事先避免这些重复项。但是这个常见问题通常伴随着特定的需求......确实需要特定的方法。方法应该有所不同,例如,数据的大小、应该保留的重复条目(通常是第一个或最后一个)、是否有要保留的索引,或者我们是否要执行任何额外的操作对重复数据的操作。
MySQL 本身也有一些特殊性,例如在执行表 UPDATE 时无法在 FROM 原因上引用同一个表(它会引发 MySQL 错误 #1093)。可以通过使用带有临时表的内部查询来克服此限制(如上面某些方法所建议的那样)。但是这种内部查询在处理大数据源时表现不佳。
但是,确实存在一种更好的方法来删除重复项,它既高效又可靠,并且可以轻松适应不同的需求。
一般的想法是创建一个新的临时表,通常添加一个唯一约束以避免进一步重复,并将以前表中的数据插入到新表中,同时注意重复。这种方法依赖于简单的 MySQL INSERT 查询,创建一个新的约束来避免进一步的重复,并且不需要使用内部查询来搜索重复项和应该保存在内存中的临时表(因此也适合大数据源)。
这就是它可以实现的方式。鉴于我们有一个表employee,其中包含以下列:
employee (id, first_name, last_name, start_date, ssn)
为了删除具有重复ssn列的行,并仅保留找到的第一个条目,可以遵循以下过程:
-- create a new tmp_eployee table
CREATE TABLE tmp_employee LIKE employee;
-- add a unique constraint
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
-- scan over the employee table to insert employee entries
INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id;
-- rename tables
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
技术说明
- 第 1 行创建一个新的tmp_eployee表,其结构与雇员表完全相同
- 第 2 行向新的tmp_eployee表添加 UNIQUE 约束以避免任何进一步的重复
- 第 3 行按 id 扫描原始员工表,将新员工条目插入新的tmp_eployee表,同时忽略重复的条目
- 第 4 行重命名表,以便新员工表包含所有条目而没有重复项,并且以前数据的备份副本保存在backup_employee表中
⇒使用这种方法,1.6M 寄存器在不到 200 秒的时间内转换为 6k。
Chetan,按照这个过程,您可以快速轻松地删除所有重复项并通过运行创建一个 UNIQUE 约束:
CREATE TABLE tmp_jobs LIKE jobs;
ALTER TABLE tmp_jobs ADD UNIQUE(site_id, title, company);
INSERT IGNORE INTO tmp_jobs SELECT * FROM jobs ORDER BY id;
RENAME TABLE jobs TO backup_jobs, tmp_jobs TO jobs;
当然,这个过程可以进一步修改,以适应删除重复项时的不同需求。下面是一些例子。
✔ 保留最后一个条目而不是第一个条目的变化
有时我们需要保留最后一个重复的条目而不是第一个。
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
INSERT IGNORE INTO tmp_employee SELECT * FROM employee ORDER BY id DESC;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- 在第 3 行,ORDER BY id DESC子句使最后一个 ID 优先于其余 ID
✔ 对重复项执行某些任务的变化,例如对找到的重复项进行计数
有时我们需要对找到的重复条目进行一些进一步的处理(例如保持重复的计数)。
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;
INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- 在第 3 行,创建了一个新列n_duplicates
- 在第 4 行,INSERT INTO ... ON DUPLICATE KEY UPDATE查询用于在发现重复项时执行附加更新(在这种情况下,增加计数器)INSERT INTO ... ON DUPLICATE KEY UPDATE查询可以是用于对找到的重复项执行不同类型的更新。
✔ 用于重新生成自动增量字段 id 的变化
有时我们使用自增字段,为了使索引尽可能紧凑,我们可以利用删除重复项在新临时表中重新生成自增字段。
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
INSERT IGNORE INTO tmp_employee SELECT (first_name, last_name, start_date, ssn) FROM employee ORDER BY id;
RENAME TABLE employee TO backup_employee, tmp_employee TO employee;
- 在第 3 行,不是选择表上的所有字段,而是跳过 id 字段,以便数据库引擎自动生成一个新字段
✔ 更多变化
许多进一步的修改也是可行的,具体取决于所需的行为。例如,以下查询将使用第二个临时表,除了 1) 保留最后一个条目而不是第一个条目;和 2) 增加对找到的重复项的计数器;也 3) 重新生成自动增量字段 id,同时保持输入顺序与以前的数据相同。
CREATE TABLE tmp_employee LIKE employee;
ALTER TABLE tmp_employee ADD UNIQUE(ssn);
ALTER TABLE tmp_employee ADD COLUMN n_duplicates INT DEFAULT 0;
INSERT INTO tmp_employee SELECT * FROM employee ORDER BY id DESC ON DUPLICATE KEY UPDATE n_duplicates=n_duplicates+1;
CREATE TABLE tmp_employee2 LIKE tmp_employee;
INSERT INTO tmp_employee2 SELECT (first_name, last_name, start_date, ssn) FROM tmp_employee ORDER BY id;
DROP TABLE tmp_employee;
RENAME TABLE employee TO backup_employee, tmp_employee2 TO employee;