web-dev-qa-db-ja.com

postgresのレコード間で主キーを交換する方法

私はこれをトランザクションで達成する方法を見つけることができないようです(またはそうではありません)

私が達成する必要があるのは標準ではないため、解決策を見つけるのが困難です。

テーブル内の古いレコードを新しいレコードと「交換」するようにデータ移行ツールをコーディングする必要がありますが、次の要件/制約があります。

  • 古い記録を失わないでください
  • 古いレコードへのすべての参照が新しいレコードを指すようにします(これは単なるdb外部キーではなく、外部サービス、キャッシュ、電子メール、履歴データ、ブックマークで自分の制御の外にある参照であり、名前を付けます)
  • 移行コードはスキーマに依存しない必要があります。つまり、新しい列がテーブルに追加され、他のどのテーブルがそれを参照しているかに依存しない場合は、更新する必要はありません。
  • 2つのレコードを更新するのに必要な許容時間を超えてテーブルをロックできません。

したがって、私の理想的なソリューションは、主キーをブルートスワップすることです...

問題は、postgresqlの2つのレコード間で主キーをどのように交換できるかです。重複キーの例外で失敗しないアプローチ、つまりトランザクションの更新の「検証」を実行するアプローチを見つけるのが困難です。

私が試してみました

UPDATE table
SET id = (CASE id WHEN 1 THEN 2 WHEN 2 THEN 1 ELSE id END)
UPDATE table
SET id = CASE id WHEN 1 THEN 2 WHEN 2 THEN 1 END
WHERE id IN (1, 2);

どちらも重複キー制約で失敗する

PostgreSQL 11.6を使用しています


解決した

以下のニースの人々からの入力のおかげで、数値の主キーの一括更新のソリューション:

-- disable foreign key constraint validation
BEGIN;
SET session_replication_role='replica';

-- update the pairs of ids to their negative counterparts
WITH query AS ('query to get pairs of ids to swap')
UPDATE table 
SET id = -id 
WHERE id in (query.id1, query.id2);

-- update the pairs of negated ids to their positive counterparts swapped
WITH query AS ('query to get pairs of ids to swap')
UPDATE table 
SET id = CASE id WHEN query.id1 THEN -query.id2
                 WHEN query.id2 THEN -query.id1
                 END
WHERE id in (query.id1, query.id2);

-- enable foreign key constraint validation
SET session_replication_role='original';
COMMIT;
6
Pedro Borges

遅延制約を使用できます。そのためには、主キーを削除して再作成する必要があります。

CREATE UNIQUE INDEX mytable_primkey ON mytable (id);
ALTER TABLE mytable DROP CONSTRAINT mytable_pkey;
ALTER TABLE mytable ADD PRIMARY KEY USING INDEX mytable_primkey
   DEFERRABLE INITIALLY DEFERRED;

更新自体は、次のように実行できます。

UPDATE mytable SET id = 3 - id WHERE id IN (1, 2);

ここでは1と2が例として使用されていますが、これは任意の数値で実行できます。

延期された主キーを追加するために必要なダウンタイムが許されない場合は、次のようなもう1つの更新でそれを行うことができます。

BEGIN;
UPDATE mytable SET id = 0 WHERE id = 1;
UPDATE mytable SET id = 1 WHERE id = 2;
UPDATE mytable SET id = 2 WHERE id = 0;
COMMIT;

ここで、0はidの値として使用されない任意の値です。

4
Laurenz Albe

これが可能であると私が知る唯一の方法は、遅延制約を使用することです。

主キーを削除する必要があります。

alter table x drop constraint x_pkey;

そして延期可能として再びそれを追加します:

alter table x add primary key (id) deferrable initially immediate;

更新を実行する前に、制約を延期できます。

set constraints x_pkey deferred;

次に、更新とcommitを実行します。

https://dbfiddle.uk/?rdbms=postgres_12&fiddle=469e5168c84bdbb8361426d4458bcf88

もちろん、このテーブルを参照する他のテーブルがある場合は、外部キーも削除して再作成する必要があります。

4
Colin 't Hart