318

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 ステートメントがないため、これを行う最善の方法は何ですか?

4

11 に答える 11

222

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
于 2008-10-26T01:24:17.597 に答える
117

上記の PL/SQL での二重の例は、似たようなことをしたかったので素晴らしかったですが、クライアント側が欲しかったので、C# から同様のステートメントを直接送信するために使用した 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 であるかどうかを確認し、0 の場合は挿入を実行するよりも遅くなります。

于 2010-04-22T16:00:39.017 に答える
68

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;   
于 2008-10-27T11:12:13.790 に答える
52

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

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;
于 2008-10-28T14:37:32.563 に答える
33
  1. 存在しない場合は挿入
  2. アップデート:
    
INSERT INTO mytable (id1, t1)
  デュアルから 11、「x1」を選択
  存在しない場所 (mytble WHERE id1 = 11 から id1 を選択);

UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
于 2014-01-23T14:02:31.263 に答える
27

ティム・シルベスターのコメントで指摘されているように、これまでに与えられた答えはどれも同時アクセスに直面しても安全ではなく、レースの場合は例外が発生します。これを修正するには、例外が発生した場合にすべてが再試行されるように、挿入/更新の組み合わせをある種のループ ステートメントでラップする必要があります。

例として、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; 

注意トランザクション モードSERIALIZABLEでは、お勧めしませんが、代わりに ORA-08177: can't serialize access for this transaction例外が発生する可能性があります。

于 2014-04-01T05:45:57.957 に答える
25

重複値が必要であることを除いて、グロミットの回答が必要です。一度表示される可能性のある解決策を見つけました: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); 
于 2012-12-25T15:36:00.977 に答える
10

最初のコード サンプルを何年も使用してきました。カウントではなく、notfound に注意してください。

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 回目の検索を行う必要があります。マージ コマンドの内部動作はわかりませんが、コマンドは単一のユニットであるため、Oracle は単一のインデックス ルックアップで正しい挿入または更新を実行できます。

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

于 2015-01-12T16:48:00.030 に答える
9

提案する2つのソリューションに関するメモ:

1) 挿入、例外の場合は更新、

また

2) 更新、sql%rowcount = 0 の場合は挿入

最初に挿入するか更新するかの問題も、アプリケーションに依存します。より多くの挿入または更新を期待していますか? 成功する可能性が最も高いものを最初に実行する必要があります。

間違ったものを選択すると、不要なインデックス読み取りが大量に発生します。大したことではありませんが、それでも考慮すべきことがあります。

于 2011-11-25T23:19:47.770 に答える
-3

これを試して、

insert into b_building_property (
  select
    'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
  from dual
)
minus
(
  select * from b_building_property where id = 9
)
;
于 2011-03-15T03:34:15.540 に答える
-7

http://www.praetoriate.com/oracle_tips_upserts.htmから:

「Oracle9i では、UPSERT は単一のステートメントでこのタスクを実行できます。」

INSERT
FIRST WHEN
   credit_limit >=100000
THEN INTO
   rich_customers
VALUES(cust_id,cust_credit_limit)
   INTO customers
ELSE
   INTO customers SELECT * FROM new_customers;
于 2010-02-10T01:24:03.153 に答える