web-dev-qa-db-ja.com

重複したエントリを削除する方法は?

既存のテーブルに一意の制約を追加する必要があります。これは、テーブルにすでに数百万の行があり、行の多くが追加する必要のある一意の制約に違反していることを除き、問題ありません。

問題のある行を削除する最速のアプローチは何ですか?重複を見つけて削除するSQLステートメントがありますが、実行に時間がかかります。この問題を解決する別の方法はありますか?テーブルをバックアップしてから、制約が追加された後に復元するのでしょうか?

93
gjrwebber

たとえば、次のことができます。

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
101
just somebody

これらのアプローチのいくつかは少し複雑に思えますが、私は通常これを次のように行います。

テーブルtableが与えられた場合、最大のfield3で行を保持する(field1、field2)で一意にしたい:

DELETE FROM table USING table alias 
  WHERE table.field1 = alias.field1 AND table.field2 = alias.field2 AND
    table.max_field < alias.max_field

たとえば、user_accountsというテーブルがあり、電子メールに一意の制約を追加したいのですが、重複しています。また、最後に作成されたもの(重複する間の最大ID)を保持したいと言います。

DELETE FROM user_accounts USING user_accounts ua2
  WHERE user_accounts.email = ua2.email AND user_account.id < ua2.id;
  • 注-USINGは標準SQLではなく、PostgreSQLの拡張機能(非常に便利な拡張機能)ですが、元の質問では特にPostgreSQLに言及しています。
173
Tim

新しいテーブルを作成する代わりに、切り捨てた後、同じテーブルに一意の行を再挿入することもできます。 1回のトランザクションですべてを実行します。オプションで、ON COMMIT DROPを使用して、トランザクションの最後に一時テーブルを自動的に削除できます。下記参照。

このアプローチは、テーブル全体から削除する行が多数ある場合にのみ役立ちます。ほんの数回の複製については、単純なDELETEを使用します。

あなたは数百万行に言及しました。操作fastを行うには、セッションに十分な 一時バッファ を割り当てます。設定は調整する必要がありますbefore現在のセッションで一時バッファが使用されます。テーブルのサイズを確認します。

SELECT pg_size_pretty(pg_relation_size('tbl'));

temp_buffersを適宜設定します。メモリ内表現にはもう少しRAMが必要なため、十分に切り上げてください。

SET temp_buffers = 200MB;    -- example value

BEGIN;

-- CREATE TEMPORARY TABLE t_tmp ON COMMIT DROP AS -- drop temp table at commit
CREATE TEMPORARY TABLE t_tmp AS  -- retain temp table after commit
SELECT DISTINCT * FROM tbl;  -- DISTINCT folds duplicates

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;
-- ORDER BY id; -- optionally "cluster" data while being at it.

COMMIT;

この方法は、依存するオブジェクトが存在する場合、新しいテーブルifを作成するよりも優れている場合があります。テーブルを参照するビュー、インデックス、外部キーまたはその他のオブジェクト。 TRUNCATE とにかくクリーンなスレートで開始し(バックグラウンドで新しいファイル)、muchは大きなテーブルのDELETE FROM tblよりも高速です(DELETEは、実際には小さなテーブルの方が高速です。

大きなテーブルの場合、定期的にfasterを実行して、インデックスと外部キーを削除し、テーブルを再入力してこれらのオブジェクトを再作成します。 fkの制約に関する限り、もちろん新しいデータが有効であることを確認する必要があります。そうしないと、fkを作成しようとしたときに例外が発生します。

TRUNCATEDELETEよりも積極的なロックを必要とすることに注意してください。これは、重い同時ロードのあるテーブルの問題である可能性があります。

TRUNCATEがオプションではない場合、または一般的に小規模から中規模のテーブルの場合、 data-modifying CTE (Postgres9.1+):

WITH del AS (DELETE FROM tbl RETURNING *)
INSERT INTO tbl
SELECT DISTINCT * FROM del;
-- ORDER BY id; -- optionally "cluster" data while being at it.

大きなテーブルの場合は、TRUNCATEの方が高速なので遅い。しかし、小さなテーブルの場合はより高速(そしてよりシンプルに!)になります。

依存するオブジェクトがまったくない場合、新しいテーブルを作成して古いテーブルを削除することもできますが、この普遍的なアプローチではほとんど何も得られません。

利用可能なRAMに収まらないような非常に大きなテーブルの場合、newテーブルの作成はかなり速くなります。これは、依存するオブジェクトで発生する可能性のあるトラブル/オーバーヘッドと比較検討する必要があります。

25

Oidまたはctidを使用できます。これらは通常、テーブル内の「非表示」列です。

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);
20
Jan Marek

PostgreSQLウィンドウ関数は、この問題に便利です。

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

重複の削除を参照してください。

19
shekwi

重複を削除する一般化されたクエリ:

DELETE FROM table_name
WHERE ctid NOT IN (
  SELECT max(ctid) FROM table_name
  GROUP BY column1, [column 2, ...]
);

ctidは、すべてのテーブルで使用できる特別な列ですが、特に明記されていない限り表示されません。 ctid列の値は、テーブル内のすべての行で一意と見なされます。

8
naXa

古いpostgresql.orgメーリングリスト から:

create table test ( a text, b text );

一意の値

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

重複する値

insert into test values ( 'x', 'y');
insert into test values ( 'x', 'x');
insert into test values ( 'y', 'y' );
insert into test values ( 'y', 'x' );

もう一つの二重複製

insert into test values ( 'x', 'y');

select oid, a, b from test;

重複する行を選択

select o.oid, o.a, o.b from test o
    where exists ( select 'x'
                   from test i
                   where     i.a = o.a
                         and i.b = o.b
                         and i.oid < o.oid
                 );

重複する行を削除する

注:PostgreSQLは、削除のfrom句に記載されているテーブルのエイリアスをサポートしていません。

delete from test
    where exists ( select 'x'
                   from test i
                   where     i.a = test.a
                         and i.b = test.b
                         and i.oid < test.oid
             );
7
Bhavik Ambani

Erwin Brandstetter's answer を使用して、結合テーブル(独自のプライマリIDがないテーブル)の重複を正常に削除しましたが、重要な注意事項が1つあることがわかりました。

ON COMMIT DROPを含めると、トランザクションの終了時に一時テーブルが削除されます。私にとって、それは一時テーブルがもう利用できなくなったことを意味しましたそれを挿入するまでに!

CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;を実行したところ、すべてうまくいきました。

一時テーブルは、セッションの終了時に削除されます。

4
codebykat

まず、保持する「複製」を決定する必要があります。すべての列が等しい場合は、OK、それらのいずれかを削除できます...しかし、おそらく、最新の基準または他の基準のみを保持したいでしょうか?

最速の方法は、上記の質問に対する回答と、テーブル上の重複の割合に依存します。行の50%を捨てる場合は、CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;、および行の1%を削除する場合は、DELETEを使用することをお勧めします。

また、このようなメンテナンス操作では、一般的にwork_mem RAMの適切なチャンク:EXPLAINを実行し、ソート/ハッシュの数Nを確認し、work_memをRAM/2/Nに設定します。RAMを大量に使用します。同時接続が1つしかない限り...

3
peufeu
DELETE FROM table
  WHERE something NOT IN
    (SELECT     MAX(s.something)
      FROM      table As s
      GROUP BY  s.this_thing, s.that_thing);
3
Secko

この関数は、インデックスを削除せずに重複を削除し、テーブルに対して実行します。

使用法:select remove_duplicates('mytable');

 --- 
 --- remove_duplicates(tablename)は、テーブルから重複レコードを削除します(セットから一意のセットに変換します)
 --- 
 CREATE OR置換関数remove_duplicates(text)戻り値void AS $$ 
 DECLARE 
 tablename ALIAS FOR $ 1; 
 BEGIN 
 EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' ||テーブル名|| 'AS(SELECT DISTINCT * FROM' ||テーブル名|| ');'; 
 EXECUTE 'DELETE FROM' ||テーブル名|| ';'; 
 EXECUTE 'INSERT INTO' ||テーブル名|| '(SELECT * FROM _DISTINCT_' ||テーブル名|| ');'; 
 EXECUTE 'DROP TABLE _DISTINCT_' ||テーブル名|| ';'; 
 RETURN; 
 END; 
 $$ LANGUAGE plpgsql; 
3
Ole Tange

重複するエントリが1つまたは少数で、実際にduplicated(つまり、2回表示される)である場合は、上記のように「非表示」ctid列を使用できます。 、LIMITとともに:

DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);

これにより、選択した行の最初の行のみが削除されます。

これは非常にうまく機能し、非常に迅速です:

CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
1
Mark Cupitt
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);

列ごとに重複を削除し、IDが最小の行を保持します。パターンは postgres wiki から取得されます

CTEを使用すると、上記のより読みやすいバージョンを実現できます。

WITH duplicate_ids as (
    SELECT id, rnum 
    FROM num_of_rows
    WHERE rnum > 1
),
num_of_rows as (
    SELECT id, 
        ROW_NUMBER() over (partition BY column1, 
                                        column2, 
                                        column3 ORDER BY id) AS rnum
        FROM tablename
)
DELETE FROM tablename 
WHERE id IN (SELECT id from duplicate_ids)
1
denplis
CREATE TABLE test (col text);
INSERT INTO test VALUES
 ('1'),
 ('2'), ('2'),
 ('3'),
 ('4'), ('4'),
 ('5'),
 ('6'), ('6');
DELETE FROM test
 WHERE ctid in (
   SELECT t.ctid FROM (
     SELECT row_number() over (
               partition BY col
               ORDER BY col
               ) AS rnum,
            ctid FROM test
       ORDER BY col
     ) t
    WHERE t.rnum >1);
1
Shamseer PC

PostgreSQL 8.4を使用しています。提案されたコードを実行したとき、実際に重複を削除していないことがわかりました。いくつかのテストを実行して、「DISTINCT ON(duplicate_column_name)」と「ORDER BY duplicate_column_name」を追加するとうまくいくことがわかりました。私はSQLの第一人者ではありません。PostgreSQL8.4 SELECT ... DISTINCT docでこれを見つけました。

CREATE OR REPLACE FUNCTION remove_duplicates(text, text) RETURNS void AS $$
DECLARE
  tablename ALIAS FOR $1;
  duplicate_column ALIAS FOR $2;
BEGIN
  EXECUTE 'CREATE TEMPORARY TABLE _DISTINCT_' || tablename || ' AS (SELECT DISTINCT ON (' || duplicate_column || ') * FROM ' || tablename || ' ORDER BY ' || duplicate_column || ' ASC);';
  EXECUTE 'DELETE FROM ' || tablename || ';';
  EXECUTE 'INSERT INTO ' || tablename || ' (SELECT * FROM _DISTINCT_' || tablename || ');';
  EXECUTE 'DROP TABLE _DISTINCT_' || tablename || ';';
  RETURN;
END;
$$ LANGUAGE plpgsql;
1
CM.