web-dev-qa-db-ja.com

MySQLテーブルの重複を削除する方法は?

DELETEテーブルの指定されたsidに対してMySQL行を複製する必要があります。

SQLクエリでこれを行うにはどうすればよいですか?

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

このようなものですが、私はそれを行う方法がわかりません。

148
Ali Poder

これにより、新しいテーブルを作成せずに重複を削除します

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

注:インデックスがメモリに収まる場合にのみ機能します

201
user187291

次の列を持つテーブル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  
117
Abhijoy_D

次に、単一のSIDだけでなく、すべての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
)
54
Kamil Szot

MySQLでの重複行の削除、ウォークスルー

テーブルを作成し、いくつかの行を挿入します:

dev-db> create table penguins(foo int, bar varchar(15), baz datetime);
Query OK, 0 rows affected (0.07 sec)
dev-db> insert into penguins values(1, 'skipper', now());
dev-db> insert into penguins values(1, 'skipper', now());
dev-db> insert into penguins values(3, 'kowalski', now());
dev-db> insert into penguins values(3, 'kowalski', now());
dev-db> insert into penguins values(3, 'kowalski', now());
dev-db> insert into penguins values(4, 'rico', now());
Query OK, 6 rows affected (0.07 sec)
dev-db> 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)

その後、重複を削除します:

dev-db> 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)

結果:

dev-db> 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)

削除ステートメントの実行内容

擬似コード:重複を削除する2つの列で行をグループ化します。最大集計を使用して、保持する各グループの1行を選択します。左結合は、左のテーブルからすべての行を返し、一致する行を右のテーブルに返します。この場合、左側のテーブルにはテーブル内のすべての行があり、右側には保持する行ごとではなく、NULLの行のみが含まれます。これらの行を削除すると、グループごとに一意の行のみが残ります。

詳細な技術的説明、そのsql deleteステートメントの読み方:

エイリアス「a」を持つテーブルペンギンは、エイリアス「b」と呼ばれるテーブルペンギンのサブセットに結合されたままになります。サブセットである右側のテーブル「b」は、fooとbarでグループ化された最大タイムスタンプを見つけます。これは、左側のテーブル「a」に一致します。左側の(foo、bar、baz)には、テーブル内のすべての行があります。右側のサブセット 'b'には(maxtimestamp、foo、bar)があり、これはIS maxの左側にのみ一致します。

その最大ではないすべての行の値はNULLのmaxtimestampです。それらのNULL行をフィルターダウンすると、fooとbarでグループ化されたすべての行のセットがあり、それは最新のタイムスタンプbazではありません。それらを削除します。

これを実行する前に、テーブルのバックアップを作成します。

この問題がこのテーブルで再び発生するのを防ぎます:

これが機能するようになり、「重複行」が発生した場合。すばらしいです。あなたの仕事はまだ終わっていません。テーブルに(これらの2つの列に)新しい複合一意キーを定義して、そもそも重複が追加されないようにします。優れた免疫システムのように、挿入時に不良な行をテーブルに入れてはいけません。後で重複を追加するこれらすべてのプログラムは抗議をブロードキャストします。それらを修正すると、この問題は二度と起こりません。

47
Eric Leschinski

自分でこの問題に遭遇した後、巨大なデータベースで、私は他の答えのパフォーマンスに完全に感銘を受けませんでした。最新の重複行のみを保持し、残りを削除したい。

一時テーブルを使用しない1クエリステートメントでは、これが最適でした。

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

唯一の注意点は、クエリを複数回実行する必要があることですが、それでも、他のオプションよりもうまく機能することがわかりました。

12
seaders

これは常に私のために働くようです:

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として失敗しないという利点があります2つのフィールドに基づいて重複レコードを追加し、そのようなレコードは無視します。

Fwdを移動すると、これらの2つのフィールドに基づいて重複するレコードを作成できなくなります。

12
user3649739

簡単な答えを次に示します。

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

以下はすべてのテーブルで機能します

CREATE TABLE `noDup` LIKE `Dup` ;
INSERT `noDup` SELECT DISTINCT * FROM `Dup` ;
DROP TABLE `Dup` ;
ALTER TABLE `noDup` RENAME `Dup` ;
6
M.B.Miri

これは私にとって古いレコードを削除するのに役立ちます:

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
richardhell

この手順は、テーブル内のすべての重複(複数を含む)を削除し、最後の重複を保持します。これは 各グループの最後のレコードを取得する の拡張です

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

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
Simon
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;
4
temonehm

別の簡単な方法... UPDATE IGNOREを使用:

Uは、1つ以上の列のインデックス(タイプインデックス)を使用する必要があります。新しい一時参照列を作成します(インデックスの一部ではありません)。この列では、ignore句を使用して更新することで一意をマークします。ステップバイステップ:

一時的な参照列を追加して、一意をマークします。

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

=>これにより、テーブルに列が追加されます。

テーブルを更新し、すべてを一意としてマークしますが、重複キーの問題による可能性のあるエラーを無視します(レコードはスキップされます)。

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

=>重複レコードは一意としてマークされません= 'はい'、つまり、重複レコードの各セットのうち1つだけが一意としてマークされます。

一意ではないものをすべて削除します。

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

=>これにより、すべての重複レコードが削除されます。

列をドロップ...

ALTER TABLE `yourtable` DROP `unique`;
3
Werner

Wernerのソリューション 上記 が最も便利であると思います。主キーの存在に関係なく機能し、テーブルを混乱させず、将来性のあるプレーンなSQLを使用し、非常に理解しやすいからです。

私のコメントで述べたように、その解決策は適切に説明されていません。それに基づいて、これは私のものです。

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;

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

2
xtian

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

これは大きなテーブルで機能します:

 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

これは基本的にテーブルをコピーして空にし、個別の値のみを戻すことで機能すると思いますが、大量のデータで実行する前に再確認してください。

テーブルのカーボンコピーを作成します

oldtablenameのようなテーブルtemp_tableを作成します。挿入temp_table select * from oldtablename;

元のテーブルを空にします

DELETE * from oldtablename;

コピーされたテーブルからすべての個別の値を元のテーブルにコピーして戻します

Firsttable、lastname、dobによるtemp_tableグループからのoldtablename SELECT *の挿入

一時テーブルを削除します。

テーブルtemp_tableの削除

区別したいaLLフィールドでグループ化する必要があります。

0
ChrisAardvark
DELETE T2
FROM   table_name T1
JOIN   same_table_name T2 ON (T1.title = T2.title AND T1.ID <> T2.ID)
0
Nav
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
)
0
Patrick

@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
Gujamin

これにより、列column_nameが主キーになり、それまではすべてのエラーが無視されます。したがって、column_nameの重複値を持つ行を削除します。

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