오라클 : UPSERT 방법 (테이블에 업데이트 또는 삽입?)


293

UPSERT 조작은 테이블에 이미 데이터와 일치하는 행이 있는지에 따라 테이블에 행을 갱신하거나 삽입합니다.

if table t has a row exists that has key X:
    update t set mystuff... where mykey=X
else
    insert into t mystuff...

오라클은 특정 UPSERT 문을 가지고 있지 않으므로이를 수행하는 가장 좋은 방법은 무엇입니까?

답변:


60

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;   

3
@ chotchki : 정말? 설명이 도움이 될 것입니다.
Tony Andrews

15
문제는 다른 프로세스가 성공적으로 삭제를 실행할 수있는 삽입과 업데이트 사이에 창이 있다는 것입니다. 그러나 삭제되지 않은 테이블 에서이 패턴을 사용했습니다.
chotchki

2
그래 나 동의 해. 왜 나에게 분명하지 않은지 모르겠다.
Tony Andrews

4
나는 Chotchki에 동의하지 않는다. "잠금 기간 : 트랜잭션 내 명령문에 의해 획득 된 모든 잠금은 트랜잭션 기간 동안 유지되므로 더티 읽기, 업데이트 손실 및 동시 트랜잭션의 파괴적인 DDL 작업을 포함하여 파괴적인 간섭을 방지합니다." Souce : 링크
yohannc

5
@ yohannc : 요점은 행 삽입을 시도하지 않고 잠금을 얻지 못했다는 것입니다.
Tony Andrews

211

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

57
분명히 "병합"문은 원자 적이 지 않습니다. 동시에 사용하면 "ORA-0001 : unique constraint"이 발생할 수 있습니다. 일치 여부 확인 및 새 레코드 삽입은 잠금으로 보호되지 않으므로 경쟁 조건이 있습니다. 이를 안정적으로 수행하려면이 예외를 포착하고 병합을 다시 실행하거나 간단한 업데이트를 수행해야합니다. Oracle 10에서는 "log errors"절을 사용하여 오류가 발생할 때 나머지 행을 계속하고 문제가되는 행을 중지하지 않고 다른 테이블에 기록 할 수 있습니다.
팀 실베스터

1
안녕하세요, 나는 쿼리에서 동일한 쿼리 패턴을 사용하려고 시도했지만 어떻게 든 쿼리가 중복 행을 삽입하고 있습니다. DUAL 테이블에 대한 자세한 정보를 찾을 수 없습니다. 누구든지 DUAL 및 병합 구문에 대한 정보를 어디서 얻을 수 있습니까?
Shekhar

5
@Shekhar Dual은 단일 행과 열이있는 더미 테이블입니다. adp-gmbh.ch/ora/misc/dual.html
YogoZuno

7
@TimSylvester-Oracle은 트랜잭션을 사용하므로 트랜잭션 시작시 데이터의 스냅 샷이 트랜잭션 전체에서 일관성을 유지하여 트랜잭션 내에서 변경 한 내용을 저장합니다. 데이터베이스에 대한 동시 호출은 실행 취소 스택을 사용합니다. 따라서 Oracle은 동시 트랜잭션이 시작 / 완료된 시점의 순서에 따라 최종 상태를 관리합니다. 따라서 동일한 SQL 코드에 대한 동시 호출 수에 관계없이 삽입 전에 제약 조건 검사를 수행하면 경쟁 조건이 발생하지 않습니다. 최악의 경우, 많은 경합이 발생할 수 있으며 Oracle은 최종 상태에 도달하는 데 훨씬 더 오래 걸립니다.
Neo

2
@RandyMagruder 2015 년인데도 오라클에서 안정적으로 upsert를 수행 할 수 없습니다! 동시 안전 솔루션에 대해 알고 있습니까?
dan b

105

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인지 확인하고 삽입 된 경우 삽입하는 것보다 느립니다.


10
이 패턴을 다시 확인하기 위해 여기로 돌아 왔습니다. 동시 삽입을 시도하면 자동으로 실패합니다. 하나의 삽입이 적용되고, 두 번째 병합은 삽입 또는 업데이트되지 않습니다. 그러나 두 가지 별도의 진술을하는 더 빠른 방법은 안전합니다.
Synesso

3
나 같은 oralcle 초보자이 무엇인지 요청할 수 있습니다 이중 : 테이블이 참조 stackoverflow.com/q/73751/808698
인 헤이 THELEN

5
이 패턴으로 데이터를 두 번 써야 한다는 것이 너무 나쁩니다 (John, Smith ...). 이 경우, 나는 아무것도 사용을 이길 수 없다 , 나는 훨씬 더 간단 사용하여 선호 다음 . MERGEDELETEINSERT
니콜라스 Barbulesco

이 대답은 두 번 데이터를 기록 할 필요가 없다 @NicolasBarbulesco stackoverflow.com/a/4015315/8307814을
whyer

@NicolasBarbulescoMERGE 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);

46

예외 점검이없는 다른 대안 :

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;

제공 한 솔루션이 저에게 효과적이지 않습니다. % rowcount는 명시 적 커서에서만 작동합니까?
Synesso

레코드가 이미 있고 값이 같기 때문에 업데이트에서 수정 된 0 개의 행을 반환하면 어떻게됩니까?
Adriano Varoli Piazza

10
@Adriano : WHERE 절이 행과 일치하는 경우에도 sql % rowcount는> 0을 반환하지만 업데이트에서 해당 행의 데이터를 실제로 변경하지 않아도 마찬가지입니다.
Tony Andrews

작동하지 않음 : PLS-00207 : 암시 적 커서 SQL에 적용된 식별자 'COUNT'는 유효한 커서 속성이 아닙니다.
Patrik Beck

구문 오류는 여기에 :(
ilmirons

27
  1. 존재하지 않는 경우 삽입
  2. 최신 정보:
    
mytable에 삽입 (id1, t1) 
  11에서 'x1'을 선택하십시오. 
  존재하지 않는 곳 (mytble에서 id1 = 11 선택); 

업데이트 mytable SET t1 = 'x1'어디서 id1 = 11;

26

팀 실베스터 (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이 발생할 수 있습니다 . 대신 이 트랜잭션 예외에 대한 액세스를 직렬화 할 수 없습니다 .


3
우수한! 마지막으로, 동시에 안전한 답변에 액세스합니다. 클라이언트 (예 : Java 클라이언트)에서 이러한 구문을 사용하는 방법은 무엇입니까?
Sebien

1
저장된 proc을 호출 할 필요가 없다는 뜻입니까? 이 경우 특정 Java 예외를 포착하고 Java 루프에서 다시 시도 할 수도 있습니다. Java의 경우 Oracle의 SQL보다 훨씬 편리합니다.
Eugene Beresovsky

죄송합니다. 구체적이지 않았습니다. 그러나 당신은 올바른 길을 이해했습니다. 네가 방금 말한 것처럼 사직했다. 그러나 더 많은 SQL 쿼리, 더 많은 클라이언트 / 서버 왕복을 생성하기 때문에 100 % 만족스럽지 않습니다. 성능 측면에서 좋은 솔루션은 아닙니다. 그러나 내 목표는 내 프로젝트의 Java 개발자가 내 메소드를 사용하여 모든 테이블에서 upsert하도록하는 것입니다 (테이블 당 하나의 PLSQL 저장 프로 시저 또는 upsert 유형 당 하나의 프로 시저를 만들 수 없음).
Sebien

@Sebien 동의합니다. SQL 영역에 캡슐화하는 것이 더 좋으며 할 수 있다고 생각합니다. 나는 당신을 위해 그것을 알아 내기 위해 자원하지 않습니다 ... :) 게다가 실제로 이러한 예외는 아마도 푸른 달에 한 번도 발생할 것이므로 99.9 %의 사례에서 성능에 영향을 미치지 않아야합니다. 물론로드 테스트를 할 때를 제외하고 ...
Eugene Beresovsky

24

중복 값이 ​​필요한 것을 제외하고 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); 

2
당신은 의미 했습니까 INSERT (B.CILT, B.SAYFA, B.KUTUK, B.MERNIS_NO) VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO); ?
Matteo

확실한. 감사. 결정된.
Hubbitus

고맙게도 답변을 편집했습니다! :) 내 편집은 슬프게도 거부되었습니다. stackoverflow.com/review/suggested-edits/7555674
Matteo

9

제안하는 두 가지 솔루션에 대한 참고 사항 :

1) 예외가 있으면 업데이트를 삽입하고

또는

2) 업데이트, sql % rowcount = 0이면 삽입

먼저 삽입 또는 업데이트 여부에 대한 문제는 응용 프로그램에 따라 다릅니다. 더 많은 인서트 또는 더 많은 업데이트가 필요하십니까? 성공할 가능성이 가장 높은 것이 먼저 가야합니다.

잘못된 것을 선택하면 불필요한 인덱스 읽기가 많이 발생합니다. 큰 문제는 아니지만 여전히 고려해야 할 사항입니다.


SQL % NOTFOUND 내 개인적인 취향이다
아르투로 에르난데스

8

몇 년 동안 첫 번째 코드 샘플을 사용해 왔습니다. 카운트가 아니라 주목하십시오.

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은 암시 적 커서를 열고이를 사용하여 해당 삽입을 랩핑하여 키가 존재하지 않을 때만 삽입이 발생한다는 것을 알 수 있습니다. 그러나 삽입은 독립적 인 명령이며 두 번째 조회를 수행해야합니다. 병합 명령의 내부 작동을 모르지만 명령이 단일 단위이므로 Oracle은 단일 색인 조회로 올바른 삽입 또는 업데이트를 실행할 수 있습니다.

일부 테이블에서 데이터를 가져 와서 테이블을 업데이트하여 행을 삽입하거나 삭제할 수있는 처리가 필요할 때 병합이 더 낫습니다. 그러나 단일 행의 경우 구문이 더 일반적이기 때문에 첫 번째 경우를 고려할 수 있습니다.


0

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

결과:

  1. b 4 5
  2. c 3 3
  3. 1 1

-3

이 시도,

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
)
;

-6

에서 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;

14
-1 일반적인 Don Burleson cr @ p 두렵습니다. 이것은 하나의 테이블 또는 다른 테이블에 삽입되는 것입니다. 여기에는 "upsert"가 없습니다!
Tony Andrews
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.