既存のテーブルに一意の制約を追加する必要があります。これは、テーブルにすでに数百万の行があり、行の多くが追加する必要のある一意の制約に違反していることを除き、問題ありません。
問題のある行を削除する最速のアプローチは何ですか?重複を見つけて削除するSQLステートメントがありますが、実行に時間がかかります。この問題を解決する別の方法はありますか?テーブルをバックアップしてから、制約が追加された後に復元するのでしょうか?
たとえば、次のことができます。
CREATE TABLE tmp ...
INSERT INTO tmp SELECT DISTINCT * FROM t;
DROP TABLE t;
ALTER TABLE tmp RENAME TO t;
これらのアプローチのいくつかは少し複雑に思えますが、私は通常これを次のように行います。
テーブル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に言及しています。新しいテーブルを作成する代わりに、切り捨てた後、同じテーブルに一意の行を再挿入することもできます。 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を作成しようとしたときに例外が発生します。
TRUNCATE
はDELETE
よりも積極的なロックを必要とすることに注意してください。これは、重い同時ロードのあるテーブルの問題である可能性があります。
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テーブルの作成はかなり速くなります。これは、依存するオブジェクトで発生する可能性のあるトラブル/オーバーヘッドと比較検討する必要があります。
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
列の値は、テーブル内のすべての行で一意と見なされます。
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
);
Erwin Brandstetter's answer を使用して、結合テーブル(独自のプライマリ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);
この関数は、インデックスを削除せずに重複を削除し、テーブルに対して実行します。
使用法: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;
重複するエントリが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;
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);
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;