テーブルに重複した行があり、テーブルが大きいため、最も効率的な方法で重複を削除したい。いくつかの調査の後、私はこのクエリを思いついた:
WITH TempEmp AS
(
SELECT name, ROW_NUMBER() OVER(PARTITION by name, address, zipcode ORDER BY name) AS duplicateRecCount
FROM mytable
)
-- Now Delete Duplicate Records
DELETE FROM TempEmp
WHERE duplicateRecCount > 1;
ただし、Netezzaではなく、SQLでのみ機能します。 DELETE
句の後のWITH
が気に入らないように思えますか?
@ erwin-brandstetterのソリューションが好きですが、USING
キーワードを使用してソリューションを表示したかったのです。
_DELETE FROM table_with_dups T1
USING table_with_dups T2
WHERE T1.ctid < T2.ctid -- delete the "older" ones
AND T1.name = T2.name -- list columns that define duplicates
AND T1.address = T2.address
AND T1.zipcode = T2.zipcode;
_
レコードを削除する前に確認する場合は、DELETE
を_SELECT *
_に、USING
をコンマ_,
_に置き換えるだけです。
_SELECT * FROM table_with_dups T1
, table_with_dups T2
WHERE T1.ctid < T2.ctid -- select the "older" ones
AND T1.name = T2.name -- list columns that define duplicates
AND T1.address = T2.address
AND T1.zipcode = T2.zipcode;
_
更新:速度について、ここでいくつかの異なるソリューションをテストしました。多くの重複が予想されない場合、このソリューションはNOT IN (...)
句があるものよりも優れています。サブクエリで多くの行が生成されるためです。
IN (...)
を使用するようにクエリを書き換えると、ここで紹介したソリューションと同様に実行されますが、SQLコードの簡潔性は大幅に低下します。
更新2:キー列のいずれかにNULL
値がある場合(実際にはIMOを使用しないでください)、その列の条件でCOALESCE()
を使用できます。
_ AND COALESCE(T1.col_with_nulls, '[NULL]') = COALESCE(T2.col_with_nulls, '[NULL]')
_
他に一意の識別子がない場合は、ctid
を使用できます。
delete from mytable
where exists (select 1
from mytable t2
where t2.name = mytable.name and
t2.address = mytable.address and
t2.Zip = mytable.Zip and
t2.ctid > mytable.ctid
);
すべてのテーブルに一意の自動インクリメントIDを設定することをお勧めします。このようなdelete
を実行することが、重要な理由の1つです。
完全な世界では、everyテーブルにはある種の一意の識別子があります。
一意の列(またはその組み合わせ)がない場合は、 ctid
列 を使用します。
_DELETE FROM tbl
WHERE ctid NOT IN (
SELECT min(ctid) -- ctid is NOT NULL by definition
FROM tbl
GROUP BY name, address, zipcode); -- list columns defining duplicates
_
上記のクエリは短く、便利なように列名を一度だけリストします。 NOT IN (SELECT ...)
は、NULL値を含めることができる場合、トリッキーなクエリスタイルですが、システム列ctid
がNULLになることはありません。見る:
EXISTS
を @ Gordonでデモンストレーション として使用すると、通常は高速になります。 USING
句との自己結合もあります @ isapirが後で追加されたように 。どちらも同じクエリプランになります。
ただし、重要な違いに注意してください:これらの他のクエリは、NULL
値を-として扱います等しくない、_GROUP BY
_(またはDISTINCT
または DISTINCT ON ()
)はNULL値を等しいものとして扱います。キー列が_NOT NULL
_に定義されているかどうかは関係ありません。それ以外の場合、「重複」の定義に応じて、いずれかのアプローチが必要になります。 または値の比較で _IS NOT DISTINCT FROM
_ を使用します(一部のインデックスを使用できない場合があります)。
免責事項:
ctid
はPostgresの内部実装の詳細であり、SQL標準ではなく、警告なしにメジャーバージョン間で変更することができます(それは非常にまれです)。その値は、バックグラウンドプロセスまたは同時書き込み操作のためにコマンド間で変更できます(ただし、同じコマンド内では変更できません)。
関連する:
余談:
DELETE
ステートメントのターゲットをCTEにすることはできません。基になるテーブルのみです。これはSQL Serverからの波及です-あなたのアプローチ全体もそうです。
ここに私が思いついたものがあり、group by
DELETE FROM mytable
WHERE id NOT in (
SELECT MIN(id)
FROM mytable
GROUP BY name, address, zipcode
)
重複を削除し、重複がある最も古いレコードを保持します。
ウィンドウ関数を使用して、重複する行を非常に効果的に削除できます。
DELETE FROM tab
WHERE id IN (SELECT id
FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), id
FROM tab) x
WHERE x.row_number > 1);
いくつかのPostgreSQLの最適化バージョン(ctidを使用):
DELETE FROM tab
WHERE ctid = ANY(ARRAY(SELECT ctid
FROM (SELECT row_number() OVER (PARTITION BY column_with_duplicate_values), ctid
FROM tab) x
WHERE x.row_number > 1));
有効な構文は http://www.postgresql.org/docs/current/static/sql-delete.html で指定されています
テーブルを変更して、一意の自動増分主キーIDを追加し、次のようなクエリを実行して、重複の各セットの最初の(つまり、最も低いIDを持つ)クエリを実行できるようにします。 Postgresでは、キーの追加は他のDBよりも少し複雑であることに注意してください。
DELETE FROM mytable d USING (
SELECT min(id), name, address, Zip
FROM mytable
GROUP BY name, address, Zip HAVING COUNT() > 1
) AS k
WHERE d.id <> k.id
AND d.name=k.name
AND d.address=k.address
AND d.Zip=k.Zip;
テーブル内の重複する行から1つの行を保持する場合。
create table some_name_for_new_table as
(select * from (select *,row_number() over (partition by pk_id) row_n from
your_table_name_where_duplicates_are_present) a where row_n = 1);
これにより、コピー可能なテーブルが作成されます。
テーブルをコピーする前に、列「row_n」を削除してください
すべての行に一意の識別子が必要な場合は、1つ(シリアルまたはGUID)を追加し、それを代理キーのように扱うことができます。
CREATE TABLE thenames
( name text not null
, address text not null
, zipcode text not null
);
INSERT INTO thenames(name,address,zipcode) VALUES
('James', 'main street', '123' )
,('James', 'main street', '123' )
,('James', 'void street', '456')
,('Alice', 'union square' , '123')
;
SELECT*FROM thenames;
-- add a surrogate key
ALTER TABLE thenames
ADD COLUMN seq serial NOT NULL PRIMARY KEY
;
SELECT*FROM thenames;
DELETE FROM thenames del
WHERE EXISTS(
SELECT*FROM thenames x
WHERE x.name=del.name
AND x.address=del.address
AND x.zipcode=del.zipcode
AND x.seq < del.seq
);
-- add the unique constrain,so that new dupplicates cannot be created in the future
ALTER TABLE thenames
ADD UNIQUE (name,address,zipcode)
;
SELECT*FROM thenames;
ドキュメントから 重複行の削除
IRCでよくある質問は、列のセットで重複している行を削除し、IDが最小の行のみを保持する方法です。このクエリは、同じ列を持つtablenameのすべての行に対して行います。 、column2、column3。
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フィールドの代わりにタイムスタンプフィールドが使用される場合があります。