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语句 合并两个表之间的数据.使用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,如果是,则执行插入操作.
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) SELECT 11, 'x1' FROM DUAL WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11); UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
如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;
NB在交易模式中SERIALIZABLE
,我不推荐使用顺便说一下,您可能会遇到
ORA-08177:无法序列化此事务异常的访问权限.
我想要Grommit回答,除非它需要重复值.我找到了可能出现一次的解决方案:http://forums.devshed.com/showpost.php?p = 1182653&posttcount = 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);
关于两种解决方案的说明:
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打开一个隐式游标,我们用它来包装一个相应的插入,这样我们就知道插入只会在键不存在时发生.但插入是一个独立的命令,它必须进行第二次查找.我不知道merge命令的内部工作原理,但由于命令是一个单元,Oracle可以使用单个索引查找执行正确的插入或更新.
我认为合并更好,当你有一些处理要做,这意味着从一些表中获取数据并更新表,可能插入或删除行.但对于单行情况,您可能会考虑第一种情况,因为语法更常见.