既存のテーブルに一意の制約を追加する必要があります。これは、テーブルにすでに数百万の行があり、行の多くが追加する必要のある一意の制約に違反していることを除いて、問題ありません。
問題のある行を削除するための最速のアプローチは何ですか?重複を見つけて削除するSQLステートメントがありますが、実行には永遠に時間がかかります。この問題を解決する別の方法はありますか?たぶん、テーブルをバックアップし、制約が追加された後に復元しますか?
既存のテーブルに一意の制約を追加する必要があります。これは、テーブルにすでに数百万の行があり、行の多くが追加する必要のある一意の制約に違反していることを除いて、問題ありません。
問題のある行を削除するための最速のアプローチは何ですか?重複を見つけて削除するSQLステートメントがありますが、実行には永遠に時間がかかります。この問題を解決する別の方法はありますか?たぶん、テーブルをバックアップし、制約が追加された後に復元しますか?
これらのアプローチのいくつかは少し複雑に見えます、そして私は一般的にこれを次のように行います:
与えられたテーブル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について言及しています。たとえば、次のことができます。
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
新しいテーブルを作成する代わりに、切り捨てた後に同じテーブルに一意の行を再挿入することもできます。すべてを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 tbl
DELETE
大きなテーブルの場合、インデックスと外部キー(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に収まらない非常に大きなテーブルの場合、新しいテーブルの作成はかなり高速になります。これを、オブジェクトに応じて発生する可能性のあるトラブル/オーバーヘッドと比較検討する必要があります。
oidまたはctidを使用できます。これは通常、テーブルの「非表示」列です。
DELETE FROM table
WHERE ctid NOT IN
(SELECT MAX(s.ctid)
FROM table s
GROUP BY s.column_has_be_distinct);
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);
重複の削除を参照してください。
重複を削除するための一般化されたクエリ:
DELETE FROM table_name
WHERE ctid NOT IN (
SELECT max(ctid) FROM table_name
GROUP BY column1, [column 2, ...]
);
この列ctid
は、すべてのテーブルで使用できる特別な列ですが、特に明記されていない限り表示されません。ctid
列の値は、テーブル内のすべての行で一意であると見なされます。詳細については、 PostgreSQLシステムの列を参照してくださいctid
。
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
);
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
);
この関数は、インデックスを削除せずに重複を削除し、任意のテーブルに対して実行します。
使用法: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;
Erwin Brandstetterの回答を使用して、結合テーブル(独自のプライマリIDがないテーブル)の重複を削除しましたが、重要な注意点が1つあることがわかりました。
含めるON COMMIT DROP
とは、トランザクションの終了時に一時テーブルが削除されることを意味します。私にとって、それは私がそれを挿入しようとしたときまでに一時テーブルがもはや利用できないことを意味しました!
私はちょうどやっただけCREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
で、すべてがうまくいきました。
一時テーブルは、セッションの終了時に削除されます。
まず、どの「複製」を保持するかを決定する必要があります。すべての列が等しい場合は、OK、それらのいずれかを削除できます...しかし、おそらく最新の、または他の基準のみを保持したいですか?
最速の方法は、上記の質問に対する回答と、テーブル上の重複の割合によって異なります。行の50%を破棄する場合は、CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
を実行する方が適切です。行の1%を削除する場合は、DELETEを使用する方が適切です。
また、このようなメンテナンス操作の場合work_mem
は、RAMの適切なチャンクに設定することをお勧めします。EXPLAINを実行し、ソート/ハッシュの数Nを確認し、work_memをRAM / 2/Nに設定します。大量のRAMを使用します。スピードに良いです。同時接続が1つしかない限り...
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);
これにより、選択した最初の行のみが削除されます。
私は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;
これは非常にうまく機能し、非常に高速です。
CREATE INDEX otherTable_idx ON otherTable( colName );
CREATE TABLE newTable AS select DISTINCT ON (colName) col1,colName,col2 FROM otherTable;
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);
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)