当前位置:  开发笔记 > 编程语言 > 正文

Oracle:如何UPSERT(更新或插入表?)

如何解决《Oracle:如何UPSERT(更新或插入表?)》经验,为你挑选了9个好方法。

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语句,最好的方法是什么?



1> Mark Harriso..:

在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


显然,"merge into"语句不是原子的.当同时使用时,它可能导致"ORA-0001:唯一约束".检查是否存在匹配和插入新记录不受锁保护,因此存在竞争条件.要可靠地执行此操作,您需要捕获此异常并重新运行合并或执行简单更新.在Oracle 10中,您可以使用"log errors"子句使其在发生错误时继续执行其余行,并将违规行记录到另一个表中,而不是仅停止.
@TimSylvester - Oracle使用事务,因此保证事务开始时数据的快照在整个事务中保持一致,保存其中的任何更改.对数据库的并发调用使用撤消堆栈; 因此,Oracle将根据并发事务的启动/完成时间顺序管理最终状态.因此,如果在插入之前完成约束检查,无论对同一SQL代码进行了多少并发调用,您将永远不会遇到竞争条件.最糟糕的情况是,您可能会遇到很多争用,而Oracle将需要更长的时间才能达到最终状态.
@Shekhar Dual是一个带有单行和列的虚拟表http://www.adp-gmbh.ch/ora/misc/dual.html
@RandyMagruder它是2015年的情况,我们仍然无法在Oracle中可靠地进行升级!您知道并发安全解决方案吗?

2> MyDeveloperD..:

上面的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,如果是,则执行插入操作.


我回到这里再次查看这种模式.尝试并发插入时,它会以静默方式失败.一个插入生效,第二个合并既不插入也不更新.但是,执行两个单独语句的更快方法是安全的.
太糟糕了,使用这种模式我们需要********两次**(John,Smith ......).在这种情况下,我使用`MERGE`赢得任何东西,我更喜欢使用更简单的`DELETE`然后`INSERT`.
像我这样的口头新手可能会问这* dual *表是什么?请参阅:http://stackoverflow.com/q/73751/808698

3> Tony Andrews..:

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;   


问题是您在插入和更新之间有一个窗口,其中另一个进程可以成功触发删除.但是,我确实在一个永远不会对其进行删除的表上使用此模式.
这仅适用于您从未从相关表中删除的情况.
@chotchki:真的吗?解释会有所帮助.
我不同意Chotchki."锁定持续时间:事务中语句获取的所有锁定都在事务持续期间保留,防止破坏性干扰,包括脏读,丢失更新以及并发事务的破坏性DDL操作." Souce:[link](http://docs.oracle.com/cd/B19306_01/server.102/b14220/consist.htm#i5704)
@yohannc:我认为关键是我们没有通过尝试和未能插入行来获得任何锁定.

4> Brian Schmit..:

没有例外检查的另一种选择:

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;


@Adriano:如果WHERE子句匹配任何行,sql%rowcount仍将返回> 0,即使更新实际上没有更改这些行上的任何数据.
@Tony安德鲁斯 - 这个解决方案线程安全吗?

5> 小智..:

    如果不存在则插入

    更新:

    
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;



6> Eugene Beres..:

如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:无法序列化此事务异常的访问权限.


优秀!最后,并发访问安全答案.从客户端(例如从Java客户端)使用这种构造的任何方式?

7> Hubbitus..:

我想要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); 


你的意思是`INSERT(B.CILT,B.SAYFA,B.KUTUK,B.MERNIS_NO)VALUES(E.CILT,E.SAYFA,E.KUTUK,E.MERNIS_NO); `?

8> AnthonyVO..:

关于两种解决方案的说明:

1)插入,如果异常则更新,

要么

2)更新,如果sql%rowcount = 0则插入

是否首先插入或更新的问题也取决于应用程序.您是否期望更多插入或更多更新?最有可能成功的那个应该先行.

如果你选错了,你将获得一堆不必要的索引读取.这不是一件大事,但仍有待考虑.



9> Arturo Herna..:

我多年来一直在使用第一个代码示例.注意不要发现而不是计数.

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可以使用单个索引查找执行正确的插入或更新.

我认为合并更好,当你有一些处理要做,这意味着从一些表中获取数据并更新表,可能插入或删除行.但对于单行情况,您可能会考虑第一种情况,因为语法更常见.

推荐阅读
php
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有