PostgreSQLで重複するレコードを削除する


113

PostgreSQL 8.3.8データベースにテーブルがあり、キー/制約がなく、まったく同じ値の複数の行があります。

すべての重複を削除して、各行のコピーを1つだけ保持したいと思います。

特に、「キー」という名前の列が1つあり、重複を識別するために使用できます(つまり、「キー」ごとに1つのエントリのみが存在する必要があります)。

これどうやってするの?(理想的には単一のSQLコマンドを使用)この場合、速度は問題になりません(数行しかない)。

回答:


80
DELETE FROM dupes a
WHERE a.ctid <> (SELECT min(b.ctid)
                 FROM   dupes b
                 WHERE  a.key = b.key);

20
使用しないでください。遅すぎます。
パヴェルMalisak

5
このソリューションは確実に機能しますが、以下の @rapimoのソリューションははるかに速く実行されます。これは、他のソリューションで行われているグループ化ではなく、ここでの内側の選択ステートメントが(DupesテーブルのすべてのN行に対して)N回実行されることに関係していると思います。
David

巨大なテーブル(数百万レコード)の場合、@ rapimoのソリューションとは異なり、これは実際にはメモリに収まります。したがって、これらの場合、これはより高速なものです(スワッピングなし)。
Giel

1
説明の追加:ctidは行の物理的な場所を示す特別なpostgres列であるため、機能します。テーブルに一意のIDがない場合でも、これを一意のIDとして使用できます。postgresql.org/docs/8.2/ddl-system-columns.html
Eric Burel

193

より速い解決策は

DELETE FROM dups a USING (
      SELECT MIN(ctid) as ctid, key
        FROM dups 
        GROUP BY key HAVING COUNT(*) > 1
      ) b
      WHERE a.key = b.key 
      AND a.ctid <> b.ctid

20
なぜa_horse_with_no_nameのソリューションより速いのですか?
Roberto

3
これは2つのクエリしか実行しないため、より高速です。最初にすべての重複を選択し、次にテーブルからすべてのアイテムを削除します。@a_horse_with_no_nameによるクエリは、テーブル内のすべてのアイテムについて、他のものと一致するかどうかを確認するクエリを実行します。
Aeolun

5
なにctid
techkuz

6
ドキュメントから:ctid。テーブル内の行バージョンの物理的な場所。ctidを使用して行バージョンをすばやく見つけることができますが、行のctidは、VACUUM FULLによって更新または移動されるたびに変更されることに注意してください。したがって、ctidは長期的な行識別子としては役に立ちません。
Saim

1
2つ以上の重複する行がある場合、一度に1つの重複のみが削除されるため、これは機能しないようです。
フランキードレイク

73

これは速くて簡潔です:

DELETE FROM dupes T1
    USING   dupes T2
WHERE   T1.ctid < T2.ctid  -- delete the older versions
    AND T1.key  = T2.key;  -- add more columns if needed

詳細については、一意の識別子なしで重複行を削除する方法の私の回答も参照してください。


ctは何の略ですか?カウント?
techkuz

4
@trthhrtz ctidは、テーブル内のレコードの物理的な場所を指します。当時私がコメントで書いたものとは逆に、ctが折り返されたり、ctidが低い値の方が実際には新しい場合があるため、小なり演算子を使用しても必ずしも古いバージョンを指すとは限りません。
isapir

1
参考までに、私はこのソリューションを試し、15分待ってから中止しました。rapimoのソリューションを試したところ、約10秒で完了しました(約700,000行が削除されました)。
パトリック

@Patrickは、rapimoの回答がその場合には機能しないため、dbに一意の識別子がない場合は想像できません。
スタッカシュ

@isapir私は好奇心旺盛です、上の答え、彼らは選択したとおりに古いレコードを正しく保持していmin(ctid)ますか?あなたのものは新しいものを保持しているのですか?ありがとう!
スタッカシュ

17

私はこれを試しました:

DELETE FROM tablename
WHERE id IN (SELECT id
              FROM (SELECT id,
                             ROW_NUMBER() OVER (partition BY column1, column2, column3 ORDER BY id) AS rnum
                     FROM tablename) t
              WHERE t.rnum > 1);

Postgres wikiによって提供されます:

https://wiki.postgresql.org/wiki/Deleting_duplicates


@rapimoの回答と受け入れられた回答(@a_horse_with_no_name)と比較したパフォーマンスのアイデアはありますか?
tuxayo 2017

3
これは、質問のように、すべての列が同じで、id含まれている場合は機能しません。
ibizaman 2017年

このクエリは、元のコピーと重複の両方を削除します。問題は、少なくとも1つの行を保持することです。
pyBomb

@pyBombが間違っており、idcolumn1 ... 3が重複する最初の場所が保持されます
Jeff

postgresql 12以降、これはBY FARで最速のソリューションです(3億行に対して)。承認された回答を含め、この質問で提案されたすべてをテストしました。この「公式」ソリューションは実際に最速であり、OP(および私の)のすべての要件を満たしています
Jeff

7

私は自分のバージョンを作成する必要がありました。@a_horse_with_no_nameによって書き込まれたバージョンは、私のテーブル(2100万行)では非常に低速です。そして、@ rapimoは単に重複を削除しません。

これは私がPostgreSQL 9.5で使用するものです

DELETE FROM your_table
WHERE ctid IN (
  SELECT unnest(array_remove(all_ctids, actid))
  FROM (
         SELECT
           min(b.ctid)     AS actid,
           array_agg(ctid) AS all_ctids
         FROM your_table b
         GROUP BY key1, key2, key3, key4
         HAVING count(*) > 1) c);

6

一時テーブルを使用します。

create table tab_temp as
select distinct f1, f2, f3, fn
  from tab;

次に、削除tabして名前をに変更tab_temptabます。


8
このアプローチでは、トリガー、インデックス、統計は考慮されません。確かにそれらを追加することもできますが、それはさらに多くの作業を追加します。
ジョーダン

誰もがそれを必要とするわけではありません。このアプローチは非常に高速で、インデックスのない20万通のメール(varchar 250)の他のメールよりもはるかに優れています。
Sergey Telshevsky 2017年

完全なコード:DROP TABLE IF EXISTS tmp; CREATE TABLE tmp as ( SELECT * from (SELECT DISTINCT * FROM your_table) as t ); DELETE from your_table; INSERT INTO your_table SELECT * from tmp; DROP TABLE tmp;
Eric Burel

1

別のアプローチ(idテーブルのように一意のフィールドがある場合にのみ機能します)で列ごとにすべての一意のIDを検索し、一意のリストにない他のIDを削除

DELETE
FROM users
WHERE users.id NOT IN (SELECT DISTINCT ON (username, email) id FROM users);

問題は、私の質問では、テーブルに一意のIDがないことです。「重複」とは、すべての列でまったく同じ値を持つ複数の行でした。
アンドレ・Morujão

右、私はいくつかのメモを追加しました
Zaytsev Dmitry

1

どうですか:

と
  u AS(SELECT DISTINCT * FROM your_table)、
  x AS(your_tableから削除)
INSERT INTO your_table SELECT * FROM u;に挿入します。

私は実行順序を心配していましたが、DELETEはSELECT DISTINCTの前に発生しますが、私には問題なく動作しました。また、テーブル構造についての知識を必要としないという追加のボーナスがあります。


唯一の欠点は、同等性をサポートしないデータ型(たとえばjson)がある場合、これが機能しないことです。
a_horse_with_no_name

0

これは私にはうまくいきました。重複する値を含むテーブル、termsがありました。クエリを実行して、重複するすべての行を一時テーブルに入力します。次に、一時テーブルのIDを使用して削除ステートメントを実行しました。valueは重複を含んだ列です。

        CREATE TEMP TABLE dupids AS
        select id from (
                    select value, id, row_number() 
over (partition by value order by value) 
    as rownum from terms
                  ) tmp
                  where rownum >= 2;

delete from [table] where id in (select id from dupids)

0

以下が使用するソリューションPARTITION BYです。

DELETE FROM dups
USING (
  SELECT
    ctid,
    (ctid != min(ctid) OVER (PARTITION BY key_column1, key_column2 [...])) AS is_duplicate
  FROM dups 
) dups_find_duplicates
WHERE dups.ctid == dups_find_duplicates.ctid
AND dups_find_duplicates.is_duplicate
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.