92

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

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

4

16 に答える 16

174

これらのアプローチのいくつかは少し複雑に見えます、そして私は一般的にこれを次のように行います:

与えられたテーブルtableで、(field1、field2)で一意にし、行を最大field3に保ちます。

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について言及しています。
于 2010-12-14T18:34:14.307 に答える
102

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

CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
于 2009-11-17T02:36:47.063 に答える
26

新しいテーブルを作成する代わりに、切り捨てた後に同じテーブルに一意の行を再挿入することもできます。すべてを1つのトランザクションで実行します。

このアプローチは、テーブル全体から削除する行がたくさんある場合にのみ役立ちます。ほんの少しの複製には、プレーンを使用しますDELETE

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

SELECT pg_size_pretty(pg_relation_size('tbl'));

temp_buffers少なくともその少し上に設定します。

SET temp_buffers = 200MB;   -- example value

BEGIN;

CREATE TEMP TABLE t_tmp AS  -- retains temp for duration of session
SELECT DISTINCT * FROM tbl  -- DISTINCT folds duplicates
ORDER  BY id;               -- optionally "cluster" data

TRUNCATE tbl;

INSERT INTO tbl
SELECT * FROM t_tmp;        -- retains order (implementation detail)

COMMIT;

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

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

TRUNCATEよりも積極的なロックが必要であることに注意してくださいDELETE。これは、同時負荷が大きいテーブルでは問題になる可能性があります。ただし、テーブルを完全に削除して交換するよりも混乱は少なくなります。

がオプションではない場合TRUNCATE、または一般的に中小規模のテーブルの場合、データ変更CTE(Postgres 9.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に収まらない非常に大きなテーブルの場合、新しいテーブルの作成はかなり高速になります。これを、オブジェクトに応じて発生する可能性のあるトラブル/オーバーヘッドと比較検討する必要があります。

于 2012-01-11T21:26:36.490 に答える
20

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

DELETE FROM table
 WHERE ctid NOT IN
  (SELECT MAX(s.ctid)
    FROM table s
    GROUP BY s.column_has_be_distinct);
于 2011-05-12T11:05:16.557 に答える
19

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

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

于 2013-04-04T10:33:35.613 に答える
9

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

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

この列ctidは、すべてのテーブルで使用できる特別な列ですが、特に明記されていない限り表示されません。ctid列の値は、テーブル内のすべての行で一意であると見なされます。詳細については、 PostgreSQLシステムの列を参照してくださいctid

于 2016-04-11T23:01:54.010 に答える
7

古い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' );

もう1つの二重複製

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

重複する行を削除する

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

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
             );
于 2012-02-13T12:57:12.777 に答える
4

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

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

---
--- remove_duplicates(tablename)は、テーブルから重複レコードを削除します(セットから一意のセットに変換します)
---
CREATE OR REPLACE FUNCTION remove_duplicates(text)RETURNS void AS $$
宣言する
  テーブル名ALIASFOR$ 1;
始める
  EXECUTE'CREATE TEMPORARY TABLE _DISTINCT_' || テーブル名|| 'AS(SELECT DISTINCT * FROM'||テーブル名||');';
  EXECUTE'DELETE FROM' || テーブル名|| ';';
  EXECUTE'INSERT INTO' || テーブル名|| '(SELECT * FROM _DISTINCT_'||テーブル名||');';
  EXECUTE'DROP TABLE _DISTINCT_' || テーブル名|| ';';
  戻る;
終わり;
$$言語plpgsql;
于 2009-12-01T18:25:55.623 に答える
4

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

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

私はちょうどやっただけCREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;で、すべてがうまくいきました。

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

于 2013-11-03T18:27:19.723 に答える
3

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

最速の方法は、上記の質問に対する回答と、テーブル上の重複の割合によって異なります。行の50%を破棄する場合は、CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;を実行する方が適切です。行の1%を削除する場合は、DELETEを使用する方が適切です。

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

于 2009-11-22T01:31:12.387 に答える
3
DELETE FROM table
  WHERE something NOT IN
    (SELECT     MAX(s.something)
      FROM      table As s
      GROUP BY  s.this_thing, s.that_thing);
于 2009-11-17T02:38:59.263 に答える
3

重複するエントリが1つまたは少数しかなく、実際に重複している(つまり、2回表示される)場合は、ctid上記で提案したように、「非表示」列を次のコマンドと一緒に使用できLIMITます。

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

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

于 2014-09-15T16:48:00.920 に答える
1

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

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;
于 2010-02-15T23:16:39.127 に答える
1

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

CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
于 2013-10-28T05:35:50.680 に答える
1
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);
于 2013-11-21T08:55:21.043 に答える
1
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が最小の行を保持します。パターンはpostgreswikiから取得されます

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)
于 2017-02-18T10:07:24.627 に答える