回答:
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;
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での2つの例は、私が同様のことをしたかったのですばらしいですが、クライアント側にしたかったので、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であるかどうかを確認し、そうである場合は挿入を行うよりも時間がかかります。
MERGE
、と私ははるかに簡単な使用して好むDELETE
、その後INSERT
。
MERGE INTO mytable d USING (SELECT 1 id, 'x' name from dual) s ON (d.id = s.id) WHEN MATCHED THEN UPDATE SET d.name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);
例外チェックなしの別の選択肢:
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;
Tim Sylvesterのコメントで指摘されているように、これまでに出された答えはいずれも同時アクセスに直面しても安全ではなく、レースの場合は例外が発生します。これを修正するには、挿入/更新コンボをある種のループステートメントでラップする必要があります。これにより、例外が発生した場合は、すべてが再試行されます。
例として、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が発生する可能性が
あります。代わりに、このトランザクション例外のアクセスをシリアル化できません。
だまされた値が必要な場合を除いて、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);
INSERT (B.CILT, B.SAYFA, B.KUTUK, B.MERNIS_NO) VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
?
提案する2つのソリューションに関するメモ:
1)挿入、例外の場合は更新、
または
2)更新、sql%rowcount = 0の場合、挿入
最初に挿入するか更新するかという問題もアプリケーションに依存します。より多くの挿入またはより多くの更新を期待していますか?成功する可能性が最も高いものを最初に実行する必要があります。
間違ったものを選択すると、不必要なインデックスの読み取りが大量に発生します。大したことではありませんが、それでも考慮すべき点があります。
私は何年もの間、最初のコードサンプルを使用しています。数えるのではなく、見つけられないことに注意してください。
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は単一のインデックスルックアップで正しい挿入または更新を実行できた可能性があります。
いくつかのテーブルからデータを取得してテーブルを更新し、場合によっては行を挿入または削除するという処理が必要な場合は、マージの方が良いと思います。ただし、単一行の場合は、構文がより一般的であるため、最初のケースを検討できます。
MERGEを使用して、1つのテーブルを別のテーブルにアップサートする例をコピーして貼り付けます。
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
結果:
http://www.praetoriate.com/oracle_tips_upserts.htmから:
「Oracle9iでは、UPSERTは1つのステートメントでこのタスクを実行できます。」
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;