在MySQL中删除重复的行


375

我有一个包含以下字段的表:

id (Unique)
url (Unique)
title
company
site_id

现在,我需要删除具有same的行title, company and site_id。一种方法是使用以下SQL以及脚本(PHP):

SELECT title, site_id, location, id, count( * ) 
FROM jobs
GROUP BY site_id, company, title, location
HAVING count( * ) >1

运行此查询后,我可以使用服务器端脚本删除重复项。

但是,我想知道是否只能使用SQL查询来完成。


1
快速问题:总是不希望重复(标题,公司,site_id)不存在吗?如果是这样,我将在数据库中设置一个约束,以将title,company和site_id强制为唯一。这意味着您不需要清理过程。而且只需要一行SQL。
J. Polfer

1
请参考这个stackoverflow的链接。它对我来说很有吸引力。

我可以推荐这种解决方案(发布在另一个线程中):stackoverflow.com/a/4685232/195835
Simon East

您也可以检查这个答案
Jose Rui Santos 2015年

Answers:


607

一种简单的方法是UNIQUE在3列上添加索引。在编写ALTER语句时,请包含IGNORE关键字。像这样:

ALTER IGNORE TABLE jobs
ADD UNIQUE INDEX idx_name (site_id, title, company);

这将删除所有重复的行。作为一项额外的好处,INSERTs重复的将来会出错。和往常一样,您可能需要在运行类似内容之前进行备份...


8
有趣的是,但是IGNORE子句删除这些重复项的假设可能与需求不符。截断最接近可接受匹配的不正确值听起来对您有好处吗?
OMG小马

75
仅作记录,如果您使用InnoDB,则可能有问题,关于在InnoDB数据库中使用ALTER IGNORE TABLE有一个已知的错误。
DarkMantis


42
对于InnoDB表,请首先执行以下查询:set session old_alter_table=1;
shock_one 2014年


180

如果您不想更改列属性,则可以使用以下查询。

由于您有一列具有唯一ID(例如,auto_increment列),因此您可以使用它来删除重复项:

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND (`a`.`title` = `b`.`title` OR `a`.`title` IS NULL AND `b`.`title` IS NULL)
    AND (`a`.`company` = `b`.`company` OR `a`.`company` IS NULL AND `b`.`company` IS NULL)
    AND (`a`.`site_id` = `b`.`site_id` OR `a`.`site_id` IS NULL AND `b`.`site_id` IS NULL);

在MySQL中,您可以使用NULL安全的相等运算符(也称为“太空飞船运算符”)来进一步简化它:

DELETE `a`
FROM
    `jobs` AS `a`,
    `jobs` AS `b`
WHERE
    -- IMPORTANT: Ensures one version remains
    -- Change "ID" to your unique column's name
    `a`.`ID` < `b`.`ID`

    -- Any duplicates you want to check for
    AND `a`.`title` <=> `b`.`title`
    AND `a`.`company` <=> `b`.`company`
    AND `a`.`site_id` <=> `b`.`site_id`;

3
该解决方案无法正常工作,我尝试制作一些重复的记录,并且它的操作类似于(影响20行),但是如果再次运行,它将显示(影响4行),依此类推,直到达到(影响0行)这有点可疑,这是最适合我的方法,它几乎相同,但是一次运行就可以了,我编辑了解决方案
Nassim 2015年

1
@Nassim:您必须做的事情与此答案有所不同,因为它对我来说非常适用(在MySQL中)。
劳伦斯·多尔

3
对于像我这样困惑的任何人,都需要NULL比较项,因为在MySQL中NULL不等于NULL。如果保证相关列不为NULL,则可以省略这些条款。
伊恩

3
是的,自MYSQL 5.7起,接受的答案不再有效,因此这应该是接受的答案,因为它是通用的,并且也不需要临时表创建。
那本

1
如果给定记录有很多副本(例如100个减少为1个),并且有很多条件的记录,则非常慢。建议改用stackoverflow.com/a/4685232/199364。恕我直言,总是使用链接的方法;它本身就是一种更快的技术。
制造商

78

MySQL对于引用要从中删除的表有限制。您可以使用临时表来解决该问题,例如:

create temporary table tmpTable (id int);

insert  into tmpTable
        (id)
select  id
from    YourTable yt
where   exists
        (
        select  *
        from    YourTabe yt2
        where   yt2.title = yt.title
                and yt2.company = yt.company
                and yt2.site_id = yt.site_id
                and yt2.id > yt.id
        );

delete  
from    YourTable
where   ID in (select id from tmpTable);

根据Kostanos在评论中的建议:
对于数据库非常大的情况,上面唯一的慢查询是DELETE。该查询可能更快:

DELETE FROM YourTable USING YourTable, tmpTable WHERE YourTable.id=tmpTable.id

3
@andomar,除非where子句中的字段之一包含空值,否则此方法工作正常。示例:sqlfiddle.com/#
编码器

1
插入SQL是昂贵的吗?我想知道,因为它在我的MySQL数据库中超时。
卡西欧(Cassio)2013年

4
如果您的数据库很大,这里唯一的慢查询就是DELETE查询。此查询可能会更快:DELETE FROM YourTable USING YourTable, tmpTable WHERE YourTable.id=tmpTable.id
Kostanos

@Kostanos不但DELETE,而且INSERT到了临时餐桌,我花了很长时间。因此create index tmpTable_id_index on tmpTable (id),至少对我而言,tmp表的索引可能会有所帮助。
Jiezhi.G

1
如果您的桌子很大,那么值得添加以下索引:create temporary table tmpTable (id int, PRIMARY KEY (id));
Dallas Clarke

44

如果该IGNORE语句不适用于我的情况,则可以使用以下语句:

CREATE TABLE your_table_deduped LIKE your_table;


INSERT your_table_deduped
SELECT *
FROM your_table
GROUP BY index1_id,
         index2_id;

RENAME TABLE your_table TO your_table_with_dupes;

RENAME TABLE your_table_deduped TO your_table;

#OPTIONAL
ALTER TABLE `your_table` ADD UNIQUE `unique_index` (`index1_id`, `index2_id`);

#OPTIONAL
DROP TABLE your_table_with_dupes;

1
如果您具有具有外键约束的innoDB设置,则效果很好。
magdmartin

@magdmartin,但是外部约束不会阻止表删除吗?
Basilevs 2014年

1
IGNORE声明对我不起作用,这对删除500万条记录非常有效。干杯。
Mauvis Ledford'Mar

32

删除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;

27

还有另一种解决方案:

DELETE t1 FROM my_table t1, my_table t2 WHERE t1.id < t2.id AND t1.my_field = t2.my_field AND t1.my_field_2 = t2.my_field_2 AND ...

4
这与他六个月前提交的@rehriff的答案有何不同?
劳伦斯·多尔

@LawrenceDol我想它更具可读性,而且我认为他的回答在我回答时并不相同,我认为他的回答得到了编辑。
Mostafa -T

1
嗯。对我来说太长了,而记录数却不多!

8

如果您有一个包含大量记录的大表,那么上述解决方案将无法正常工作或花费太多时间。然后我们有一个不同的解决方案

-- Create temporary table

CREATE TABLE temp_table LIKE table1;

-- Add constraint
ALTER TABLE temp_table ADD UNIQUE(title, company,site_id);

-- Copy data
INSERT IGNORE INTO temp_table SELECT * FROM table1;

-- Rename and drop
RENAME TABLE table1 TO old_table1, temp_table TO table1;
DROP TABLE old_table1;

6

我有针对SQLServer的此查询摘要,但我认为它几乎可以在其他DBMS中使用:

DELETE
FROM Table
WHERE Table.idTable IN  (  
    SELECT MAX(idTable)
    FROM idTable
    GROUP BY field1, field2, field3
    HAVING COUNT(*) > 1)

我忘了告诉您,此查询不会删除重复行中具有最低ID的行。如果这对您有用,请尝试以下查询:

DELETE
FROM jobs
WHERE jobs.id IN  (  
    SELECT MAX(id)
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING COUNT(*) > 1)

如果一个组中有两个以上重复项,那将不起作用。
OMG小马

11
不幸的是,MySQL不允许您从要删除的表中进行选择ERROR 1093: You can't specify target table 'Table' for update in FROM clause
Andomar

1
要解决该"You can't specify target table 'Table' for update in FROM..."错误,请使用:DELETE FROM Table WHERE Table.idTable IN ( SELECT MAX(idTable) FROM (SELECT * FROM idTable) AS tmp GROUP BY field1, field2, field3 HAVING COUNT(*) > 1)强制MySQL创建一个临时表。但是,在大型数据集中它的运行速度非常慢……在这种情况下,我将推荐Andomar的代码,它的速度要快得多。
lepe

6

更快的方法是将不同的行插入到临时表中。使用delete,我花了几个小时才从800万行的表中删除重复项。使用insert和distinct,仅用了13分钟。

CREATE TABLE tempTableName LIKE tableName;  
CREATE INDEX ix_all_id ON tableName(cellId,attributeId,entityRowId,value);  
INSERT INTO tempTableName(cellId,attributeId,entityRowId,value) SELECT DISTINCT cellId,attributeId,entityRowId,value FROM tableName;  
TRUNCATE TABLE tableName;
INSERT INTO tableName SELECT * FROM tempTableName; 
DROP TABLE tempTableName;  

1
您的第四行应该说TRUNCATE TABLE tableName,第五行应该说INSERT INTO tableName SELECT * FROM tempTableName;
Sana

5

一种易于理解且无主键的解决方案:

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;

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


1
这在mysql 5.7上确实很好用,那里接受的解决方案不再起作用
Robin31 '18

5

使用DELETE JOIN语句删除重复的行MySQL为您提供了DELETE JOIN语句,可用于快速删除重复的行。

以下语句删除重复的行并保留最高的ID:

DELETE t1 FROM contacts t1
    INNER JOIN
contacts t2 WHERE
t1.id < t2.id AND t1.email = t2.email;

5

我找到了一种简单的方法。(保持最新)

DELETE t1 FROM tablename t1 INNER JOIN tablename t2 
WHERE t1.id < t2.id AND t1.column1 = t2.column1 AND t1.column2 = t2.column2;

4

适用于所有情况的简单快捷:

CREATE TEMPORARY TABLE IF NOT EXISTS _temp_duplicates AS (SELECT dub.id FROM table_with_duplications dub GROUP BY dub.field_must_be_uniq_1, dub.field_must_be_uniq_2 HAVING COUNT(*)  > 1);

DELETE FROM table_with_duplications WHERE id IN (SELECT id FROM _temp_duplicates);

错误代码:1055。SELECT列表的表达式#2不在GROUP BY子句中,并且包含未聚合的列'dub.id',该列在功能上不依赖于GROUP BY子句中的列;这与sql_mode = only_full_group_by不兼容
Swoogan

您可以使用sql_mode禁用“硬控制”,请参见stackoverflow.com/questions/23921117/disable-only-full-group-by
artemiuz

4

这将删除标题,公司和站点具有相同值的重复行。第一次出现将保留,其余所有重复将被删除

DELETE t1 FROM tablename t1
INNER JOIN tablename t2 
WHERE 
    t1.id < t2.id AND
    t1.title = t2.title AND
    t1.company=t2.company AND
    t1.site_ID=t2.site_ID;

它很慢(5w +行,锁定等待超时),但是起作用了
yurenchen

3

每当我用Google搜索“从mysql删除重复项”时,我都会不断访问此页面,但是对于我的theIGNORE解决方案,因为我有一个InnoDB mysql表而无法正常工作

该代码随时可以更好地工作

CREATE TABLE tableToclean_temp LIKE tableToclean;
ALTER TABLE tableToclean_temp ADD UNIQUE INDEX (fontsinuse_id);
INSERT IGNORE INTO tableToclean_temp SELECT * FROM tableToclean;
DROP TABLE tableToclean;
RENAME TABLE tableToclean_temp TO tableToclean;

tableToclean =您需要清除的表的名称

tableToclean_temp =创建和删除的临时表


2

此解决方案将重复项移动到一个表中,将唯一性

-- speed up creating uniques table if dealing with many rows
CREATE INDEX temp_idx ON jobs(site_id, company, title, location);

-- create the table with unique rows
INSERT jobs_uniques SELECT * FROM
    (
    SELECT * 
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) > 1
    UNION
    SELECT *
    FROM jobs
    GROUP BY site_id, company, title, location
    HAVING count(1) = 1
) x

-- create the table with duplicate rows
INSERT jobs_dupes 
SELECT * 
FROM jobs
WHERE id NOT IN
(SELECT id FROM jobs_uniques)

-- confirm the difference between uniques and dupes tables
SELECT COUNT(1)
AS jobs, 
(SELECT COUNT(1) FROM jobs_dupes) + (SELECT COUNT(1) FROM jobs_uniques)
AS sum
FROM jobs

你为什么要参加工会而不仅仅是SELECT * FROM jobs GROUP BY site_id, company, title, location
timctran

2

从8.0(2018)版本开始,MySQL最终支持窗口功能

窗口功能既方便又高效。这里是一个解决方案,演示了如何使用它们来解决此任务。

在子查询中,我们可以用来ROW_NUMBER()为表中column1/column2组中的每个记录分配位置,顺序为id。如果没有重复项,则记录将获得行号1。如果存在重复项,则它们将按升序编号id(从1)。

在子查询中为记录正确编号后,外部查询仅删除行号不为1的所有记录。

查询:

DELETE FROM tablename
WHERE id IN (
    SELECT id
    FROM (
        SELECT 
            id, 
            ROW_NUMBER() OVER(PARTITION BY column1, column2 ORDER BY id) rn
        FROM output
    ) t
    WHERE rn > 1
)

1

删除表中的重复记录。

delete from job s 
where rowid < any 
(select rowid from job k 
where s.site_id = k.site_id and 
s.title = k.title and 
s.company = k.company);

要么

delete from job s 
where rowid not in 
(select max(rowid) from job k 
where s.site_id = k.site_id and
s.title = k.title and 
s.company = k.company);

1
-- Here is what I used, and it works:
create table temp_table like my_table;
-- t_id is my unique column
insert into temp_table (id) select id from my_table GROUP by t_id;
delete from my_table where id not in (select id from temp_table);
drop table temp_table;

0

为了复制具有唯一列的记录,例如COL1,COL2,COL3,不应复制(假设我们错过了表结构中3列的唯一列,并且表中已经有多个重复项)

DROP TABLE TABLE_NAME_copy;
CREATE TABLE TABLE_NAME_copy LIKE TABLE_NAME;
INSERT INTO TABLE_NAME_copy
SELECT * FROM TABLE_NAME
GROUP BY COLUMN1, COLUMN2, COLUMN3; 
DROP TABLE TABLE_NAME;
ALTER TABLE TABLE_NAME_copy RENAME TO TABLE_NAME;

希望会对开发人员有所帮助。


0

TL; TR;

可以在mysqltutorial.org站点上找到有关此问题的详细介绍的教程

如何在MySQL中删除重复的行

非常清楚地显示了如何以三种不同的方式删除重复的行

A)使用DELETE JOIN声明

B)使用中间表

C)使用ROW_NUMBER()功能

我希望它会帮助某人。


0

我有一个表,忘记在id行中添加主键。虽然在id上具有auto_increment。但是有一天,一件事重播了数据库中的mysql bin日志,其中插入了一些重复的行。

我删除重复的行

  1. 选择唯一的重复行并将其导出

select T1.* from table_name T1 inner join (select count(*) as c,id from table_name group by id) T2 on T1.id = T2.id where T2.c > 1 group by T1.id;

  1. 按ID删除重复的行

  2. 从导出的数据中插入行。

  3. 然后在id上添加主键


-2

我想更具体地说明要删除的记录,因此这是我的解决方案:

delete
from jobs c1
where not c1.location = 'Paris'
and  c1.site_id > 64218
and exists 
(  
select * from jobs c2 
where c2.site_id = c1.site_id
and   c2.company = c1.company
and   c2.location = c1.location
and   c2.title = c1.title
and   c2.site_id > 63412
and   c2.site_id < 64219
)

-4

您可以轻松地从此代码中删除重复的记录。

$qry = mysql_query("SELECT * from cities");
while($qry_row = mysql_fetch_array($qry))
{
$qry2 = mysql_query("SELECT * from cities2 where city = '".$qry_row['city']."'");

if(mysql_num_rows($qry2) > 1){
    while($row = mysql_fetch_array($qry2)){
        $city_arry[] = $row;

        }

    $total = sizeof($city_arry) - 1;
        for($i=1; $i<=$total; $i++){


            mysql_query( "delete from cities2 where town_id = '".$city_arry[$i][0]."'");

            }
    }
    //exit;
}

3
这是非常糟糕的形式-数据库任务应该在数据库中完成,因为它们要快得多,而不是在php / mysql之间不断发送数据,因为您比其他人更了解。
最多

-4

我必须对文本字段执行此操作,并在索引上遇到了100个字节的限制。

我通过添加一列,对字段进行md5哈希以及进行更改来解决了这一问题。

ALTER TABLE table ADD `merged` VARCHAR( 40 ) NOT NULL ;
UPDATE TABLE SET merged` = MD5(CONCAT(`col1`, `col2`, `col3`))
ALTER IGNORE TABLE table ADD UNIQUE INDEX idx_name (`merged`);
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.