データベーステーブルがあり、フィールドの1つ(主キーではない)に一意のインデックスがあります。次に、この列の下の値を2行に交換します。これはどのように行うことができますか?私が知っている2つのハックは次のとおりです。
- 両方の行を削除して、それらを再挿入します。
- 他の値で行を更新し、スワップしてから実際の値に更新します。
しかし、これらは問題の適切な解決策ではないように思われるので、私はこれらに行きたくありません。誰か助けてもらえますか?
魔法の言葉はDEFERRABLEです。
DROP TABLE ztable CASCADE;
CREATE TABLE ztable
( id integer NOT NULL PRIMARY KEY
, payload varchar
);
INSERT INTO ztable(id,payload) VALUES (1,'one' ), (2,'two' ), (3,'three' );
SELECT * FROM ztable;
-- This works, because there is no constraint
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
ALTER TABLE ztable ADD CONSTRAINT OMG_WTF UNIQUE (payload)
DEFERRABLE INITIALLY DEFERRED
;
-- This should also work, because the constraint
-- is deferred until "commit time"
UPDATE ztable t1
SET payload=t2.payload
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
結果:
DROP TABLE
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ztable_pkey" for table "ztable"
CREATE TABLE
INSERT 0 3
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
UPDATE 2
id | payload
----+---------
1 | one
2 | three
3 | two
(3 rows)
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "omg_wtf" for table "ztable"
ALTER TABLE
UPDATE 2
id | payload
----+---------
1 | one
2 | two
3 | three
(3 rows)
私はあなたが解決策2に行くべきだと思います。私が知っているSQLバリアントには「スワップ」関数はありません。
これを定期的に行う必要がある場合は、ソフトウェアの他の部分がこのデータをどのように使用しているかに応じて、ソリューション1をお勧めします。注意しないと、ロックの問題が発生する可能性があります。
しかし、要するに、あなたが提供したもの以外の解決策はありません。
SQL Serverで機能する別のアプローチがあります。それは、UPDATEステートメントで一時テーブル結合を使用することです。
この問題は、同じ値の2つの行が同時に存在することが原因で発生しますが、両方の行を同時に(新しい一意の値に)更新する場合、制約違反はありません。
擬似コード:
-- setup initial data values:
insert into data_table(id, name) values(1, 'A')
insert into data_table(id, name) values(2, 'B')
-- create temp table that matches live table
select top 0 * into #tmp_data_table from data_table
-- insert records to be swapped
insert into #tmp_data_table(id, name) values(1, 'B')
insert into #tmp_data_table(id, name) values(2, 'A')
-- update both rows at once! No index violations!
update data_table set name = #tmp_data_table.name
from data_table join #tmp_data_table on (data_table.id = #tmp_data_table.id)
このテクニックを提供してくれたRichHに感謝します。- マーク
Andy Irvingの答えにさらに
これは、複合キーがあり、一意の制約の一部であるフィールドを交換する必要がある同様の状況で(SQL Server 2005で)うまくいきました。
キー: pID、LNUM rec1: 10, 0 rec2: 10, 1 rec3: 10, 2
結果が
キー: pID、LNUM rec1: 10、1 rec2: 10、2 rec3: 10、0
必要な SQL:
UPDATE DOCDATA
SET LNUM = CASE LNUM
WHEN 0 THEN 1
WHEN 1 THEN 2
WHEN 2 THEN 0
END
WHERE (pID = 10)
AND (LNUM IN (0, 1, 2))
更新する 2 つの行の PK がわかっていると仮定すると、これは SQL Server で機能しますが、他の製品については言えません。SQL は、ステートメント レベルで (と思われる) アトミックです。
CREATE TABLE testing
(
cola int NOT NULL,
colb CHAR(1) NOT NULL
);
CREATE UNIQUE INDEX UIX_testing_a ON testing(colb);
INSERT INTO testing VALUES (1, 'b');
INSERT INTO testing VALUES (2, 'a');
SELECT * FROM testing;
UPDATE testing
SET colb = CASE cola WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
WHERE cola IN (1,2);
SELECT * FROM testing;
だからあなたはから行きます:
cola colb
------------
1 b
2 a
に:
cola colb
------------
1 a
2 b
また、#2が最善の策だと思いますが、更新中に問題が発生した場合に備えて、必ずトランザクションでラップします。
異なる値で一意のインデックス値を更新する代わりの方法は、行内の他のすべての値を他の行の値に更新することです。これを行うと、一意のインデックス値をそのままにしておくことができ、最終的には必要なデータが得られます。ただし、他のテーブルが外部キー関係でこのテーブルを参照している場合は、DB内のすべての関係がそのまま残ることに注意してください。
私も同じ問題を抱えてる。PostgreSQL で提案したアプローチを次に示します。私の場合、一意のインデックスはシーケンス値であり、行の明示的なユーザー順序を定義しています。ユーザーは Web アプリ内で行をシャッフルし、変更を送信します。
「before」トリガーを追加する予定です。そのトリガーでは、一意のインデックス値が更新されるたびに、他の行に新しい値が既に保持されているかどうかを確認します。もしそうなら、私は彼らに私の古い価値を与え、彼らから価値を効果的に盗みます.
PostgreSQL によって、このシャッフルを before トリガーで実行できるようになることを願っています。
折り返し、走行距離をお知らせします。
SQL Server では、MERGE ステートメントは、通常は UNIQUE KEY/INDEX を壊す行を更新できます。(気になったので調べてみました。)
ただし、必要な行でMERGEを提供するには、一時テーブル/変数を使用する必要があります。
Oracle にはオプション DEFERRED がありますが、それを制約に追加する必要があります。
SET CONSTRAINT emp_no_fk_par DEFERRED;
セッション全体で延期可能なすべての制約を延期するには、ALTER SESSION SET Constraint=DEFERRED ステートメントを使用できます。
Oracle はこれを正確に解決する整合性チェックを延期しましたが、SQL Server または MySQL では利用できません。