답변:
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)의 의견에서 지적한 것처럼 지금까지의 답변 중 어느 것도 동시 액세스에 대해 안전 하지 않으며 레이스의 경우 예외를 제기하지 않습니다. 이를 수정하려면 삽입 / 업데이트 콤보를 일종의 루프 명령문으로 랩핑해야 예외의 경우 전체가 재 시도됩니다.
예를 들어, 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&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은 암시 적 커서를 열고이를 사용하여 해당 삽입을 랩핑하여 키가 존재하지 않을 때만 삽입이 발생한다는 것을 알 수 있습니다. 그러나 삽입은 독립적 인 명령이며 두 번째 조회를 수행해야합니다. 병합 명령의 내부 작동을 모르지만 명령이 단일 단위이므로 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;