it-swarm-ko.tech

INSERT 솔루션 OR SQL Server에 대한 업데이트

MyTable(KEY, datafield1, datafield2...)의 테이블 구조를 가정하십시오.

종종 기존 레코드를 업데이트하거나 존재하지 않는 경우 새 레코드를 삽입하려고합니다.

본질적으로 :

IF (key exists)
  run update command
ELSE
  run insert command

이것을 작성하는 가장 좋은 방법은 무엇입니까?

535
Chris Cudmore

거래를 잊지 마라. 성능은 좋지만 단순한 (IF EXISTS ..) 방식은 매우 위험합니다.
여러 스레드가 삽입 또는 업데이트를 수행하려고하면 쉽게 기본 키 위반을 얻을 수 있습니다.

@Beau Crawford & @Esteban이 제공하는 솔루션은 일반적인 아이디어를 보여 주지만 오류가 발생하기 쉽습니다.

교착 상태와 PK 위반을 피하기 위해 다음과 같은 것을 사용할 수 있습니다 :

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

또는

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran
336
aku

매우 비슷한 이전 질문에 대한 상세한 답변보기

@Beau Crawford의 는 SQL 2005에서 좋은 방법입니다.하지만 담당자에게 권한을 부여하는 경우에는 첫 번째 사람에게 SO it 로 가야합니다. 유일한 문제는 삽입의 경우 여전히 두 개의 IO작업입니다.

MS Sql2008은 SQL : 2003 표준에서 merge을 도입했습니다.

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

이제는 실제로 하나의 IO 작업이지만 코드는 참 멋지 네요 :-(

367
Keith

UPSERT :

 업데이트 MyTable SET FieldA = @ FieldA WHERE 키 = @ 키 
 
 IF @@ ROWCOUNT = 0 
 INSERT INTO MyTable (FieldA) VALUES (@FieldA) 

http://en.wikipedia.org/wiki/Upsert

155
Beau Crawford

많은 사람들이 당신이 MERGE을 사용하도록 제안 할 것이지만, 나는 그것에 대해주의해야합니다. 기본적으로 동시성 및 경쟁 조건에서 다중 명령문 이상을 보호하지는 않지만 다른 위험을 초래합니다.

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

이 "더 간단한"구문을 사용할 수 있음에도 불구하고 여전히이 방법을 선호합니다 (간결하게하기 위해 오류 처리가 생략 됨).

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;

많은 사람들이 다음과 같이 제안합니다.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
  INSERT ...
END
COMMIT TRANSACTION;

그러나이 모든 작업을 수행하려면 업데이트 할 행을 찾기 위해 테이블을 두 번 읽어야 할 수도 있습니다. 첫 번째 샘플에서는 한 번만 행을 찾아야합니다. 두 경우 모두 초기 읽기에서 행을 찾지 못하면 삽입이 발생합니다.

다른 사람들은 다음과 같이 제안합니다.

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

그러나 거의 모든 삽입이 실패하는 드문 시나리오를 제외하고 SQL Server가 처음부터 방지 할 수있는 예외를 잡아내는 것보다 훨씬 많은 비용이 들지 않는 한 다른 이유로 인해 문제가됩니다. 나는 여기서 많이 증명한다.

80
Aaron Bertrand
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

편집하다:

아아, 내 자신에게 해가되는 것조차도, 선택을하지 않고도 그렇게하는 솔루션이 더 적은 단계로 작업을 수행하기 때문에 더 좋아 보이는 것 같습니다.

51
Esteban Araya

한 번에 둘 이상의 레코드를 UPSERT하려면 ANSI SQL : 2003 DML 문 MERGE를 사용할 수 있습니다.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

SQL Server 2005에서 MERGE 문을 모방합니다 .

36
Eric Weilnau

꽤 늦은 것에 대해 언급하기는했지만 MERGE를 사용하여보다 완벽한 예제를 추가하고 싶습니다.

이러한 Insert + Update 문은 일반적으로 "Upsert"문이라고하며 SQL Server에서 MERGE를 사용하여 구현할 수 있습니다.

아주 좋은 예가 여기에 있습니다 : http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

위에서는 잠금 및 동시성 시나리오에 대해서도 설명합니다.

나는 같은 것을 인용 할 것이다.

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;
10
user243131
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

테이블 및 필드 이름을 원하는대로 바꿉니다. 사용하여 조건을 돌 봅니다. 그런 다음 DECLARE 행에 변수의 적절한 값 (및 유형)을 설정하십시오.

건배.

8
Denver

MERGE Statement를 사용할 수 있습니다.이 문은 존재하지 않으면 데이터를 삽입하거나 if가 있으면 업데이트를 사용합니다.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
6
Daniel Acosta

UPDATE if-no-rows-updated 다음 INSERT 경로로 이동하면 경쟁 조건을 방지하기 위해 INSERT를 먼저 수행하는 것을 고려하십시오 (중간에 DELETE가 없다고 가정)

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET [email protected]
   WHERE [email protected]
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

경쟁 조건을 피하는 것 외에도 대부분의 경우 레코드가 이미 존재하면 INSERT가 실패하여 CPU를 낭비하게됩니다.

SQL2008 이상에서는 MERGE를 사용하는 것이 좋습니다.

4
Kristen

이는 사용 패턴에 따라 다릅니다. 하나는 세부 사항에서 길을 잃지 않고 사용 큰 그림을 봐야한다. 예를 들어, 레코드가 생성 된 후 사용 패턴이 99 % 업데이트 된 경우 'UPSERT'가 가장 적합한 솔루션입니다.

첫 번째 삽입 (적중) 후에는 모든 단일 명령문 업데이트가 적용되며 if 또는 buts는 없습니다. 삽입물의 'where'조건이 필요하다면 중복이 삽입되고 잠금을 처리하지 않아도됩니다.

UPDATE <tableName> SET <field>[email protected] WHERE [email protected];

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END
3
Saleh Najar

SQL Server 2008에서는 MERGE 문을 사용할 수 있습니다.

3
Bart

MS SQL Server 2008에는 SQL : 2003 표준의 일부인 MERGE 문이 도입되었습니다. 많은 사람들이 알 수 있듯이 한 행의 경우를 처리하는 것은 그리 큰 문제가 아니지만 큰 데이터 세트를 처리 할 때는 모든 성능 문제가 발생하는 커서가 필요합니다. MERGE 문은 큰 데이터 세트를 다룰 때 매우 환영받을 것입니다.

2
bjorsig

모든 사람들이 sprocs를 직접 실행하는 이러한 까다로운 사용자의 두려움에서 HOLDLOCK-s로 넘어 가기 전에 다음과 같이 지적합니다. 설계상의 새로운 PK-s의 고유성을 보장해야 함 (신원 키, Oracle의 시퀀스 생성기 외부 ID-s에 대한 고유 색인, 색인에 의해 다루어지는 조회). 그것이 문제의 알파와 오메가입니다. 당신이 그것을 가지고 있지 않다면 우주의 HOLDLOCK-s는 당신을 구원 할 것입니다. 만약 당신이 그것을 가지고 있다면, 당신은 첫 번째 선택에서 UPDLOCK을 넘어서 아무것도 필요하지 않습니다.

Sprocs는 일반적으로 매우 통제 된 조건과 신뢰할 수있는 호출자 (중간 계층)의 가정하에 실행됩니다. 간단한 upsert 패턴 (update + insert 또는 merge)이 미드 티어 또는 테이블 디자인의 버그를 의미하는 중복 PK를 본 경우 SQL은 이러한 경우에 오류를 발생시키고 레코드를 거부한다는 것을 의미합니다. 이 경우 HOLDLOCK을 놓는 것은 예외를 먹는 것과 같고 잠재적으로 오류가있는 데이터를 가져 오는 것과 같습니다.

그런데 MERGE를 사용하거나 UPDATE를 누른 다음 INSERT를 서버에서 더 쉽게 수행 할 수 있으며 처음 선택하기 위해 (UPDLOCK)을 추가하는 것을 기억할 필요가 없으므로 오류가 발생하기 쉽습니다. 또한 작은 배치로 삽입/업데이트를 수행하는 경우 트랜잭션이 적합한 지 여부를 결정하기 위해 데이터를 알아야합니다. 이것은 관련없는 레코드 모음 일 뿐이며, 추가적인 "포괄적 인"트랜잭션은 해를 끼칠 것입니다.

1
ZXX

삽입물이 뒤 따르는 업데이트를 처음 시도하면 경쟁 조건이 정말로 중요합니까? key key 에 대한 값을 설정하려는 두 개의 스레드가 있다고 가정 해 보겠습니다.

스레드 1 : 값 = 1
스레드 2 : 값 = 2

경쟁 조건 시나리오의 예

  1. 가 정의되지 않았습니다.
  2. 스레드 1이 업데이트에 실패합니다.
  3. 스레드 2가 업데이트와 함께 실패합니다.
  4. 정확히 thread 1 또는 thread 2 중 하나가 삽입에 성공합니다. 예 : 스레드 1
  5. 다른 스레드는 삽입 (오류 중복 키 포함) - 스레드 2와 함께 실패합니다.

    • 결과 : 삽입 할 두 개의 밟기 중 "첫 번째"가 값을 결정합니다.
    • 결과 : 데이터를 쓰는 마지막 두 스레드 (업데이트 또는 삽입)가 값을 결정해야합니다.

그러나; 다중 스레드 환경에서 OS 스케줄러는 스레드 실행 순서를 결정합니다. 위의 시나리오에서이 경쟁 조건이있는 곳에서 실행 순서를 결정한 것은 OS입니다. 즉 : "스레드 1"또는 "스레드 2"가 시스템 관점에서 "처음"이라고 말하는 것은 잘못된 것입니다.

스레드 1과 스레드 2에서 실행 시간이 너무 가까울 때 경쟁 조건의 결과는 중요하지 않습니다. 유일한 요구 사항은 스레드 중 하나가 결과 값을 정의해야한다는 것입니다.

구현의 경우 : 삽입 이후에 update가 발생하면 "duplicate key"오류가 발생하며, 이는 성공으로 처리되어야합니다.

물론, 데이터베이스의 값이 마지막에 쓴 값과 같다고 가정해서는 안됩니다.

1
runec

나는 아래의 솔루션을 시도했다 및 나를 위해, 삽입 문에 대한 동시 요청이 발생할 때 작동합니다.

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran
0
Dev