回答:
たとえば、次のことができます。
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
CREATE TABLE tmp AS SELECT ...;
。そうすれば、レイアウトが何であるかを理解する必要さえありませんtmp
。:)
これらのアプローチのいくつかは少し複雑に見えますが、私は通常これを次のように行います:
与えられたtable 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の最大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に言及しています。USING
は、postgresqlで何ができるかをよりよく説明できますか?
WHERE table1.ctid<table2.ctid
-シリアル列を追加する必要はありません
新しいテーブルを作成する代わりに、一意の行を切り捨てた後で同じテーブルに再挿入することもできます。すべてを1つのトランザクションで実行します。オプションで、トランザクションの終了時に一時テーブルをで自動的に削除できますON COMMIT DROP
。下記参照。
この方法は、テーブル全体から削除する行がたくさんある場合にのみ役立ちます。重複が少ししかない場合は、プレーンを使用しますDELETE
。
数百万行について言及しました。操作を高速にするには、セッションに十分な一時バッファーを割り当てます。現在のセッションで一時バッファが使用される前に、設定を調整する必要があります。テーブルのサイズを確認します。
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;
この方法は、依存するオブジェクトが存在する場合、新しいテーブルを作成するよりも優れています。テーブルを参照するビュー、インデックス、外部キー、またはその他のオブジェクト。TRUNCATE
とにかく白紙の状態(バックグラウンドの新しいファイル)から始め、大きなテーブルよりもはるかに高速ですDELETE FROM tbl
(DELETE
実際には小さなテーブルの方が高速です)。
大きなテーブルの場合、インデックスと外部キーを削除し、テーブルにデータを再入力してこれらのオブジェクトを再作成する方が通常は高速です。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に収まらない非常に大きなテーブルの場合、新しいテーブルの作成はかなり高速になります。これを、依存するオブジェクトで発生する可能性のあるトラブル/オーバーヘッドと比較検討する必要があります。
TRUNCATE
。アーウィンが言ったように、テーブルを切り捨てる前に、それが存在することを確認してください。@codebykatの回答を参照してください
ON COMMIT DROP
ので、「1つのトランザクションで」書いた部分を見逃した人がデータを失うことはありません。そして、「1つのトランザクション」を明確にするためにBEGIN / COMMITを追加しました。
oidまたはctidを使用できます。これは通常、テーブルの「非表示」列です。
DELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
NOT EXISTS
かなり速くする必要があります:DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
-または生存者を選択するためにソートするための列の他の列またはセットを使用します。
NOT EXISTS
か?
EXISTS
ここにあるに違いない。次のように読みます:「他の行が同じ値であるdist_col
がより大きい行が存在するすべての行を削除しますctid
」。群れのグループあたりの唯一の生存者は、最大のものctid
です。
LIMIT
重複の数がわかっている場合に使用できます。
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);
重複の削除を参照してください。
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
);
重複を削除するための一般化されたクエリ:
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
この列ctid
は、すべてのテーブルで使用できる特別な列ですが、特に記載がない限り表示されません。ctid
カラムの値はテーブルのすべての行に対して一意であると考えられます。
GROUP BY
句を正しく指定することは不可欠です。これは、現在違反している、または重複を検出するためのキーが必要な場合に「一意性の基準」になるはずです。指定が間違っていると、正しく機能しません
Erwin Brandstetterの回答を使用しましたして結合テーブル(独自のプライマリIDがないテーブル)の重複を削除したところ、重要な注意点が1つあることがわかりました。
含めるON COMMIT DROP
と、トランザクションの終了時に一時テーブルが削除されます。私にとっては、一時テーブルが利用できなくなったことを意味します挿入しようとした時点でていました。
今やりました CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
、すべてがうまく。
一時テーブルは、セッションの終了時に削除されます。
この関数は、インデックスを削除せずに重複を削除し、任意のテーブルに対して実行します。
使用法: select remove_duplicates('mytable');
--- --- remove_duplicates(tablename)はテーブルから重複レコードを削除します(セットから一意のセットに変換します) --- CREATE OR REPLACE FUNCTION remove_duplicates(text)RETURNS void AS $$ 宣言する $ 1のテーブル名エイリアス。 ベギン 「一時テーブルの作成_DISTINCT_」を実行|| テーブル名|| 'AS(SELECT DISTINCT * FROM' ||テーブル名|| ');'; 'DELETE FROM'を実行する|| テーブル名|| ';'; 'INSERT INTO'を実行する|| テーブル名|| '(SELECT * FROM _DISTINCT_' || tablename || ');'; 'DROP TABLE _DISTINCT_'を実行する|| テーブル名|| ';'; 戻る; 終わり; $$言語plpgsql;
DELETE FROM table
WHERE something NOT IN
(SELECT MAX(s.something)
FROM table As s
GROUP BY s.this_thing, s.that_thing);
重複するエントリが1つまたは少数しかなく、実際に重複している(つまり、2回表示される)場合はctid
、上記のように、「非表示」列を次のように使用できますLIMIT
。
DELETE FROM mytable WHERE ctid=(SELECT ctid FROM mytable WHERE […] LIMIT 1);
これにより、選択した行の最初の行のみが削除されます。
まず、どの「複製」を保持するかを決定する必要があります。すべての列が等しい場合は、OK、それらのいずれかを削除できます...しかし、おそらく最新の基準または他の基準のみを保持したいですか?
最速の方法は、上記の質問への回答と、テーブル上の重複の割合によって異なります。行の50%を破棄する場合は、を実行した方CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
がよいでしょう。行の1%を削除する場合は、DELETEを使用する方が適切です。
また、このようなメンテナンス操作の場合、一般的にwork_mem
は、RAMの適切なチャンクに設定することをお勧めします。EXPLAINを実行し、並べ替え/ハッシュの数Nを確認して、work_memをRAM / 2 / Nに設定します。RAMを大量に使用します。スピードにいいです。同時接続が1つしかない限り...
私はPostgreSQL 8.4を使用しています。提案されたコードを実行したところ、実際には重複を削除していないことがわかりました。いくつかのテストを実行したところ、「DISTINCT ON(duplicate_column_name)」と「ORDER BY duplicate_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;
これは非常にうまく機能し、非常に高速です:
CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
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)
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);