web-dev-qa-db-ja.com

Oracleの:どこのようにUPSERTする(更新またはテーブルに挿入する?)

UPSERT操作は、テーブルにデータと一致する行がすでにあるかどうかに応じて、テーブル内の行を更新または挿入します。

if table t has a row exists that has key X:
    update t set mystuff... where mykey=X
else
    insert into t mystuff...

Oracleには特定のUPSERTステートメントがないので、これを実行するための最良の方法は何ですか?

262
Mark Harrison

MERGEに代わるもの( "昔ながらの方法"):

begin
   insert into t (mykey, mystuff) 
      values ('X', 123);
exception
   when dup_val_on_index then
      update t 
      set    mystuff = 123 
      where  mykey = 'X';
end;   
46
Tony Andrews

MERGEステートメント は、2つのテーブル間でデータをマージします。 DUALを使用すると、このコマンドを使用できます。これは同時アクセスから保護されていないことに注意してください。

create or replace
procedure ups(xa number)
as
begin
    merge into mergetest m using dual on (a = xa)
         when not matched then insert (a,b) values (xa,1)
             when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;

A                      B
---------------------- ----------------------
10                     2
20                     1
198
Mark Harrison

PL/SQLにある上記の二重の例は、私が似たようなことをしたいのですが、クライアント側でも望んでいたのですごく良かったです。

MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name") 
    VALUES ( 2097153,"smith", "john" )

ただし、C#の観点からは、更新を実行して影響を受ける行が0であるかどうかを確認し、挿入されている行を更新するよりも遅くなります。

96
MyDeveloperDay

例外チェックを行わない別の方法

UPDATE tablename
    SET val1 = in_val1,
        val2 = in_val2
    WHERE val3 = in_val3;

IF ( sql%rowcount = 0 )
    THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;
43
Brian Schmitt
  1. 存在しない場合は挿入
  2. 更新:
    
 mytable(id1、t1)への挿入
 SELECT 11、 'x1' from DUAL 
どこにも存在しない(SELECT id1 from mytble WHERE id1 = 11); 
 
 mytable SET t1 = 'x1'を更新します。id1 = 11の場合。
25
test1

Tim Sylvesterのコメントで指摘されているように、これまでに挙げられた答えのどれも、同時アクセスに直面しても安全ではありませんそしてレースの場合には例外を発生させます。これを修正するには、insert/updateコンボをある種のループ文でラップする必要があります。そのため、例外が発生した場合はすべてが再試行されます。

例として、Grommitのコードをループでラップして安全に実行できるようにする方法を次に示します。

PROCEDURE MyProc (
 ...
) IS
BEGIN
 LOOP
  BEGIN
    MERGE INTO Employee USING dual ON ( "id"=2097153 )
      WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
      WHEN NOT MATCHED THEN INSERT ("id","last","name") 
        VALUES ( 2097153,"smith", "john" );
    EXIT; -- success? -> exit loop
  EXCEPTION
    WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
      NULL; -- exception? -> no op, i.e. continue looping
    WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
      NULL; -- exception? -> no op, i.e. continue looping
  END;
 END LOOP;
END; 

N.B.トランザクションモードSERIALIZABLEは推奨されていませんが、 ORA-08177:このトランザクションに対するアクセスをシリアル化できません 例外が代わりに発生します。

22

私はGrommitの答えが欲しいのですが、それには二重の価値が必要です。私はそれが一度現れるかもしれない解決策を見つけました: http://forums.devshed.com/showpost.php?p=1182653&postcount=2

MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
    SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
    FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
    UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
    INSERT (  CILT,   SAYFA,   KUTUK,   MERNIS_NO)
    VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); 
19
Hubbitus

提案する2つの解決策に関するメモ

1)挿入し、例外があれば更新し、

または

2)更新、sql%rowcount = 0であれば挿入

最初に挿入するのか更新するのかという問題もアプリケーションに依存します。あなたはもっと多くの挿入やもっと多くの更新を期待していますか?最も成功する可能性が最も高いものが最初に行きます。

あなたが間違ったものを選ぶと、あなたは不必要なインデックス読み込みの束を得るでしょう。大したことではありませんが、まだ考慮すべき点があります。

8
AnthonyVO

私は最初のコードサンプルを何年も使ってきました。カウントよりも見つからないことに注意してください。

UPDATE tablename SET val1 = in_val1, val2 = in_val2
    WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
    INSERT INTO tablename
        VALUES (in_val1, in_val2, in_val3);
END IF;

以下のコードは、おそらく新しく改良されたコードです。

MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT 
    VALUES (in_val1, in_val2, in_val3)

最初の例では、更新はインデックスルックアップを行います。正しい行を更新するには、それが必要です。 Oracleは暗黙のカーソルをオープンし、それを使用して対応する挿入をラップします。そのため、挿入はキーが存在しない場合にのみ行われることがわかります。しかし、挿入は独立したコマンドであり、2回目の検索をする必要があります。 mergeコマンドの内部の動作はわかりませんが、このコマンドは単一のユニットなので、Oracleは単一の索引検索で正しい挿入または更新を実行できた可能性があります。

何らかの処理が必要な場合、マージが優れていると思います。つまり、いくつかのテーブルからデータを取得してテーブルを更新し、行を挿入または削除します。しかし、単一行の場合は、構文がより一般的であるため、最初のケースを検討することができます。

7

MERGEを使用して、あるテーブルを別のテーブルに更新挿入する例をコピーして貼り付けます。

CREATE GLOBAL TEMPORARY TABLE t1
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5)
     )
  ON COMMIT DELETE ROWS;

CREATE GLOBAL TEMPORARY TABLE t2
    (id VARCHAR2(5) ,
     value VARCHAR2(5),
     value2 VARCHAR2(5))
  ON COMMIT DELETE ROWS;
ALTER TABLE t2 ADD CONSTRAINT PK_LKP_MIGRATION_INFO PRIMARY KEY (id);

insert into t1 values ('a','1','1');
insert into t1 values ('b','4','5');
insert into t2 values ('b','2','2');
insert into t2 values ('c','3','3');


merge into t2
using t1
on (t1.id = t2.id) 
when matched then 
  update set t2.value = t1.value,
  t2.value2 = t1.value2
when not matched then
  insert (t2.id, t2.value, t2.value2)  
  values(t1.id, t1.value, t1.value2);

select * from t2

結果:

  1. b 4 5
  2. c 3 3
  3. 1 1
0
Bechyňák Petr