web-dev-qa-db-ja.com

MySQLの重複行を削除する

以下のフィールドを含むテーブルがあります。

id (Unique)
url (Unique)
title
company
site_id

今、私は同じtitle, company and site_idを持つ行を削除する必要があります。そのための1つの方法は、スクリプト(PHP)と共に次のSQLを使用することです。

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

このクエリを実行した後、サーバーサイドスクリプトを使用して重複を削除できます。

しかし、私はこれがSQLクエリを使用してのみ行うことができるかどうかを知りたいです。

330
Chetan

これを行う非常に簡単な方法は、3つの列にUNIQUEインデックスを追加することです。 ALTERステートメントを書くときは、IGNOREキーワードを含めます。そのようです:

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

これにより、すべての重複行が削除されます。追加の利点として、重複している将来のINSERTsはエラーになります。いつものように、あなたはこのような何かを実行する前にバックアップを取りたいと思うかもしれません...

579
Chris Henry

列のプロパティを変更したくない場合は、以下のクエリを使用できます。

一意の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セーフな等号演算子 (別名 "spacehip 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`;
153
rehriff

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

コメントにおけるコスタノスの提案から:
非常に大きなデータベースがある場合、上記の唯一の遅いクエリはDELETEです。このクエリはもっと​​速いかもしれません:

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

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

MySQLテーブルの重複を削除することは一般的な問題です。これは一般に、事前にそれらの重複を回避するための制約の欠落の結果です。しかし、この一般的な問題には通常、特定のニーズが伴います...特定のアプローチが必要です。アプローチは、たとえば、データのサイズ、保持する必要のある重複エントリ(通常は最初または最後のエントリ)、保持するインデックスがあるかどうか、または追加の実行を行うかどうかによって異なります。複製されたデータに対するアクション。

また、テーブルのUPDATEを実行するときに、FROM原因で同じテーブルを参照できないなど、MySQL自体にいくつかの特異性があります(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は、employeeテーブルとまったく同じ構造を持つ新しいtmp_eployeeテーブルを作成します
  • 行2は、新しい重複を避けるために、新しいtmp_eployeeテーブルにUNIQUE制約を追加します
  • 3行目は、元のemployeeテーブルをIDでスキャンし、新しい従業員エントリを新しいtmp_eployeeテーブルに挿入しますが、重複したエントリは無視します
  • 4行目はテーブルの名前を変更するため、新しいemployeeテーブルは重複のないすべてのエントリを保持し、以前のデータのバックアップコピーはbackup_employeeテーブルに保持されます。

この方法を使用すると、1.6Mレジスタが200秒未満で6kに変換されました。

Chetan 、このプロセスに従って、すべての重複を高速かつ簡単に削除し、次を実行して一意制約を作成できます。

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フィールドがスキップされ、DBエンジンが新しいフィールドを自動的に生成します。

✔さらなるバリエーション

目的の動作に応じて、さらに多くの変更を行うこともできます。例として、次のクエリでは2番目の一時テーブルを使用して、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;
28

別の解決策があります:

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 ...
24
Mostafa -T

私は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)
6
Eduardo Rascon

すべてのケースでシンプルかつ高速

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

もっと早い方法は一時テーブルに別々の行を挿入することです。 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;  
4
Nav

理解しやすく、主キーなしで機能するソリューション。

1)新しいブール列を追加する

alter table mytable add tokeep boolean;

2)複製した列と新しい列に制約を追加します

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

3)ブール列をtrueに設定します。新しい制約のため、これは複製された行の1つでのみ成功します。

update ignore mytable set tokeep = true;

4)tokeepとしてマークされていない行を削除する

delete from mytable where tokeep is null;

5)追加した列を削除する

alter table mytable drop tokeep;

将来、新しい重複が防止されるように、追加した制約を保持することをお勧めします。

3
xtian

私はいつでもこのページを訪問し続けますが、私は "重複するフォームからMySQLを削除します"が、InnoDBのMySQLテーブルがあるため、私のtheIGNOREソリューションではうまくいきません。

このコードはいつでもうまく機能する

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 =作成および削除された一時テーブル

3
Francesco

膨大な数のレコードを含む大きなテーブルがある場合、上記の解決策は機能しないか、時間がかかり過ぎます。それから私達は別の解決策を持っています

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

私は簡単な方法を見つけました。 (最新に保つ)

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

この解決策は 重複をあるテーブルに移動します そして ユニークなものを別のテーブルに移動します

-- 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
2
Anthony Vipond

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;
1
Saad Mirza

バージョン8.0(2018)以降、MySQLはついに ウィンドウ関数 をサポートします。

ウィンドウ関数は便利で効率的です。これは、この割り当てを解決するためにそれらを使用する方法を示すソリューションです。

副照会では、 ROW_NUMBER() を使用して、idで順序付けられた、column1/column2グループ内のテーブル内の各レコードに位置を割り当てることができます。重複がない場合、レコードは行番号1を取得します。重複がある場合は、(1で始まる)idを昇順にして番号が付けられます。

副照会でレコードが正しく番号付けされると、外部照会は、行番号が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
GMB

一意の列を持つレコードを複製するには、 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
Abdul Rehman

TL; TR;

この問題を解決するための広く記述されたチュートリアル mysqltutorial.org siteにあります

MySQLで重複行を削除する方法

3つの異なる方法で重複行を削除する方法を非常に明確に示しています

A)DELETE JOINステートメントの使用

B)中間テーブルを使う

C)ROW_NUMBER()関数を使う

誰かに役立つことを願っています。

0
simhumileco

テーブル内の重複レコードを削除する。

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);
0
Arun Solomon
-- 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
Duy Hoang