删除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表,其结构与employee表完全相同
- 第2 行将 UNIQUE约束添加到新的tmp_eployee表中,以避免任何进一步的重复
- 第3行通过id 扫描原始的雇员表,将新的雇员条目插入新的tmp_eployee表中,而忽略重复的条目
- 第4行重命名表,以便新的employee表保留所有条目,而不包含重复项,并且将前一个数据的备份副本保留在backup_employee表上
⇒ 使用这种方法,在不到200s的时间内将1.6M寄存器转换为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;