その他のヒント
ここで魔法の言葉は 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)
アンディアーヴィングの答えへさらに
これは同じような状況で(SQL Server 2005で)私のために働いた 複合キーがあり、一意制約の一部であるフィールドを交換する必要があります。
key:pID、LNUM rec1:10、0 rec2:10、1 rec3:10、2
そして結果がなるようにLNUMを交換する必要があります
key: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))
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)
この手法を提供してくれたRich Hに感謝します。 -マーク
#2が最善策だと思いますが、アップデート中に何かがうまくいかない場合に備えて、トランザクションでラップするのは確実でしょう。
一意のインデックス値を異なる値で更新する代わりに(要求されたため)、行内の他のすべての値を他の行の値に更新します。これを行うと、一意のインデックスの値をそのままにしておくことができ、最終的には必要なデータが得られます。ただし、他のテーブルが外部キー関係でこのテーブルを参照する場合、DB内のすべての関係がそのまま残ることに注意してください。
同じ問題があります。 PostgreSQLで提案されているアプローチを次に示します。私の場合、一意のインデックスはシーケンス値であり、行の明示的なユーザーオーダーを定義します。ユーザーはWebアプリで行をシャッフルし、変更を送信します。
「前」を追加する予定です。引き金。そのトリガーでは、一意のインデックス値が更新されるたびに、他の行が既に新しい値を保持しているかどうかを確認します。もしそうなら、私は彼らに私の古い価値を与え、それらから価値を効果的に奪います。
PostgreSQLがbeforeトリガーでこのシャッフルを許可することを望んでいます。
投稿して、マイレージをお知らせします。
更新する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
to:
cola colb
------------
1 a
2 b
OracleにはDEFERREDオプションがありますが、制約に追加する必要があります。
SET CONSTRAINT emp_no_fk_par DEFERRED;
セッション全体で遅延可能なすべての制約を延期するには、ALTER SESSION SET constraint = DEFERREDステートメントを使用できます。
Oracleは完全にこれを解決する整合性チェックを延期しましたが、SQL ServerまたはMySQLのいずれでも使用できません。
SQL Serverでは、MERGEステートメントは通常、一意のキー/インデックスを壊す行を更新できます。 (好奇心が強いので、これをテストしただけです。)
ただし、必要な行を含むMERGEを提供するには、一時テーブル/変数を使用する必要があります。
通常、テーブル内のインデックスがまったく持つことのできない値を考えます。通常-一意の列値の場合-それは本当に簡単です。たとえば、列 'position'(複数の要素の順序に関する情報)の値は0です。
次に、値Aを変数にコピーし、値Bで更新してから、変数から値Bを設定できます。 2つのクエリがありますが、より良い解決策はありません。
1)名前のIDを切り替える
id student
1 Abbot
2 Doris
3 Emerson
4 Green
5 Jeames
サンプル入力の場合、出力は次のとおりです。
idの学生
1 Doris
2 Abbot
3 Green
4 Emerson
5 Jeames
&quot; n行の場合の管理方法......&quot;