質問
データベーステーブルがあり、フィールドの1つ(主キーではない)に一意のインデックスがあります。次に、この列の 2 行の値を交換したいと思います。どうすればこんなことができるのでしょうか?私が知っているハックは次の 2 つです。
- 両方の行を削除して、再度挿入します。
- 他の値で行を更新してスワップし、実際の値に更新します。
しかし、これらは問題の適切な解決策ではないと思われるため、私はこれらを使用したくありません。誰か手伝ってくれませんか?
解決
解決策 2 を選択する必要があると思います。私が知っているどの SQL バリアントにも「swap」関数はありません。
これを定期的に行う必要がある場合は、ソフトウェアの他の部分がこのデータをどのように使用しているかに応じて、解決策 1 をお勧めします。注意しないとロックの問題が発生する可能性があります。
しかし、要するに:あなたが提供したもの以外に解決策はありません。
他のヒント
魔法の言葉は 延期可能 ここ:
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で)私のために機能し、一意の制約の一部であるフィールドを交換する必要があります。
鍵:pid、lnum rec1:10、0 rec2:10、1 rec3:10、2
結果が次のようになるように、LNUMを交換する必要があります
鍵: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 アプリ内で行をシャッフルし、変更を送信します。
「before」トリガーを追加する予定です。そのトリガーでは、一意のインデックス値が更新されるたびに、他の行がすでに新しい値を保持しているかどうかを確認します。もしそうなら、私は彼らに私の古い価値を与え、事実上彼らから価値を盗みます。
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
に:
cola colb
------------
1 a
2 b
Oracle の場合は DEFERRED オプションがありますが、それを制約に追加する必要があります。
SET CONSTRAINT emp_no_fk_par DEFERRED;
セッション全体で延期可能な ALL 制約を延期するには、ALTER SESSION SETconstraints=DEFERRED ステートメントを使用できます。
Oracle にはこの問題を正確に解決する遅延整合性チェックがありますが、SQL Server や MySQL では利用できません。
SQL Server では、MERGE ステートメントは、通常なら UNIQUE KEY/INDEX を破壊する行を更新できます。(興味があったので試してみました。)
ただし、必要な行を含む MERGE を提供するには、一時テーブル/変数を使用する必要があります。
私は通常、テーブル内のどのインデックスにも存在し得ない値を考えます。通常、一意の列値の場合は非常に簡単です。たとえば、列「位置」(複数の要素の順序に関する情報) の値は 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
「n行数の場合、どうやって管理しますか......」