重複した行を削除するにはどうすればよいですか?


1285

かなり大きなものから重複行を削除する最良の方法は何ですか SQL Serverテーブル(300,000以上の行)ですか?

もちろん、RowIDアイデンティティーフィールドが存在するため、行は完全な複製にはなりません。

MyTable

RowID int not null identity(1,1) primary key,
Col1 varchar(20) not null,
Col2 varchar(2048) not null,
Col3 tinyint not null

13
PostgreSQLユーザーがこれを読むための簡単なヒント(たくさん、リンクされる頻度によって異なります):PgはCTE用語を更新可能なビューとして公開しないためDELETE FROM、CTE用語を直接使用することはできません。stackoverflow.com/q/18439054/398670を
クレイグリンガー

@CraigRingerは、Sybaseでも同じです-残りのソリューションをここで収集しました(PGや他のソリューションでも有効になるはずです:stackoverflow.com/q/19544489/1855801ROWID()もしあれば、関数をRowID列で置き換えてください)
maf-soft 2013

12
ここに警告を追加します。重複除外プロセスを実行するときは、最初に何を削除するかを常に再確認してください。これは、誤って適切なデータを削除することがよくある領域の1つです。
ジェフデイビス

回答:


1142

nullがないと仮定するとGROUP BY、一意の列とSELECTMIN (or MAX)保持する行としてRowId になります。次に、行IDがなかったものをすべて削除します。

DELETE FROM MyTable
LEFT OUTER JOIN (
   SELECT MIN(RowId) as RowId, Col1, Col2, Col3 
   FROM MyTable 
   GROUP BY Col1, Col2, Col3
) as KeepRows ON
   MyTable.RowId = KeepRows.RowId
WHERE
   KeepRows.RowId IS NULL

整数の代わりにGUIDがある場合は、置き換えることができます

MIN(RowId)

CONVERT(uniqueidentifier, MIN(CONVERT(char(36), MyGuidColumn)))

327
これも機能しますか?DELETE FROM MyTable WHERE RowId NOT IN (SELECT MIN(RowId) FROM MyTable GROUP BY Col1, Col2, Col3);
GeorgSchölly10年

10
@Andriy -でSQL Serverは、LEFT JOINより効率が低いNOT EXISTS sqlinthewild.co.za/index.php/2010/03/23/...同じサイトでも比較NOT INNOT EXISTSsqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in 3つのうちNOT EXISTS最もパフォーマンスが良いと思います。3つすべてが自己結合を使用して計画を生成しますが、それは回避できます。
マーティン・スミス

12
@マーティン、@ジョージ:それで、私は小さなテストをしました。大きなテーブルが作成され、ここで説明するようにデータが入力されました。sqlinthewild.co.za / index.php / 2010/03/23 / 次に、2つのSELECTが生成されました。1つはLEFT JOIN + WHERE IS NULLテクニックを使用し、もう1つはNOTを使用しています。一つに。それから私は実行計画を進めました、そして何を推測しますか?クエリのコストはLEFTのための18%は、NOT IN、のために82%に対して、JOINた大きな私には驚き。自分にしてはいけないことや、その逆もあるかもしれませんが、それが本当なら知りたいです。
Andriy M

16
@GeorgSchöllyはエレガントな答えを提供してくれました。私のPHPバグが重複した行を作成したテーブルで使用しました。
フィリップカーンズ2013年

12
申し訳ありませんが、なぜDELETE MyTable FROM MyTable正しい構文ですか?ここDELETEのドキュメントでは、オプションの直後にテーブル名を置くことはできません。これが他の人に明らかである場合は申し訳ありません。私はSQLの初心者です。なぜそれが機能するのかよりも重要です:テーブルの名前をそこに含めるかどうかの違いは何ですか?
levininja 2013年

760

これを行う別の可能な方法は

; 

--Ensure that any immediately preceding statement is terminated with a semicolon above
WITH cte
     AS (SELECT ROW_NUMBER() OVER (PARTITION BY Col1, Col2, Col3 
                                       ORDER BY ( SELECT 0)) RN
         FROM   #MyTable)
DELETE FROM cte
WHERE  RN > 1;

使っています ORDER BY (SELECT 0)タイの場合にどの行を保存するかは任意なので上記をています。

RowID例えばあなたが使用できるように最新のものを保存するにはORDER BY RowID DESC

実行計画

これの実行計画は、自己結合を必要としないため、受け入れられた回答よりも単純で効率的であることがよくあります。

Execution Plans

ただし、常にそうであるとは限りません。GROUP BYソリューションが推奨される1つの場所は、ハッシュ集計ストリーム集約よりもが選択されるです。

ROW_NUMBER一方、解決策は、常にほとんど同じプランを与えるGROUP BY戦略がより柔軟です。

Execution Plans

ハッシュ集約アプローチを支持する可能性のある要因は、

  • パーティション化列に有用なインデックスはありません
  • 比較的少ないグループで、各グループの複製が比較的多い

この2番目のケースの極端なバージョン(各グループに重複が多いグループが非常に少ない場合)では、新しいテーブルに保持する行を挿入してからTRUNCATE、元のテーブルをコピーしてコピーし戻すことで、削除と比較してロギングを最小限に抑えることもできます。行の非常に高い割合。


28
追加する場合:受け入れられた回答は、を使用するテーブルでは機能しませんuniqueidentifier。これははるかに単純で、どのテーブルでも完全に機能します。マーティン、ありがとう。
BrunoLM 2010年

15
これは素晴らしい回答です。それが重複していることに気づく前に古いPKを削除したときに、それはうまくいきました。+100
ミカエルエリアソン

12
私はDBA.SEでこの質問を(この回答で)質問してから回答することをお勧めします。次に、それを正規の回答のリストに追加できます
Nick Chammas

16
受け入れられた回答とは異なり、これはRowId比較するキー()がないテーブルでも機能しました。
vossad01 2013年

8
一方、これはすべてのSQLサーバーバージョンで機能するわけではありません
David

150

重複の削除に関する良い記事がありますマイクロソフトサポートサイトに、ます。それはかなり保守的です-彼らはあなたが別々のステップですべてを行うようにします-しかしそれは大きなテーブルに対してうまくいくはずです。

これを行うために私は過去に自己結合を使用しましたが、おそらくHAVING句できれいにすることができます:

DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField 
AND dupes.secondDupField = fullTable.secondDupField 
AND dupes.uniqueField > fullTable.uniqueField

完璧!これは、古いmariadbバージョン10.1.xxで重複行を削除する最も効率的な方法であることがわかりました。ありがとうございました!
Drunken M

はるかにシンプルで理解しやすい!
マルク

98

次のクエリは、重複する行を削除するのに役立ちます。この例のテーブルが持つIDID列として、重複データを持つ列がありColumn1Column2そしてColumn3

DELETE FROM TableName
WHERE  ID NOT IN (SELECT MAX(ID)
                  FROM   TableName
                  GROUP  BY Column1,
                            Column2,
                            Column3
                  /*Even if ID is not null-able SQL Server treats MAX(ID) as potentially
                    nullable. Because of semantics of NOT IN (NULL) including the clause
                    below can simplify the plan*/
                  HAVING MAX(ID) IS NOT NULL) 

次のスクリプトショーの使用GROUP BYHAVINGORDER BY1つのクエリでは、重複する列とそのカウントして結果を返します。

SELECT YourColumnName,
       COUNT(*) TotalCount
FROM   YourTableName
GROUP  BY YourColumnName
HAVING COUNT(*) > 1
ORDER  BY COUNT(*) DESC 

1
最初のスクリプト「FROM句で更新するためにターゲットテーブル「TableName」を指定できません」のMySQLエラー
D.Rosado

すでに報告されているエラーD.Rosadoを除いて、最初のクエリも非常に低速です。対応するSELECTクエリは、受け入れられた回答よりも+-20倍長く私のセットアップにかかりました。
2013年

8
@parvus-質問のタグはMySQLではなくSQL Serverです。SQL Serverでは構文は問題ありません。また、MySQLはサブクエリ最適化することで悪名高く有名です。この答えはSQL Serverでは問題ありません。実際、はNOT INしばしばよりもパフォーマンスが優れていOUTER JOIN ... NULLます。HAVING MAX(ID) IS NOT NULL意味的には必要ないはずですが、ここで
Martin Smith

2
PostgreSQL 8.4でうまく動作します。
通常、2014

63
delete t1
from table t1, table t2
where t1.columnA = t2.columnA
and t1.rowid>t2.rowid

Postgres:

delete
from table t1
using table t2
where t1.columnA = t2.columnA
and t1.rowid > t2.rowid

SQL Serverの質問にPostgresソリューションを投稿する理由
Lankymart 2016年

2
@Lankymart postgresユーザーもここに来ているので。この回答のスコアを見てください。
ガブリエル

2
これは、herehereおよびhereのように、いくつかの一般的なSQLの質問で見られまし。OPは彼の答えを得て、他の誰もがいくつかの助けを得ました。問題ありません。
ガブリエル

44
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 

1
Azure SQL DWでこのメッセージが表示されます:DELETEステートメントでは、FROM句は現在サポートされていません。
2016年

40

これにより、最初の行を除いて重複する行が削除されます

DELETE
FROM
    Mytable
WHERE
    RowID NOT IN (
        SELECT
            MIN(RowID)
        FROM
            Mytable
        GROUP BY
            Col1,
            Col2,
            Col3
    )

参照(http://www.codeproject.com/Articles/157977/Remove-Duplicate-Rows-from-a-Table-in-SQL-Server


10
mysqlの場合、エラーが発生します。エラーコード:1093。FROM句で更新するターゲットテーブル 'Mytable'を指定できません。しかし、この小さな変更はmysqlで機能します。DELETEFROM Mytable WHERE RowID NOT IN(SELECT ID FROM(SELECT MIN(RowID)AS ID FROM Mytable GROUP BY Col1、Col2、Col3)AS TEMP)
Ritesh

35

SQL Serverテーブルから重複する行を削除するためにCTEを使用します

この記事に従うことを強くお勧めします:: http://codaffection.com/sql-server-article/delete-duplicate-rows-in-sql-server/

オリジナルを保つことによって

WITH CTE AS
(
SELECT *,ROW_NUMBER() OVER (PARTITION BY col1,col2,col3 ORDER BY col1,col2,col3) AS RN
FROM MyTable
)

DELETE FROM CTE WHERE RN<>1

オリジナルを維持することなく

WITH CTE AS
(SELECT *,R=RANK() OVER (ORDER BY col1,col2,col3)
FROM MyTable)
 
DELETE CTE
WHERE R IN (SELECT R FROM CTE GROUP BY R HAVING COUNT(*)>1)

24

重複する行をフェッチするには:

SELECT
name, email, COUNT(*)
FROM 
users
GROUP BY
name, email
HAVING COUNT(*) > 1

重複する行を削除するには:

DELETE users 
WHERE rowid NOT IN 
(SELECT MIN(rowid)
FROM users
GROUP BY name, email);      

MySQLユーザーの場合、最初にでなければならないことに注意してください。次に、同じテーブルからはDELETE FROMアクセスできないため、機能しません。MySQLではこれが爆発します。SELECTDELETEMySQL error 1093
Íhor私

23

正確で重複した行を削除するための迅速でダーティ(小さなテーブルの場合):

select  distinct * into t2 from t1;
delete from t1;
insert into t1 select *  from t2;
drop table t2;

3
質問は実際には(行IDが原因で)非正確な重複を指定していることに注意してください。
Dennis Jaheruddin、2015

21

読みやすく、SELECTステートメントに変換して何を削除するかを確認してから実行する前に確認するのが簡単だったので、内部結合よりもsubquery \ having count(*)> 1のソリューションをお勧めします。

--DELETE FROM table1 
--WHERE id IN ( 
     SELECT MIN(id) FROM table1 
     GROUP BY col1, col2, col3 
     -- could add a WHERE clause here to further filter
     HAVING count(*) > 1
--)

内部クエリに表示されるすべてのレコードを削除しませんか?重複のみを削除し、オリジナルを保持する必要があります。
サンディ

3
select句のmin(id)に基づいて、最小のIDを持つもののみを返します。
James Errico、

2
クエリの最初、2番目、最後の行のコメントを外します。
James Errico、2015年

7
これはすべての重複をクリーンアップしません。重複している行が3つある場合は、MIN(id)の行のみが選択され、その行が削除されます。残りの2行は重複しています。
Chloe

2
それにもかかわらず、私はこのステートメントを何度も何度も繰り返し使用したので、接続がタイムアウトしたり、コンピューターがスリープしたりする代わりに、実際に進行しました。MAX(id)後者の重複を排除するように変更しLIMIT 1000000、テーブル全体をスキャンする必要がないように内部クエリに追加しました。これは、他の回答よりもはるかに速く進行を示しました。テーブルが管理可能なサイズにプルーニングされた後、他のクエリで終了できます。ヒント:col1 / col2 / col3にgroup byのインデックスがあることを確認してください。
Chloe

17
SELECT  DISTINCT *
      INTO tempdb.dbo.tmpTable
FROM myTable

TRUNCATE TABLE myTable
INSERT INTO myTable SELECT * FROM tempdb.dbo.tmpTable
DROP TABLE tempdb.dbo.tmpTable

5
myTableへの外部キー参照がある場合、切り捨ては機能しません。
Sameer Alibhai 2013年

15

それは特別な状況下で機能するので、私は自分の解決策を共有したいと思いました。私の場合、重複する値を持つテーブルには外部キーがありませんでした(値が別のデータベースから重複していたため)。

begin transaction
-- create temp table with identical structure as source table
Select * Into #temp From tableName Where 1 = 2

-- insert distinct values into temp
insert into #temp 
select distinct * 
from  tableName

-- delete from source
delete from tableName 

-- insert into source from temp
insert into tableName 
select * 
from #temp

rollback transaction
-- if this works, change rollback to commit and execute again to keep you changes!!

PS:このような作業をするときは、常にトランザクションを使用します。これにより、すべてが全体として実行されることが保証されるだけでなく、何もリスクを冒すことなくテストすることができます。しかし、もちろん、とにかくバックアップを取る必要があります...


14

このクエリは、私にとって非常に優れたパフォーマンスを示しました。

DELETE tbl
FROM
    MyTable tbl
WHERE
    EXISTS (
        SELECT
            *
        FROM
            MyTable tbl2
        WHERE
            tbl2.SameValue = tbl.SameValue
        AND tbl.IdUniqueValue < tbl2.IdUniqueValue
    )

2Mのテーブルから30秒弱で100万行を削除しました(50%重複)


14

CTEの使用。アイデアは、重複するレコードを形成する1つ以上の列を結合してから、好きな方を削除することです。

;with cte as (
    select 
        min(PrimaryKey) as PrimaryKey
        UniqueColumn1,
        UniqueColumn2
    from dbo.DuplicatesTable 
    group by
        UniqueColumn1, UniqueColumn1
    having count(*) > 1
)
delete d
from dbo.DuplicatesTable d 
inner join cte on 
    d.PrimaryKey > cte.PrimaryKey and
    d.UniqueColumn1 = cte.UniqueColumn1 and 
    d.UniqueColumn2 = cte.UniqueColumn2;

1
JOINにANDがないと思います。
ジャスティン

13

さらに別の簡単な解決策は、ここに貼り付けたリンクにあります。これは簡単に理解でき、同様の問題のほとんどに効果があるようです。ただし、SQL Server用ですが、使用されている概念は許容範囲を超えています。

リンクされたページの関連部分は次のとおりです。

次のデータを検討してください。

EMPLOYEE_ID ATTENDANCE_DATE
A001    2011-01-01
A001    2011-01-01
A002    2011-01-01
A002    2011-01-01
A002    2011-01-01
A003    2011-01-01

それでは、これらの重複データをどのように削除できますか?

まず、次のコードを使用して、そのテーブルにID列を挿入します。

ALTER TABLE dbo.ATTENDANCE ADD AUTOID INT IDENTITY(1,1)  

それを解決するには、次のコードを使用します。

DELETE FROM dbo.ATTENDANCE WHERE AUTOID NOT IN (SELECT MIN(AUTOID) _
    FROM dbo.ATTENDANCE GROUP BY EMPLOYEE_ID,ATTENDANCE_DATE) 

1
「簡単につかむ」、「効果的であるように見える」が、メソッドの内容についての言葉ではない。リンクが無効になると想像してみて、メソッド簡単に把握できて効果的だったことを知るにはどうすればよいでしょうか。メソッドの説明の重要な部分を投稿に追加することを検討してください。そうでない場合、これは答えではありません。
Andriy M

このメソッドは、まだIDが定義されていないテーブルに役立ちます。多くの場合、主キーを定義するために重複を取り除く必要があります!
ジェフデイビス

@JeffDavis- ROW_NUMBER開始する前に新しい列を追加する必要がないため、バージョンはその場合に問題なく機能します。
マーティン・スミス

12

重複の削除に関する別の良い記事があります。

それが難しい理由について説明します。「SQLはリレーショナル代数に基づいており、セットでは重複が許可されていないため、リレーショナル代数では重複が発生しません。

一時テーブルソリューションと2つのmysqlの例。

将来的には、データベースレベルで、またはアプリケーションの観点からそれを防ぐ予定です。データベースは参照整合性を維持する責任があるため、データベースレベルをお勧めします。開発者は問題を引き起こすだけです;)


1
SQLはマルチセットに基づいています。しかし、セットに基づいていたとしても、この2つのタプル(1、a)と(2、a)は異なります。
Andrew

12

はい。一時テーブルを使用します。「上手くいく」とは言えない、パフォーマンスの低い単一のステートメントが必要な場合は、次のようにします。

DELETE FROM MyTable WHERE NOT RowID IN
    (SELECT 
        (SELECT TOP 1 RowID FROM MyTable mt2 
        WHERE mt2.Col1 = mt.Col1 
        AND mt2.Col2 = mt.Col2 
        AND mt2.Col3 = mt.Col3) 
    FROM MyTable mt)

基本的に、テーブル内の各行について、副選択は、検討中の行とまったく同じであるすべての行の一番上のRowIDを見つけます。したがって、「元の」重複しない行を表すRowIDのリストが作成されます。


11

重複しない行を保持する必要があるテーブルがありました。速度や効率はわかりません。

DELETE FROM myTable WHERE RowID IN (
  SELECT MIN(RowID) AS IDNo FROM myTable
  GROUP BY Col1, Col2, Col3
  HAVING COUNT(*) = 2 )

7
これは、最大で1つの重複があることを前提としています。
Martin Smith、

なんでHAVING COUNT(*) > 1
Philipp M

11

これを使って

WITH tblTemp as
(
SELECT ROW_NUMBER() Over(PARTITION BY Name,Department ORDER BY Name)
   As RowNumber,* FROM <table_name>
)
DELETE FROM tblTemp where RowNumber >1

10

もう1つの方法は、同じフィールドとUnique Indexを使用て新しいテーブルを作成することです。次に、すべてのデータを古いテーブルから新しいテーブルに移動します。SQL SERVERは自動的に無視します(値が重複する場合にどうするかについてのオプションもあります:無視、割り込み、またはsth)重複値。したがって、重複する行のない同じテーブルがあります。一意のインデックスが必要ない場合は、データの転送後に削除できます

特に大きなテーブルの場合、DTS(データをインポート/エクスポートするSSISパッケージ)を使用して、すべてのデータを新しい一意にインデックス付けされたテーブルに迅速に転送できます。700万行の場合、数分で完了します。


9

以下のクエリを使用することで、単一の列または複数の列に基づいて重複レコードを削除できます。以下のクエリは、2つの列に基づいて削除しています。テーブル名は:testingおよび列名empno,empname

DELETE FROM testing WHERE empno not IN (SELECT empno FROM (SELECT empno, ROW_NUMBER() OVER (PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)
or empname not in
(select empname from (select empname,row_number() over(PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)

9
  1. 同じ構造で新しい空のテーブルを作成する

  2. このようなクエリを実行する

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) > 1
  3. 次に、このクエリを実行します

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) = 1

9

これは重複レコードを削除する最も簡単な方法です

 DELETE FROM tblemp WHERE id IN 
 (
  SELECT MIN(id) FROM tblemp
   GROUP BY  title HAVING COUNT(id)>1
 )

http://askme.indianyouth.info/details/how-to-dumplicate-record-from-table-in-using-sql-105


なぜこれを支持しているのですか?同じIDが2つ以上ある場合、これは機能しません。代わりに次のように記述します。idが存在しない場合はtblempから削除します(タイトルによってtblempグループからmin(id)を選択)
crellee

7

このアプローチについても触れておきます。このアプローチは役立つ場合があり、すべてのSQLサーバーで機能します。かなりの場合、1つしかありません-2つの重複があり、IDと重複の数がわかっています。この場合:

SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0

7

アプリケーションレベルから(残念ながら)。重複を防止する適切な方法は、一意のインデックスを使用することでデータベースレベルにあることに同意しますが、SQL Server 2005では、インデックスは900バイトのみに許可されており、私のvarchar(2048)フィールドはそれを吹き飛ばします。

私はそれがどれほどうまく機能するかはわかりませんが、インデックスで直接実行できなくても、これを強制するトリガーを記述できると思います。何かのようなもの:

-- given a table stories(story_id int not null primary key, story varchar(max) not null)
CREATE TRIGGER prevent_plagiarism 
ON stories 
after INSERT, UPDATE 
AS 
    DECLARE @cnt AS INT 

    SELECT @cnt = Count(*) 
    FROM   stories 
           INNER JOIN inserted 
                   ON ( stories.story = inserted.story 
                        AND stories.story_id != inserted.story_id ) 

    IF @cnt > 0 
      BEGIN 
          RAISERROR('plagiarism detected',16,1) 

          ROLLBACK TRANSACTION 
      END 

また、varchar(2048)は私には怪しげに聞こえます(生活の中のいくつかのものは2048バイトですが、それはかなり一般的ではありません); それは本当にvarchar(max)ではないのですか?



7
DELETE
FROM
    table_name T1
WHERE
    rowid > (
        SELECT
            min(rowid)
        FROM
            table_name T2
        WHERE
            T1.column_name = T2.column_name
    );

こんにちはティーナ、あなたは削除コメントの後にテーブルアリス名T1を逃しました、さもなければそれは構文例外をスローします。
Nagaraj M 2017

6
CREATE TABLE car(Id int identity(1,1), PersonId int, CarId int)

INSERT INTO car(PersonId,CarId)
VALUES(1,2),(1,3),(1,2),(2,4)

--SELECT * FROM car

;WITH CTE as(
SELECT ROW_NUMBER() over (PARTITION BY personid,carid order by personid,carid) as rn,Id,PersonID,CarId from car)

DELETE FROM car where Id in(SELECT Id FROM CTE WHERE rn>1)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.