Answers:
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语句 合并两个表之间的数据。使用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
,我更喜欢使用,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)的评论所指出的那样,面对并发访问,到目前为止给出的答案都不是安全的,并且在比赛中会引发例外情况。为了解决这个问题,必须将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;
注意:在事务模式下SERIALIZABLE
(我不推荐使用btw),您可能会遇到
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);
吗
关于以下两种解决方案的说明:
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打开了一个隐式游标,并且我们使用它来包装相应的插入,因此我们知道该插入仅在键不存在时才会发生。但是insert是一个独立的命令,它必须进行第二次查找。我不知道merge命令的内部工作原理,但是由于该命令是单个单元,因此Oracle可以通过单个索引查找执行正确的插入或更新。
我认为当您要做一些处理时,合并会更好,这意味着从某些表中获取数据并更新表,可能会插入或删除行。但是对于单行情况,您可以考虑第一种情况,因为语法更常见。
使用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
结果:
来自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;