データベース内の一意のインデックス付き列の値を交換する

StackOverflow https://stackoverflow.com/questions/644

  •  08-06-2019
  •  | 
  •  

質問

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

  1. 両方の行を削除して、再度挿入します。
  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行数の場合、どうやって管理しますか......」

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top