删除MySQL中的重复行

时间:2010-07-22 18:03:56

标签: mysql sql duplicates

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

id (Unique)
url (Unique)
title
company
site_id

现在,我需要删除具有相同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查询来完成。

26 个答案:

答案 0 :(得分:582)

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

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

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

答案 1 :(得分:153)

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

由于您的列中包含唯一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-safe equal operator(又名"spaceship operator")进一步简化它:

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`;

答案 2 :(得分:72)

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);

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

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

答案 3 :(得分:40)

如果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;

答案 4 :(得分:24)

还有另一种解决方案:

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 ...

答案 5 :(得分:22)

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

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

但是,有一种更好的方法可以删除重复项,既有效又可靠,并且可以轻松适应不同的需求。

一般的想法是创建一个新的临时表,通常添加一个唯一的约束来避免进一步的重复,并将前一个表中的数据插入到新表中,同时处理重复项。这种方法依赖于简单的MySQL INSERT查询,创建一个新的约束以避免进一步的重复,并且跳过使用内部查询来搜索重复项的需要以及应该保存在内存中的临时表(因此也适合大数据源)。

这是如何实现的。鉴于我们有一个表员工,其中包含以下列:

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寄存器在不到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;

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

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

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

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优先于其余

✔对重复项执行某些任务的变化,例如对发现的重复项进行计数

有时我们需要对找到的重复条目执行一些进一步处理(例如保留重复项的计数)。

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;

答案 6 :(得分:6)

我有SQLServer的查询snipet,但我认为它可以在其他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)

答案 7 :(得分:4)

更快捷的方法是将不同的行插入临时表中。使用删除,我花了几个小时从一个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;  

答案 8 :(得分: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);

答案 9 :(得分:3)

我随时随地访问此页面google&#34;删除重复的表单mysql&#34;但是对于我的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 =创建和删除的临时表

答案 10 :(得分:3)

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

1)添加一个新的布尔列

alter table mytable add tokeep boolean;

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

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

3)将boolean列设置为true。由于新约束

,这只会在其中一个重复行上成功
update ignore mytable set tokeep = true;

4)删除尚未标记为“保养”的行

delete from mytable where tokeep is null;

5)删除添加的列

alter table mytable drop tokeep;

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

答案 11 :(得分:3)

如果你有一个包含大量记录的大表,那么以上解决方案将无效或花费太多时间。然后我们有一个不同的解决方案

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

答案 12 :(得分: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

答案 13 :(得分:2)

这对我有用:

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;

答案 14 :(得分:2)

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

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

答案 15 :(得分:2)

从8.0版(2018)开始,MySQL最终支持window functions

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

在子查询中,我们可以使用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
)

答案 16 :(得分:1)

使用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;

答案 17 :(得分:0)

  copyToClipboard = async e => {
    window.getSelection().removeAllRanges();
    var range = document.createRange();
    range.selectNode(this.textContent);
    window.getSelection().addRange(range);
    document.execCommand("copy");
    window.getSelection().removeAllRanges();
    this.setState({ copied: true });
  };

  initRef = c => (this.textContent = c);

  render() {
    const { copied } = this.state;
    return (
      <div className="positioning">
        <p onClick={this.copyToClipboard} ref={this.initRef}>
          chonchol@gmail.com
        </p>

        {copied === false ? (
          <p className="success">Copy to Clipboard</p>
        ) : (
          <p className="success">Copied to Clipboard</p>
        )}
      </div>
    );
  }

答案 18 :(得分:0)

TL; TR;

解决方案很详尽的教程可以在 mysqltutorial.org 站点找到:

How To Delete Duplicate Rows in MySQL

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

A)使用tikcIntervals语句

B)使用中间表

C)使用DELETE JOIN函数

我希望它将对某人有所帮助。

答案 19 :(得分:0)

要删除表中的重复记录。

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);

答案 20 :(得分: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;

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

答案 21 :(得分: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上添加主键

答案 22 :(得分:-1)

使用DELETE t1 FROM table_name t1 JOIN table_name t2 WHERE t1.id < t2.id AND t1.title = t2.title AND t1.company = t2.company AND t1.site_id = t2.site_id; 语句删除重复的行:

autoprefixer 10

答案 23 :(得分:-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
)

答案 24 :(得分:-3)

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

$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;
}

答案 25 :(得分:-3)

我必须使用文本字段执行此操作,并且在索引上遇到了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`);