删除MySQL中的重复行

Che*_*tan 351 mysql sql duplicates

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

id (Unique)
url (Unique)
title
company
site_id
Run Code Online (Sandbox Code Playgroud)

现在,我需要删除具有相同的行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
Run Code Online (Sandbox Code Playgroud)

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

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

Chr*_*nry 596

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

ALTER IGNORE TABLE jobs
ADD UNIQUE INDEX idx_name (site_id, title, company);
Run Code Online (Sandbox Code Playgroud)

这将删除所有重复的行.作为额外的好处,INSERTs重复的未来将会出错.与往常一样,您可能希望在运行此类内容之前进行备份...

  • 只是为了记录如果你使用InnoDB然后你可能有一个问题,有一个已知的错误关于使用ALTER IGNORE TABLE和InnoDB数据库. (75认同)
  • 这不再是5.7.4中的支持,https://dev.mysql.com/doc/refman/5.7/en/alter-table.html (46认同)
  • 对于InnoDB表,首先执行以下查询:`set session old_alter_table = 1;` (42认同)
  • [上述错误](http://bugs.mysql.com/bug.php?id=40344)@DarkMantis提及和[它的解决方案](http://stackoverflow.com/questions/8053447/mysql-alter-忽略-表给出了完整性约束违章). (26认同)
  • [有趣](http://dev.mysql.com/doc/refman/5.1/en/alter-table.html),但IGNORE子句用于删除这些重复项的假设是一个可能与需求不匹配的问题.不正确的值被截断为最接近的可接受的匹配声音对您有益吗? (8认同)
  • 确实不再支持这种情况:`从MySQL 5.7.4开始,删除了ALTER TABLE的IGNORE子句,并且它的使用会产生错误. (6认同)
  • 如上所述,如果表类型是InnoDB,则不能使用这种方式,在这种情况下,您可以使用SQL查询将表类型更改为MyISAM:`ALTER TABLE table_name ENGINE = MYISAM` (4认同)
  • @Vinny对我来说,最接近的替代方案是使用`INSERT IGNORE INTO`的方法,如http://www.tocker.ca/the-future-of-alter-ignore-table-syntax.html和https://www.tocker.ca/the-future-of-alter-ignore-table-syntax.html和https://www.tocker.ca/the-future-of-alter-ignore-table-syntax.html和https://www.tocker.ca/the-future-of-alter-ignore-table-syntax.html和https:// /stackoverflow.com/a/47392593/7154924。 (3认同)
  • 工作!但是 ... $mm = "set session old_alter_table=1; "; $rmm = mysql_query($mm); (2认同)
  • 由于它不再适用于MySQL 5.7.4或更高版本,因此有什么替代方案? (2认同)

小智 170

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

由于您的列具有唯一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);
Run Code Online (Sandbox Code Playgroud)

在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`;
Run Code Online (Sandbox Code Playgroud)

  • 我选择了这个解决方案,因为我认为这是解决方案,其中最清楚的是代码在做什么. (15认同)
  • 这应该是公认的答案,因为(A)它是标准的SQL,(B)清楚地显示了幕后没有魔法的情况. (7认同)
  • 这个解决方案工作不正常,我尝试制作一些重复的记录,它做了类似的事情(20行受影响),但是如果你再次运行它会显示你(4行受影响),依此类推,直到你到达(0行受影响)这有点可疑,这里最适合我,它几乎是一样的,但它一次运行,我编辑了解决方案 (3认同)
  • 对于像我这样困惑的人,需要NULL比较术语,因为NULL在MySQL中不等于NULL.如果保证相关列不为NULL,则可以保留这些条款. (3认同)
  • 是的,自MYSQL 5.7起,接受的答案不再有效,因此,这应该是接受的答案,因为它是通用的,并且也不需要临时表创建。 (3认同)
  • 如果给定记录有很多副本(例如 100 减少为 1),并且有许多具有该条件的记录,则非常慢。推荐 /sf/answers/327966271/ 代替。恕我直言,始终使用链接方法;它本质上是一种更快的技术。 (3认同)

And*_*mar 75

MySQL对引用要删除的表有限制.您可以使用临时表解决此问题,例如:

create temporary table tmpTable (id int);

insert  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);
Run Code Online (Sandbox Code Playgroud)

从Kostanos在评论中的建议:
对于你拥有一个非常大的数据库的情况,上面唯一的慢查询是DELETE.此查询可能更快:

DELETE FROM YourTable USING YourTable, tmpTable WHERE YourTable.id=tmpTable.id
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该是公认的答案 (5认同)
  • 这里唯一的慢查询是DELETE,如果你有大数据库的话.这个查询可能更快:```使用YourTable删除你的表,tmpTable WHERE YourTable.id = tmpTable.id``` (4认同)
  • @andomar,除非where子句中的一个字段包含空值,否则这个工作正常.示例:http://sqlfiddle.com/#!2/983f3/1 (3认同)

Kam*_*mil 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;
Run Code Online (Sandbox Code Playgroud)


Cés*_*mar 31

删除MySQL表上的重复项是一个常见的问题,这通常是缺少约束的结果,以避免事先重复这些重复.但这个常见问题通常伴随着特定的需求......确实需要特定的方法.该方法应该根据,例如,数据的大小,应该保留的重复条目(通常是第一个或最后一个),是否存在索引,或者我们是否要执行任何其他对重复数据采取行动.

MySQL本身也有一些特殊性,例如在执行表UPDATE时无法在FROM原因上引用同一个表(它会引发MySQL错误#1093).通过使用带有临时表的内部查询(如上面某些方法所建议的),可以克服此限制.但是这种内部查询在处理大数据源时不会特别好.

然而,确实存在一种更好的方法来消除重复,这既是高效又可靠的,并且可以很容易地适应不同的需求.

总的想法是创建一个新的临时表,通常加入了独特的约束,以避免进一步的重复,从您的旧表插入数据到新的一个,同时利用重复的照顾.这种方法依赖于简单的MySQL INSERT查询,创建一个新的约束以避免进一步的重复,并且跳过使用内部查询来搜索重复项的需要以及应该保存在内存中的临时表(因此也适合大数据源).

这是如何实现的.鉴于我们有一名表员工,请使用以下列:

employee (id, first_name, last_name, start_date, ssn)
Run Code Online (Sandbox Code Playgroud)

为了删除具有重复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;
Run Code Online (Sandbox Code Playgroud)

技术说明

  • 第1行创建一个新的tmp_eployee表,其结构与employee表完全相同
  • 第2行为新的tmp_eployee表添加了一个UNIQUE约束,以避免任何进一步的重复
  • 第3行按ID 扫描原始员工表,将新员工条目插入新的tmp_eployee表,同时忽略重复的条目
  • 第4行重命名表,以便新员工表保存所有没有重复项的条目,并且以前数据的备份副本保存在backup_employee表中

使用这种方法,1.6M寄存器在不到200s的时间内转换为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;
Run Code Online (Sandbox Code Playgroud)

当然,在删除重复项时,可以进一步修改此过程以使其适应不同的需要.一些例子如下.

✔保留最后一个条目而不是第一个条目的变化

有时我们需要保留最后一个重复的条目,而不是第一个.

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;
Run Code Online (Sandbox Code Playgroud)
  • 在第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;
Run Code Online (Sandbox Code Playgroud)
  • 在第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;
Run Code Online (Sandbox Code Playgroud)
  • 在第3行,不是选择表中的所有字段,而是跳过id字段,以便DB引擎自动生成一个新字段

✔进一步的变化

根据所需的行为,许多进一步的修改也是可行的.例如,以下查询将使用第二个临时表,除了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;
Run Code Online (Sandbox Code Playgroud)


小智 25

还有另一种解决方案:

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 ...
Run Code Online (Sandbox Code Playgroud)

  • 这与他6个月前提交的@rehriff的答案有什么不同? (4认同)

Dhr*_*hal 8

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

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;
Run Code Online (Sandbox Code Playgroud)


fai*_*gat 7

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

-- 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;
Run Code Online (Sandbox Code Playgroud)


Edu*_*con 6

我有这个查询snipet for SQLServer但我认为它可以在其他DBMS中使用,几乎没有变化:

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

我忘了告诉你这个查询不会删除重复行ID最低的行.如果这适用于您尝试此查询:

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

  • 不幸的是,MySQL不允许您从"ERROR 1093"中删除要删除的表:您无法在FROM子句中为更新指定目标表'Table' (11认同)

xti*_*ian 6

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

  1. 添加一个新的布尔列

    alter table mytable add tokeep boolean;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在重复的列和新列上添加约束

    alter table mytable add constraint preventdupe unique (mycol1, mycol2, tokeep);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 将布尔列设置为 true。由于新的约束,这只会在重复的行之一上成功

    update ignore mytable set tokeep = true;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 删除未标记为 tokeep 的行

    delete from mytable where tokeep is null;
    
    Run Code Online (Sandbox Code Playgroud)
  5. 删除添加的列

    alter table mytable drop tokeep;
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 这在 mysql 5.7 中非常有效,接受的解决方案不再有效 (3认同)

小智 6

使用 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;
Run Code Online (Sandbox Code Playgroud)


Nav*_*Nav 5

更快的方法是将不同的行插入到临时表中。使用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;  
Run Code Online (Sandbox Code Playgroud)


小智 5

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

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);
Run Code Online (Sandbox Code Playgroud)


Ric*_*yen 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;
Run Code Online (Sandbox Code Playgroud)


GMB*_*GMB 5

从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
)
Run Code Online (Sandbox Code Playgroud)