SQL Server에 업데이트 저장 프로 시저 삽입


104

레코드가 있으면 업데이트를 수행하는 저장된 proc을 작성했습니다. 그렇지 않으면 삽입을 수행합니다. 다음과 같이 보입니다.

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

이런 식으로 작성하는 논리는 업데이트가 where 절을 사용하여 암시 적 선택을 수행하고 0을 반환하면 삽입이 발생한다는 것입니다.

이 방법의 대안은 선택을 수행 한 다음 반환 된 행 수에 따라 업데이트 또는 삽입을 수행하는 것입니다. 업데이트를 수행하면 두 번의 선택이 발생하기 때문에 비효율적이라고 생각했습니다 (첫 번째 명시 적 선택 호출과 업데이트 위치에 두 번째 암시 적). 프로세서가 삽입을한다면 효율성에 차이가 없을 것입니다.

내 논리가 여기에 있습니까? 이것이 저장된 proc에 삽입 및 업데이트를 결합하는 방법입니까?

답변:


61

귀하의 가정이 맞습니다. 이것이 최선의 방법이며 upsert / merge 라고 합니다.

UPSERT의 중요성-sqlservercentral.com에서 :

위에서 언급 한 경우의 모든 업데이트에 대해 EXISTS 대신 UPSERT를 사용하면 테이블에서 하나의 추가 읽기를 제거합니다. 안타깝게도 Insert의 경우 UPSERT 및 IF EXISTS 메서드는 모두 테이블에서 동일한 수의 읽기를 사용합니다. 따라서 존재 확인은 추가 I / O를 정당화 할 매우 타당한 이유가있을 때만 수행되어야합니다. 작업을 수행하는 최적화 된 방법은 DB에서 가능한 한 거의 읽지 않도록하는 것입니다.

가장 좋은 전략은 업데이트를 시도하는 것입니다. 업데이트의 영향을받는 행이 없으면 삽입하십시오. 대부분의 경우 행은 이미 존재하며 하나의 I / O 만 필요합니다.

편집 : 이 패턴의 문제와 안전하게 작동하는 방법에 대해 알아 보려면 이 답변 과 링크 된 블로그 게시물을 확인하십시오.


1
글쎄요, 적어도 하나의 질문에 답한 것 같습니다. 그리고 질문의 코드가 이미 나에게 맞는 것처럼 보였기 때문에 코드를 추가하지 않았습니다. 트랜잭션에 넣을 것이지만 업데이트에 대해 격리 수준을 고려하지 않았습니다. 답변에 지적 해 주셔서 감사합니다!
binOr

54

사용할 수있는 좋고 안전한 패턴에 대해서는 내 블로그게시물을 읽으십시오 . 많은 고려 사항이 있으며이 질문에 대한 대답은 안전하지 않습니다.

빠른 답변을 위해 다음 패턴을 시도하십시오. SQL 2000 이상에서 잘 작동합니다. SQL 2005는 다른 옵션을 여는 오류 처리를 제공하고 SQL 2008은 MERGE 명령을 제공합니다.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

1
블로그 게시물에서 존재 확인에 WITH (updlock, serializable) 힌트를 사용하여 결론을 내립니다. 그러나 MSDN 읽기 상태는 "UPDLOCK-트랜잭션이 완료 될 때까지 업데이트 잠금을 수행하고 보유하도록 지정합니다." 이것은 어쨌든 나머지 트랜잭션에 대해 업데이트 잠금이 유지되므로 직렬화 가능한 힌트가 불필요하다는 것을 의미합니까, 아니면 내가 뭔가를 오해 했습니까?
Dan Def

10

SQL Server 2000/2005와 함께 사용하려면 데이터가 동시 시나리오에서 일관되게 유지되도록 원본 코드를 트랜잭션에 포함시켜야합니다.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

이로 인해 추가 성능 비용이 발생하지만 데이터 무결성이 보장됩니다.

이미 제안한대로 가능한 경우 MERGE를 사용해야합니다.



6

트랜잭션에서 실행해야 할뿐만 아니라 높은 격리 수준도 필요합니다. 사실 기본 격리 수준은 Read Commited이며이 코드에는 Serializable이 필요합니다.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

@@ error 검사 및 롤백을 추가하는 것도 좋은 생각 일 수 있습니다.


@Munish Goyal 데이터베이스에서는 여러 명령과 절차가 병렬로 실행되기 때문입니다. 그런 다음 다른 스레드는 업데이트가 실행 된 직후와 삽입이 실행되기 전에 행을 삽입 할 수 있습니다.
Tomas Tintera

5

SQL 2008에서 병합을 수행하지 않는 경우 다음으로 변경해야합니다.

@@ rowcount = 0이고 @@ error = 0 인 경우

그렇지 않으면 어떤 이유로 업데이트가 실패하면 실패한 명령문의 행 개수가 0이기 때문에 나중에 삽입을 시도합니다.


3

UPSERT의 열렬한 팬은 관리 할 코드를 정말로 줄여줍니다. 다른 방법은 다음과 같습니다. 입력 매개 변수 중 하나는 ID이고, ID가 NULL 또는 0이면 INSERT이고 그렇지 않으면 업데이트입니다. 응용 프로그램이 ID가 있는지 알고 있다고 가정하므로 모든 상황에서 작동하지는 않지만 그렇게 할 경우 실행을 절반으로 줄입니다.


2

수정 된 Dima Malenko 게시물 :

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

오류를 트랩하고 레코드를 실패한 삽입 테이블로 보낼 수 있습니다.
WSDL을 통해 전송되는 데이터를 취하고 가능한 경우 내부적으로 수정하기 때문에이 작업을 수행해야했습니다.


1

귀하의 논리는 소리가 나지만 특정 기본 키를 전달한 경우 삽입을 방지하기 위해 코드를 추가하는 것을 고려할 수 있습니다.

그렇지 않고 업데이트가 레코드에 영향을주지 않는 경우 항상 삽입을 수행하는 경우 "UPSERT"가 실행되기 전에 누군가 레코드를 삭제하면 어떻게됩니까? 이제 업데이트하려는 레코드가 존재하지 않으므로 대신 레코드가 생성됩니다. 그것은 아마도 당신이 찾던 행동이 아닐 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.