2

、、、、のような列を持つtaccountsテーブルがあります。ここで、新しいビジネス ロジックに従って、いくつかの重複エントリを削除する必要があります。したがって、重複するアカウントは、同じまたは同じ ( & ) になります。最後にログインしたアカウントを保持する必要があります。account_id(PK)login_namepasswordlast_loginemail login_namepassword

これが私の試みです(一部のメール値はnullで空白です)

DELETE
FROM taccounts
WHERE email is not null and char_length(trim(both ' ' from email))>0 and last_login NOT IN
(
SELECT MAX(last_login)
FROM taccounts
WHERE email is not null and char_length(trim(both ' ' from email))>0 
GROUP BY lower(trim(both ' ' from email)))

同様にlogin_namepassword

DELETE
FROM taccounts
WHERE last_login NOT IN
(
SELECT MAX(last_login)
FROM taccounts
GROUP BY login_name, password)

これら2つの別々のクエリを組み合わせるより良い方法または方法はありますか?

また、他のいくつかのテーブルはaccount_id外部キーとして持っています。これらのテーブルのこの変更を更新する方法は? ` PostgreSQL 9.2.1 を使用しています

編集: メールの値の一部は null で、一部は空白 ('') です。そのため、2 つのアカウントが異なる login_name と password を持ち、それらの電子メールが null または空白である場合、それらは 2 つの異なるアカウントと見なす必要があります。

4

2 に答える 2

1

Erwin の優れた回答に加えて、古いキーと新しいキーを関連付ける中間リンク テーブルを作成すると役立つことがよくあります。

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE taccounts
        ( account_id SERIAL PRIMARY KEY
        , login_name varchar
        , email varchar
        , last_login TIMESTAMP
        );
    -- create some fake data
INSERT INTO taccounts(last_login)
SELECT gs FROM generate_series('2013-03-30 14:00:00' ,'2013-03-30 15:00:00' , '1min'::interval) gs
        ;
UPDATE taccounts
SET login_name = 'User_' || (account_id %10)::text
        , email = 'Joe' || (account_id %9)::text || '@somedomain.tld'
        ;

SELECT * FROM taccounts;

        --
        -- Create (temp) table linking old id <--> new id
        -- After inspection this table can be used as a source for the FK updates
        -- and for the final delete.
        --
CREATE TABLE update_ids AS
WITH pairs AS (
        SELECT one.account_id AS old_id
        , two.account_id AS new_id
        FROM taccounts one
        JOIN taccounts two ON two.last_login > one.last_login
                AND ( two.email = one.email OR two.login_name = one.login_name)
        )
SELECT old_id,new_id
FROM pairs pp
WHERE NOT EXISTS (
        SELECT * FROM pairs nx
        WHERE nx.old_id = pp.old_id
        AND nx.new_id > pp.new_id
        )
        ;

SELECT * FROM update_ids
        ;

UPDATE other_table_with_fk_to_taccounts dst
SET account_id. = ids.new_id
FROM update_ids ids
WHERE account_id. = ids.old_id
        ;
DELETE FROM taccounts del
WHERE EXISTS (
        SELECT * FROM update_ids ex
        WHERE ex.old_id = del.account_id
        );

SELECT * FROM taccounts;

同じことを達成するさらに別の方法は、優先キーへのポインターを持つ列をテーブル自体に追加し、それを更新と削除に使用することです。

ALTER TABLE taccounts
        ADD COLUMN better_id INTEGER REFERENCES taccounts(account_id)
        ;

   -- find the *better* records for each record.
UPDATE taccounts dst
SET better_id = src.account_id
FROM taccounts src
WHERE src.login_name = dst.login_name
AND src.last_login > dst.last_login
AND src.email IS NOT NULL
AND NOT EXISTS (
        SELECT * FROM taccounts nx
        WHERE nx.login_name = dst.login_name
        AND nx.email IS NOT NULL
        AND nx.last_login > src.last_login
        );

    -- Find records that *do* have an email address
UPDATE taccounts dst
SET better_id = src.account_id
FROM taccounts src
WHERE src.login_name = dst.login_name
AND src.email IS NOT NULL
AND dst.email IS NULL
AND NOT EXISTS (
        SELECT * FROM taccounts nx
        WHERE nx.login_name = dst.login_name
        AND nx.email IS NOT NULL
        AND nx.last_login > src.last_login
        );

SELECT * FROM taccounts ORDER BY account_id;

UPDATE other_table_with_fk_to_taccounts dst
SET account_id = src.better_id
FROM update_ids src
WHERE dst.account_id = src.account_id
AND src.better_id IS NOT NULL
        ;

DELETE FROM taccounts del
WHERE EXISTS (
        SELECT * FROM taccounts ex
        WHERE ex.account_id = del.better_id
        );
SELECT * FROM taccounts ORDER BY account_id;
于 2013-03-30T13:17:39.827 に答える
1

ほとんどの行が削除され (ほとんどが重複)、テーブルが RAM に収まる場合は、次のルートを検討してください。

  1. SELECT生き残った行を一時テーブルに。
  2. FK 参照をサバイバーに再ルーティングする
  3. DELETE ベース テーブルのすべての行。
  4. INSERT生存者。

1a. 生き残った行を蒸留する

CREATE TEMP TABLE tmp AS
SELECT DISTINCT ON (login_name, password) *
FROM  (
   SELECT DISTINCT ON (email) *
   FROM   taccounts
   ORDER  BY email, last_login DESC
   ) sub
ORDER  BY login_name, password, last_login DESC;

DISTINCT ON:

2 つの異なる条件の重複を識別するには、サブクエリを使用して 2 つのルールを順番に適用します。最初のステップでは、最新のアカウントを保持するlast_loginため、これは「シリアル化可能」です。

結果を検査し、妥当性をテストします。

SELECT * FROM tmp;

一時テーブルは、セッションの終了時に自動的に削除されます。pgAdmin(使用しているようです)では、エディターウィンドウが開いている限り、セッションは存続します。

1b. 「重複」の更新された定義の代替クエリ

SELECT *
FROM   taccounts t
WHERE  NOT EXISTS (
   SELECT  FROM taccounts t1
   WHERE  ( NULLIF(t1.email, '') = t.email
        OR (NULLIF(t1.login_name, ''), NULLIF(t1.password, '')) = (t.login_name, t.password))
   AND   (t1.last_login, t1.account_id) > (t.last_login, t.account_id)
   );

これは、「重複」列のいずれかで同一として扱わないNULLか、空の文字列 ( ) を扱いません。''

行式(t1.last_login, t1.account_id)は、2 つの重複が同じ を共有する可能性に対処しlast_loginます。この場合、大きいaccount_id方が選択されます。これは PK であるため、一意です。

2a. すべての着信 FK を識別する方法

SELECT c.confrelid::regclass::text AS referenced_table
     , c.conname AS fk_name
     , pg_get_constraintdef(c.oid) AS fk_definition
FROM   pg_attribute a 
JOIN   pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum)
WHERE  c.confrelid = 'taccounts'::regclass   -- (schema-qualified) table name
AND    c.contype  = 'f'
ORDER  BY 1, contype DESC;

外部キーの最初の列のみを構築します。それについての詳細:

またはDependents、テーブルを選択した後、pgAdmin のオブジェクト ブラウザの右側のウィンドウでライダーを調べますtaccounts

2b. 新しいプライマリに再ルーティングします

参照しているテーブルがある場合taccounts(の着信外部キー) 、重複を削除する前に、それらすべてのフィールドを更新する必要があります。 それらすべてを新しいプライマリ行に再ルーティングします。 taccounts

UPDATE referencing_tbl r
SET    referencing_column = tmp.reference_column
FROM   tmp
JOIN   taccounts t1 USING (email)
WHERE  r.referencing_column = t1.referencing_column
AND    referencing_column IS DISTINCT FROM tmp.reference_column;

UPDATE referencing_tbl r
SET    referencing_column = tmp.reference_column
FROM   tmp
JOIN   taccounts t2 USING (login_name, password)
WHERE  r.referencing_column = t1.referencing_column
AND    referencing_column IS DISTINCT FROM tmp.reference_column;

3. & 4. 殺しに行く

現在、重複は参照されなくなりました。殺しに行く。

ALTER TABLE taccounts DISABLE TRIGGER ALL;
DELETE FROM taccounts;
VACUUM taccounts;
INSERT INTO taccounts
SELECT * FROM tmp;
ALTER TABLE taccounts ENABLE TRIGGER ALL;

操作中はすべてのトリガーを無効にします。これにより、操作中の参照整合性のチェックが回避されます。トリガーを再度アクティブにすると、すべてがうまくいくはずです。上記のすべての着信FK を処理しました。同時書き込みアクセスがなく、すべての値が以前にそこにあったため、発信FK は健全であることが保証されます。

于 2013-03-30T11:14:17.617 に答える