web-dev-qa-db-ja.com

一意の識別子なしで重複行を削除する方法

テーブルに重複した行があり、テーブルが大きいため、最も効率的な方法で重複を削除したい。いくつかの調査の後、私はこのクエリを思いついた:

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が気に入らないように思えますか?

40
moe

@ 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]')
_
31
isapir

他に一意の識別子がない場合は、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つです。

52
Gordon Linoff

完全な世界では、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からの波及です-あなたのアプローチ全体もそうです。

21

ここに私が思いついたものがあり、group by

DELETE FROM mytable
WHERE id NOT in (
  SELECT MIN(id) 
  FROM mytable
  GROUP BY name, address, zipcode
)

重複を削除し、重複がある最も古いレコードを保持します。

10
Bruno Calza

ウィンドウ関数を使用して、重複する行を非常に効果的に削除できます。

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));
6
Vivek S.

有効な構文は 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;
2
Joe Murray

テーブル内の重複する行から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

すべての行に一意の識別子が必要な場合は、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;
0
wildplasser

ドキュメントから 重複行の削除

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フィールドの代わりにタイムスタンプフィールドが使用される場合があります。

0
Chad Crowe