68

データベーステーブルがあり、フィールドの1つ(主キーではない)に一意のインデックスがあります。次に、この列の下の値を2行に交換します。これはどのように行うことができますか?私が知っている2つのハックは次のとおりです。

  1. 両方の行を削除して、それらを再挿入します。
  2. 他の値で行を更新し、スワップしてから実際の値に更新します。

しかし、これらは問題の適切な解決策ではないように思われるので、私はこれらに行きたくありません。誰か助けてもらえますか?

4

12 に答える 12

37

魔法の言葉は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)
于 2012-09-15T12:38:09.643 に答える
15

私はあなたが解決策2に行くべきだと思います。私が知っているSQLバリアントには「スワップ」関数はありません。

これを定期的に行う必要がある場合は、ソフトウェアの他の部分がこのデータをどのように使用しているかに応じて、ソリューション1をお勧めします。注意しないと、ロックの問題が発生する可能性があります。

しかし、要するに、あなたが提供したもの以外の解決策はありません。

于 2008-08-03T12:26:35.843 に答える
6

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に感謝します。- マーク

于 2012-04-04T19:40:12.450 に答える
6

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))
于 2008-12-10T12:19:55.950 に答える
4

更新する 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
于 2008-09-16T14:57:15.247 に答える
3

また、#2が最善の策だと思いますが、更新中に問題が発生した場合に備えて、必ずトランザクションでラップします。

異なる値で一意のインデックス値を更新する代わりの方法は、行内の他のすべての値を他の行の値に更新することです。これを行うと、一意のインデックス値をそのままにしておくことができ、最終的には必要なデータが得られます。ただし、他のテーブルが外部キー関係でこのテーブルを参照している場合は、DB内のすべての関係がそのまま残ることに注意してください。

于 2008-08-03T13:22:25.340 に答える
3

私も同じ問題を抱えてる。PostgreSQL で提案したアプローチを次に示します。私の場合、一意のインデックスはシーケンス値であり、行の明示的なユーザー順序を定義しています。ユーザーは Web アプリ内で行をシャッフルし、変更を送信します。

「before」トリガーを追加する予定です。そのトリガーでは、一意のインデックス値が更新されるたびに、他の行に新しい値が既に保持されているかどうかを確認します。もしそうなら、私は彼らに私の古い価値を与え、彼らから価値を効果的に盗みます.

PostgreSQL によって、このシャッフルを before トリガーで実行できるようになることを願っています。

折り返し、走行距離をお知らせします。

于 2009-06-24T03:58:34.487 に答える
2

SQL Server では、MERGE ステートメントは、通常は UNIQUE KEY/INDEX を壊す行を更新できます。(気になったので調べてみました。)

ただし、必要な行でMERGEを提供するには、一時テーブル/変数を使用する必要があります。

于 2012-04-20T19:12:34.247 に答える
2

Oracle にはオプション DEFERRED がありますが、それを制約に追加する必要があります。

SET CONSTRAINT emp_no_fk_par DEFERRED; 

セッション全体で延期可能なすべての制約を延期するには、ALTER SESSION SET Constraint=DEFERRED ステートメントを使用できます。

ソース

于 2016-04-27T14:15:25.287 に答える
1

Oracle はこれを正確に解決する整合性チェックを延期しましたが、SQL Server または MySQL では利用できません。

于 2010-03-19T19:29:59.727 に答える