INSERT의 OUTPUT 절 순서에 의존하는 것이 안전합니까?


19

이 테이블이 주어지면 :

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);

두 가지 약간 다른 시나리오에서 행을 삽입하고 ID 열에서 값을 반환하려고합니다.

시나리오 1

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;

시나리오 2

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;

질문

dbo.Target1) VALUES절과 2) #Target테이블에 존재하는 순서대로 테이블 삽입 에서 반환 된 ID 값을 사용 하여 출력 행 집합의 위치를 ​​기준으로 원래 입력과 상관시킬 수 있습니까?

참고를 위해

다음은 응용 프로그램에서 발생하는 상황을 보여주는 일부 C # 코드입니다 (시나리오 1, 곧 사용하도록 변환 됨 SqlBulkCopy).

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}

답변:


22

dbo.Target 테이블 삽입에서 리턴 된 ID 값을 사용하여 1) VALUES 절 및 2) #Target 테이블에 존재하는 순서대로 리턴되도록 할 수 있습니까? 원래 입력으로?

아닙니다. 실제 문서화 된 보증 없이는 보장 할 내용에 의존 할 수 없습니다. 이 문서 에는 그러한 보증이 없다고 명시되어 있습니다 .

SQL Server는 OUTPUT 절을 사용하여 DML 문에서 행을 처리하고 반환하는 순서를 보장하지 않습니다. 원하는 의미를 보장 할 수있는 적절한 WHERE 절을 포함하거나 여러 행이 DML 연산에 적합 할 수있는 경우 보장 된 순서가 없음을 이해하는 것은 응용 프로그램의 책임입니다.

이것은 문서화되지 않은 많은 가정에 의존합니다.

  1. 연속 스캔에서 행이 출력되는 순서는 values ​​절과 동일한 순서입니다 (나는 본 적이 없지만 AFAIK는 보장되지 않습니다).
  2. 행이 삽입되는 순서는 연속 스캔에서 출력되는 순서와 동일합니다 (항상 항상 그런 것은 아닙니다).
  3. "인덱스 당"실행 계획을 사용하는 경우 출력 절의 값은 보조 인덱스의 값이 아닌 클러스터 된 인덱스 업데이트 연산자에서 가져옵니다.
  4. 예를 들어 네트워크를 통한 전송을 위해 행을 패키징 할 때 순서가 유지되도록 보장 합니다 .
  5. 순서가 예측 가능해 보이지만 병렬 삽입과 같은 기능의 구현 변경은 향후 순서를 변경하지 않습니다 (현재 OUTPUT 절이 INSERT… SELECT 문에 지정되어 결과를 클라이언트에 반환하는 경우 병렬 계획은 다음과 같습니다) INSERT를 포함하여 일반적으로 사용 중지됨 )

절에 (Color, Action)600 개의 행을 추가 하면 포인트 2 실패 (예 : 클러스터 된 PK의 가정)의 예를 볼 수 있습니다 VALUES. 그런 다음 계획에 삽입 전에 정렬 연산자가 있으므로 VALUES조항 의 원래 순서가 손실 됩니다.

목표를 달성하는 문서화 된 방법이 있으며 소스에 번호를 추가하고 MERGE대신에 사용하는 것입니다INSERT

MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 

여기에 이미지 설명을 입력하십시오

@a_horse_with_no_name

병합이 정말로 필요합니까? 당신은 단지 할 수 insert into ... select ... from (values (..)) t (...) order by sourceid없습니까?

네 가능합니다. SQL Server의 보증을 주문 ... 상태가

SELECT를 ORDER BY와 함께 사용하여 행을 채우는 INSERT 쿼리는 ID 값이 계산되는 방식을 보장하지만 행이 삽입되는 순서는 보장하지 않습니다.

그래서 당신은 사용할 수 있습니다

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId

여기에 이미지 설명을 입력하십시오

이는 식별 값이 순서대로 지정 t.SourceId되지만 특정 순서로 출력되거나 할당 된 식별 컬럼 값에 간격이 없음 을 보장합니다 (예 : 동시 삽입이 시도되는 경우).


2
갭의 가능성과 출력이 특정 순서가 아닌 것에 대한 마지막 부분은 입력과 다시 상관 관계를 맺는 데 조금 더 흥미 롭습니다. 응용 프로그램에서 주문이 작업을 수행한다고 가정하지만을 사용하는 것이 더 안전하고 명확 해 보입니다 MERGE.
ErikE

OUTPUT ... INTO [#temp]구문을 사용 SELECT ... FROM [#temp] ORDER BY하여 출력 순서를 보장하십시오.
Max Vernon

TL; DR 버전 : SQL Server에서는 ORDER BY 절이 없으면 순서가 보장되지 않는 SQL 구현이 일반적이라고 생각합니다.
nateirvin

레. 격차 : Insert성명을 Transaction방지 격차로 둘러 쌀 것인가?
Tom
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.