如何删除MySQL表上的重复项?


158

我需要DELETE为指定的sid复制行MySQL表。

如何使用SQL查询做到这一点?

DELETE (DUPLICATED TITLES) FROM table WHERE SID = "1"

这样的东西,但我不知道该怎么做。


您只需要执行一次还是需要一直执行?
Billy ONeal

具有重复记录的记录都具有相同的数据,还是其余字段彼此不同?如果选择第一个选项,则可以删除所有记录,但是如果选择第二个选项,则如何确定要保留的记录?
rael_kid 2010年

@Lex First选项。@Billy我需要一直做。
Ali Demirci '04


1
在各种版本的MySQL中,有很多事情已经改变。在此处跳过任何解决方案的路径之前,请仔细检查您的MySQL版本。
delatbabel

Answers:


215

这将删除重复项,而无需创建新表

ALTER IGNORE TABLE `table_name` ADD UNIQUE (title, SID)

注意:仅当索引适合内存时才有效


26
注意:这将保留最旧的重复记录,并删除较新的记录。如果您想保持最新,则不能使用ALTER IGNORE
哈拉兰·杜波夫

9
似乎不适用于InnoDB。我跑去ALTER TABLE foo ENGINE MyISAM解决它,之后又换了引擎。
马丁

13
这可能在MySQL> 5.5上失败,如果是这样,请使用“ set session old_alter_table = 1;”。和“设置会话old_alter_table = 0;” 之前和会后声明
chillitom


2
@delatbabel不建议使用的原因在您链接到的页面中给出。
Barmar

133

假设您有一个表格employee,其中包含以下列:

employee (first_name, last_name, start_date)

为了删除具有重复first_name列的行:

delete
from employee using employee,
    employee e1
where employee.id > e1.id
    and employee.first_name = e1.first_name  

1
其余记录在其复制组中将具有最大ID还是最小ID?
Frozen Flame》

其余的记录将具有最小的ID,因为它是不符合条件的唯一一个被删除
巴勃罗·格雷罗

1
对于大型表而言,似乎像employee针对一个索引匹配和>对一个索引的检查而对自身联接。SELECT MAX(ID) FROM t GROUP BY unique然后再JOIN匹配ID到完全匹配会更好MAX(ID)吗?
ebyrob '16

1
好答案!节省了我的时间!
Nesar

56

接下来,删除所有SID的重复项,而不仅仅是单个。

带临时表

CREATE TABLE table_temp AS
SELECT * FROM table GROUP BY title, SID;

DROP TABLE table;
RENAME TABLE table_temp TO table;

由于temp_table是刚创建的,因此没有索引。删除重复项后,您需要重新创建它们。您可以使用以下命令检查表中的索引SHOW INDEXES IN table

没有临时表:

DELETE FROM `table` WHERE id IN (
  SELECT all_duplicates.id FROM (
    SELECT id FROM `table` WHERE (`title`, `SID`) IN (
      SELECT `title`, `SID` FROM `table` GROUP BY `title`, `SID` having count(*) > 1
    )
  ) AS all_duplicates 
  LEFT JOIN (
    SELECT id FROM `table` GROUP BY `title`, `SID` having count(*) > 1
  ) AS grouped_duplicates 
  ON all_duplicates.id = grouped_duplicates.id 
  WHERE grouped_duplicates.id IS NULL
)

4
GROUP-ing对分组依据的字段的每种值组合仅产生一个结果行。因此重复项将被删除。
卡米尔·索佐特

4
我喜欢第一种方式,这里太优雅了!:B
AgelessEssence 2012年

1
@fiacre您可以暂时禁用外键检查:stackoverflow.com/questions/15501673/…您还可能冒着删除其他表引用的某些行的风险,但是您可以通过更改查询来控制将哪些记录选择到重复数据删除表中SELECT * FROM table GROUP BY title, SID;这完全取决于您对自己在做什么的了解。
卡米尔·索佐特

1
@ahnbizcad您可以使用临时表,但随后必须将数据从临时表复制回常规表。如果您使用真实表,则只需将具有重复项的旧表删除,然后重命名新表,而不必将重复项命名为旧表的名称。
卡米尔·索佐特

1
方法“无临时表”最接近最佳解决方案,但是请注意在MySQL 5.7.5中已更改的ONLY_FULL_GROUP_BY处理:dev.mysql.com/doc/refman/5.7/en/group-by-handling.html 我明白了通过将“ SELECT id”替换为“ SELECT ANY_VALUE(id)AS id”来工作
delatbabel

53

就地删除MySQL中的重复行(假设您有一个时间戳col进行排序)演练:

创建表并插入一些行:

create table penguins(foo int, bar varchar(15), baz datetime);
insert into penguins values(1, 'skipper', now());
insert into penguins values(1, 'skipper', now());
insert into penguins values(3, 'kowalski', now());
insert into penguins values(3, 'kowalski', now());
insert into penguins values(3, 'kowalski', now());
insert into penguins values(4, 'rico', now());
select * from penguins;
    +------+----------+---------------------+
    | foo  | bar      | baz                 |
    +------+----------+---------------------+
    |    1 | skipper  | 2014-08-25 14:21:54 |
    |    1 | skipper  | 2014-08-25 14:21:59 |
    |    3 | kowalski | 2014-08-25 14:22:09 |
    |    3 | kowalski | 2014-08-25 14:22:13 |
    |    3 | kowalski | 2014-08-25 14:22:15 |
    |    4 | rico     | 2014-08-25 14:22:22 |
    +------+----------+---------------------+
6 rows in set (0.00 sec)

删除重复项:

delete a
    from penguins a
    left join(
    select max(baz) maxtimestamp, foo, bar
    from penguins
    group by foo, bar) b
    on a.baz = maxtimestamp and
    a.foo = b.foo and
    a.bar = b.bar
    where b.maxtimestamp IS NULL;
Query OK, 3 rows affected (0.01 sec)
select * from penguins;
+------+----------+---------------------+
| foo  | bar      | baz                 |
+------+----------+---------------------+
|    1 | skipper  | 2014-08-25 14:21:59 |
|    3 | kowalski | 2014-08-25 14:22:15 |
|    4 | rico     | 2014-08-25 14:22:22 |
+------+----------+---------------------+
3 rows in set (0.00 sec)

完成后,将删除重复的行,并保留时间戳的最后一行。

对于那些没有时间戳或唯一列的人。

您没有timestamp或唯一的索引列作为排序依据?您正处于堕落状态。您必须执行其他步骤才能删除重复的行。

创建企鹅表并添加一些行

create table penguins(foo int, bar varchar(15)); 
insert into penguins values(1, 'skipper'); 
insert into penguins values(1, 'skipper'); 
insert into penguins values(3, 'kowalski'); 
insert into penguins values(3, 'kowalski'); 
insert into penguins values(3, 'kowalski'); 
insert into penguins values(4, 'rico'); 
select * from penguins; 
    # +------+----------+ 
    # | foo  | bar      | 
    # +------+----------+ 
    # |    1 | skipper  | 
    # |    1 | skipper  | 
    # |    3 | kowalski | 
    # |    3 | kowalski | 
    # |    3 | kowalski | 
    # |    4 | rico     | 
    # +------+----------+ 

复制第一个表并复制到该表中。

drop table if exists penguins_copy; 
create table penguins_copy as ( SELECT foo, bar FROM penguins );  

#add an autoincrementing primary key: 
ALTER TABLE penguins_copy ADD moo int AUTO_INCREMENT PRIMARY KEY first; 

select * from penguins_copy; 
    # +-----+------+----------+ 
    # | moo | foo  | bar      | 
    # +-----+------+----------+ 
    # |   1 |    1 | skipper  | 
    # |   2 |    1 | skipper  | 
    # |   3 |    3 | kowalski | 
    # |   4 |    3 | kowalski | 
    # |   5 |    3 | kowalski | 
    # |   6 |    4 | rico     | 
    # +-----+------+----------+ 

最大合计根据新的moo指数进行操作:

delete a from penguins_copy a left join( 
    select max(moo) myindex, foo, bar 
    from penguins_copy 
    group by foo, bar) b 
    on a.moo = b.myindex and 
    a.foo = b.foo and 
    a.bar = b.bar 
    where b.myindex IS NULL; 

#drop the extra column on the copied table 
alter table penguins_copy drop moo; 
select * from penguins_copy; 

#drop the first table and put the copy table back: 
drop table penguins; 
create table penguins select * from penguins_copy; 

观察和清理

drop table penguins_copy; 
select * from penguins;
+------+----------+ 
| foo  | bar      | 
+------+----------+ 
|    1 | skipper  | 
|    3 | kowalski | 
|    4 | rico     | 
+------+----------+ 
    Elapsed: 1458.359 milliseconds 

那条大的SQL delete语句在做什么?

别名为“ a”的表企鹅在称为别名“ b”的表企鹅的子集上保持连接。右表“ b”是一个子集,可找到由列foo和bar分组的最大时间戳[或max moo]。这与左表“ a”匹配。左侧的(foo,bar,baz)表格中的每一行。右侧子集“ b”具有(maxtimestamp,foo,bar),仅在最大的那一个上与左侧匹配。

非最大的每一行的maxtimestamp值为NULL。筛选掉这些NULL行,您将获得一组由foo和bar分组的所有行,这些行不是最新的时间戳baz。删除那些。

运行此表之前,请对其进行备份。

防止此问题在此表上再次发生:

如果您能做到这一点,那么它就会扑灭您的“重复行”。大。现在,在表上(这两列上)定义一个新的复合唯一键,以防止首先添加更多重复项。

就像一个好的免疫系统一样,插入时也不应该将坏行插入表中。后来,所有添加重复项的程序都会广播他们的抗议,而当您修复它们时,此问题就再也不会出现。


6
纯粹为马达加斯加参考提供参考!
Michael Wiggins 2015年

1
由于这是一个很好的答案和好的建议,因此获得了评价,谢谢Eric的工作比其他任何答案都更好。
约翰·约翰(Johan)2016年

4
注意:如果您的表具有自动递增ID列,则该ON子句只需要与该ID列匹配,就不需要别的了。
ebyrob

1
我喜欢详细的说明,但是...如果我理解正确,那么此答案将利用时间戳来区分记录。从这个意义上讲,记录不是重复的。如果您没有时间戳来区分记录,那2条或更多条记录的所有列都相同怎么办?
Rsc Rsc

1
@RscRsc如果没有时间戳列或唯一索引来应用最大聚合,则看起来您必须复制表,添加唯一索引,应用delete语句,然后将对应的表替换回原始表。我更改了答案以反映这些指示。
埃里克·莱斯钦斯基

16

我自己在一个巨大的数据库上遇到这个问题之后,对其他任何答案的性能都没有完全印象。我只想保留最新的重复行,并删除其余的行。

在没有临时表的单查询语句中,这对我来说效果最好,

DELETE e.*
FROM employee e
WHERE id IN
 (SELECT id
   FROM (SELECT MIN(id) as id
          FROM employee e2
          GROUP BY first_name, last_name
          HAVING COUNT(*) > 1) x);

唯一的警告是,我必须多次运行查询,但是即使如此,我发现它对我来说比其他选项更好。


1
务实的解决方案!为我工作-2m +行的innodb表大约需要20秒。一旦我使用了几次,然后发现重复次数很高的少数犯罪者,就手动完成了工作。
Troy Wray

1
一口气为我工作,太棒了!
Murwa '18

如果任何列的重复项超过2倍,则必须多次执行
PayteR

答案中指出的@PayteR:“唯一的警告是我必须多次运行查询”
seaders

13

这似乎总是为我工作:

CREATE TABLE NoDupeTable LIKE DupeTable; 
INSERT NoDupeTable SELECT * FROM DupeTable group by CommonField1,CommonFieldN;

这样,每个重复项和其余非重复记录中的ID最低。

我还采取了以下措施,以使删除后不再出现重复问题:

CREATE TABLE NoDupeTable LIKE DupeTable; 
Alter table NoDupeTable Add Unique `Unique` (CommonField1,CommonField2);
INSERT IGNORE NoDupeTable SELECT * FROM DupeTable;

换句话说,我创建了第一个表的副本,在我不希望重复的字段上添加了唯一索引,然后执行了一个Insert IGNORE具有不会像平常一样失败的优点Insert第一次尝试添加基于两个字段的重复记录,而忽略任何此类记录。

向前移动,就不可能基于这两个字段创建任何重复的记录。


1
您是否不需要输入ORDER BYSELECT以确保实际将哪个记录转交给NoDupeTable
ebyrob

@ebyrob我相信,除非另有指示,否则它将在没有其他条件的情况下选择最低的ID。当然ORDER by ID Asc可以,所以我还是要编辑我的答案。
user3649739

@ebyrob对不起,我不好。据我所知,在此选择中,排序依据无效。选择末尾的“排序依据”将仅对按每对中最低ID找到的重复项进行排序。或者,您可以做一个Select Max(ID),然后做,Order by Max(ID)但所有要做的就是颠倒插入的顺序。要获取最高的ID,我相信需要更复杂的选择联接,无论您如何在上方订购,都将从较低的ID获取字段值。
user3649739

其实,不确定我在想什么。你肯定会希望MAX(ID)或者MIN(ID)和列名,而不是*SELECT FROM DupeTable虽然,否则你会刚刚得到的一个ID的随机。实际上,许多SQL甚至MySQL严格要求在GROUP BY子句中未指定的每一列上调用聚合函数。
ebyrob '17

@ebyrob在测试Max(ID)Min(ID)时,除了返回Max或Mind记录的ID外,什么都不做。在每种情况下都获取相同的记录。因此,如果我有两个包含字段ID,First,Last,Notes和记录的记录1,Bob,Smith,NULL2,Bob,Smith,Arrears然后执行a SELECT *Max(ID), First,Last,Notes FROM DupeTable group by First,Last都将返回相同的记录1,但ID不同。Max(ID)将返回,2,Bob,Smith,NULL而Min(ID)将返回1,Bob,Smith,NULL。我相信要获得笔记中带有“欠款”的第二张唱片,需要参加。
user3649739'3

7

以下适用于所有表格

CREATE TABLE `noDup` LIKE `Dup` ;
INSERT `noDup` SELECT DISTINCT * FROM `Dup` ;
DROP TABLE `Dup` ;
ALTER TABLE `noDup` RENAME `Dup` ;

6

这是一个简单的答案:

delete a from target_table a left JOIN (select max(id_field) as id, field_being_repeated  
    from target_table GROUP BY field_being_repeated) b 
    on a.field_being_repeated = b.field_being_repeated
      and a.id_field = b.id_field
    where b.id_field is null;

它是一个很好的答案,除了一个小错误and a.id_field = b.id
Vikrant Goel

LEFT JOINb只需要比较b.id= a.id_field假设field_id是一个独特的自动递增ID。所以a.field_being_repeated = b.field_being_repeated是无关紧要的。(也b.id_field不会在此查询它的存在b.id
ebyrob

6

这对我来说可以删除旧记录:

delete from table where id in 
(select min(e.id)
    from (select * from table) e 
    group by column1, column2
    having count(*) > 1
); 

您可以将min(e.id)替换为max(e.id)以删除最新记录。


5
delete p from 
product p
inner join (
    select max(id) as id, url from product 
    group by url 
    having count(*) > 1
) unik on unik.url = p.url and unik.id != p.id;

1
我发现比上面的解决方案性能更高的解决方案
Christian Butzke

5

我在上面找到了Werner的解决方案是最方便的,因为无论主键是否存在,它都能工作,不会与表混淆,使用面向未来的纯SQL,这是很容易理解的。

正如我在评论中指出的那样,该解决方案尚未得到适当的解释。所以这是我的,基于此。

1)添加一个新的布尔列

alter table mytable add tokeep boolean;

2)在重复的列和新列上添加约束

alter table mytable add constraint preventdupe unique (mycol1, mycol2, tokeep);

3)将布尔列设置为true。由于新的限制,这将仅在重复的行之一上成功

update ignore mytable set tokeep = true;

4)删除未标记为保留的行

delete from mytable where tokeep is null;

5)删除添加的列

alter table mytable drop tokeep;

我建议您保留添加的约束,以便将来避免新的重复项。


4

此过程将删除表中的所有重复项(包括多个),并保留最后一个重复项。这是检索每个组中的最后一条记录的扩展

希望这对某人有用。

DROP TABLE IF EXISTS UniqueIDs;
CREATE Temporary table UniqueIDs (id Int(11));

INSERT INTO UniqueIDs
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields 
    AND T1.ID < T2.ID)
    WHERE T2.ID IS NULL);

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);

4

另一种简单的方法...使用UPDATE IGNORE:

U必须在一个或多个列上使用索引(类型索引)。创建一个新的临时参考列(不属于索引)。在此列中,您可以通过用ignore子句更新唯一性来标记唯一性。一步步:

添加一个临时参考列以标记唯一性:

ALTER TABLE `yourtable` ADD `unique` VARCHAR(3) NOT NULL AFTER `lastcolname`;

=>这会将一列添加到您的表中。

更新表,尝试将所有内容标记为唯一,但忽略由于重复的密钥问题而导致的可能的错误(将跳过记录):

UPDATE IGNORE `yourtable` SET `unique` = 'Yes' WHERE 1;

=>您会发现重复记录不会被标记为唯一='是',换句话说,每组重复记录中只有一个会被标记为唯一。

删除所有不唯一的内容:

DELETE * FROM `yourtable` WHERE `unique` <> 'Yes';

=>这将删除所有重复的记录。

放下列...

ALTER TABLE `yourtable` DROP `unique`;

我认为这是最好的解决方案,因为它不会与表混淆并且使用简单的简单sql。应该明确的一件事是:unique必须将列与当前正在复制的列一起添加到唯一约束中,否则整个事情将不起作用,因为SET unique='Yes'将永远不会失败。
xtian

另请注意,这unique是一个mysql关键字。因此,它必须具有反引号(如已正确显示)。在该列中使用另一个单词可能会更方便。
Torsten

2

删除MySQL表上的重复项是一个普遍的问题,通常伴随特定的需求。如果有人感兴趣,这里(在MySQL中删除重复的行)我将解释如何使用临时表以可靠,快速的方式删除MySQL重复项,这对于处理大数据源也是有效的(针对不同用例的示例)。

对于Ali,您可以运行以下命令:

-- create a new temporary table
CREATE TABLE tmp_table1 LIKE table1;

-- add a unique constraint    
ALTER TABLE tmp_table1 ADD UNIQUE(sid, title);

-- scan over the table to insert entries
INSERT IGNORE INTO tmp_table1 SELECT * FROM table1 ORDER BY sid;

-- rename tables
RENAME TABLE table1 TO backup_table1, tmp_table1 TO table1;

0
delete from `table` where `table`.`SID` in 
    (
    select t.SID from table t join table t1 on t.title = t1.title  where t.SID > t1.SID
)

这会在MySQL的某些配置和版本上生成SQL错误(1093)。
ebyrob

0

喜欢@eric的答案,但是如果您有一个很大的表,它似乎不起作用(The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay尝试运行该表时会得到提示)。因此,我将联接查询限制为仅考虑重复的行,最后得到:

DELETE a FROM penguins a
    LEFT JOIN (SELECT COUNT(baz) AS num, MIN(baz) AS keepBaz, foo
        FROM penguins
        GROUP BY deviceId HAVING num > 1) b
        ON a.baz != b.keepBaz
        AND a.foo = b.foo
    WHERE b.foo IS NOT NULL

在这种情况下,WHERE子句允许MySQL忽略没有重复项的任何行,并且如果这是重复项的第一个实例,也将忽略它,因此仅后续的重复项将被忽略。更改MIN(baz)MAX(baz)保留最后一个实例而不是第一个实例。


0

这适用于大表:

 CREATE Temporary table duplicates AS select max(id) as id, url from links group by url having count(*) > 1;

 DELETE l from links l inner join duplicates ld on ld.id = l.id WHERE ld.id IS NOT NULL;

要删除最早的变化max(id)min(id)


0

这将使该列成column_name为主键,同时忽略所有错误。因此它将删除具有重复值的行column_name

ALTER IGNORE TABLE `table_name` ADD PRIMARY KEY (`column_name`);

如前一个答案的注释中所述,这在5.7中不再起作用。
Barmar

0

我认为这基本上可以通过复制表并清空表,然后仅将不同的值放回表中来实现,但是在对大量数据进行处理之前,请仔细检查它。

创建表格的副本

创建表temp_table,如oldtablename;插入temp_table select * from oldtablename;

清空原始表格

从oldtablename删除*;

将所有不同的值从复制的表复制回原始表

INSERT oldtablename SELECT *从temp_table组按名字,姓氏,地址

删除临时表。

删除表temp_table

您需要按要保持不同的aLL字段分组。


0
DELETE T2
FROM   table_name T1
JOIN   same_table_name T2 ON (T1.title = T2.title AND T1.ID <> T2.ID)

它不能满足您的要求,请改善一下吗?
Samir Guiderk

0

这是我通常消除重复的方式

  1. 添加一个临时列,将其命名为任意名称(我将其称为活动列)
  2. 按您认为不应重复的字段分组,并将其活动字段设置为1,分组依据只会为该列选择重复值之一(不会选择重复项)
  3. 删除活动的零
  4. 下拉列处于活动状态
  5. (可选)(如果适合您的目的),为这些列添加唯一索引,以使它们不再重复

-2

您可以只使用DISTINCT子句来选择“清理”列表(是有关如何执行操作的非常简单的示例)。


那如何回答这个问题?使用DISTINCT您会失去关于复制品的所有信息。您能显示一种使用它删除重复项的方法吗?
luk2302

-3

如果您对它们进行计数,然后在删除查询中添加一个限制,仅保留一个,是否可以工作?

例如,如果您有两个或多个,则按如下方式编写查询:

DELETE FROM table WHERE SID = 1 LIMIT 1;

-5

从表中删除重复数据时,只有几个基本步骤:

  • 备份你的桌子!
  • 查找重复的行
  • 删除重复的行

这是完整的教程:https : //blog.teamsql.io/deleting-duplicate-data-3541485b3473


如果唯一ID不同,是否可以正常工作。您是sadece benzersiz idfarklıise de buişeyarar吗?
安德鲁(Andrew)

默认情况下,此处描述的方法不适用于MySQL版本> 5.7.5。这是因为处理了ONLY_FULL_GROUP_BY。在这里看到: dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
delatbabel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.