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 ステートメントがないため、これを行う最善の方法は何ですか?
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
上記の 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 の場合は挿入を実行するよりも遅くなります。
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;
例外チェックを行わない別の方法:
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;
INSERT INTO mytable (id1, t1) デュアルから 11、「x1」を選択 存在しない場所 (mytble WHERE id1 = 11 から id1 を選択); UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
ティム・シルベスターのコメントで指摘されているように、これまでに与えられた答えはどれも同時アクセスに直面しても安全ではなく、レースの場合は例外が発生します。これを修正するには、例外が発生した場合にすべてが再試行されるように、挿入/更新の組み合わせをある種のループ ステートメントでラップする必要があります。
例として、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例外が発生する可能性があります。
重複値が必要であることを除いて、グロミットの回答が必要です。一度表示される可能性のある解決策を見つけました: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);
最初のコード サンプルを何年も使用してきました。カウントではなく、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 は単一のインデックス ルックアップで正しい挿入または更新を実行できます。
いくつかのテーブルからデータを取得してテーブルを更新し、おそらく行を挿入または削除することを意味する処理が必要な場合は、マージの方が優れていると思います。ただし、単一行の場合は、構文がより一般的であるため、最初のケースを考慮することができます。
提案する2つのソリューションに関するメモ:
1) 挿入、例外の場合は更新、
また
2) 更新、sql%rowcount = 0 の場合は挿入
最初に挿入するか更新するかの問題も、アプリケーションに依存します。より多くの挿入または更新を期待していますか? 成功する可能性が最も高いものを最初に実行する必要があります。
間違ったものを選択すると、不要なインデックス読み取りが大量に発生します。大したことではありませんが、それでも考慮すべきことがあります。
これを試して、
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
)
;
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;