É seguro confiar na ordem da cláusula OUTPUT do INSERT?

19

Dada esta tabela:

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

Em dois cenários ligeiramente diferentes, desejo inserir linhas e retornar os valores da coluna de identidade.

Cenário 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)
;

Cenário 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
;

Questão

Posso confiar nos valores de identidade retornados da dbo.Targetinserção da tabela a serem retornados na ordem em que existiam na VALUEScláusula 1) e na #Targettabela 2) , para que eu possa correlacioná-los por sua posição no conjunto de linhas de saída com a entrada original?

Para referência

Aqui está um código C # reduzido que demonstra o que está acontecendo no aplicativo (cenário 1, que será convertido para uso em breve 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();
}
ErikE
fonte

Respostas:

22

Posso confiar nos valores de identidade retornados da inserção da tabela dbo.Target a serem retornados na ordem em que existiam na cláusula 1) VALUES e 2) na tabela #Target, para que eu possa correlacioná-los por sua posição no conjunto de linhas de saída para a entrada original?

Não, você não pode confiar em nada a ser garantido sem uma garantia documentada real. A documentação declara explicitamente que não existe tal garantia.

O SQL Server não garante a ordem na qual as linhas são processadas e retornadas pelas instruções DML usando a cláusula OUTPUT. Cabe ao aplicativo incluir uma cláusula WHERE apropriada que possa garantir a semântica desejada ou entender que quando várias linhas podem se qualificar para a operação DML, não há ordem garantida.

Isso dependeria de muitas suposições não documentadas

  1. A ordem em que as linhas são produzidas a partir da varredura constante está na mesma ordem que a cláusula de valores (nunca as vi diferentes, mas o AFAIK não é garantido).
  2. A ordem em que as linhas são inseridas será igual à ordem em que são produzidas na varredura constante (definitivamente nem sempre é o caso).
  3. Se você estiver usando um plano de execução "amplo" (por índice), os valores da cláusula de saída serão extraídos do operador de atualização de índice clusterizado e não dos índices secundários.
  4. Que o pedido é garantido para ser preservado posteriormente - por exemplo, ao empacotar linhas para transmissão pela rede .
  5. Mesmo que a ordem pareça previsível agora, a implementação de alterações em recursos como inserção paralela não alterará a ordem no futuro (atualmente, se a cláusula OUTPUT for especificada na instrução INSERT… SELECT para retornar resultados ao cliente, planos paralelos serão desativado em geral, incluindo INSERTs )

Um exemplo de falha do ponto dois (assumindo PK de cluster (Color, Action)) pode ser visto se você adicionar 600 linhas à VALUEScláusula. Em seguida, o plano possui um operador de classificação antes da inserção, perdendo seu pedido original na VALUEScláusula

Existe uma maneira documentada de alcançar seu objetivo e isso é adicionar uma numeração à fonte e usá-la em MERGEvez deINSERT

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; 

insira a descrição da imagem aqui

@um cavalo sem nome

A mesclagem é realmente necessária? Você não poderia simplesmente fazer um insert into ... select ... from (values (..)) t (...) order by sourceid?

Sim você pode. Solicitar garantias no SQL Server… afirma que

As consultas INSERT que usam SELECT com ORDER BY para preencher linhas garantem como os valores de identidade são computados, mas não a ordem na qual as linhas são inseridas

Então você poderia usar

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

insira a descrição da imagem aqui

Isso garantiria que os valores de identidade sejam atribuídos em ordem, t.SourceIdmas não que sejam gerados em qualquer ordem específica, ou que os valores da coluna de identidade atribuídos não tenham lacunas (por exemplo, se uma inserção simultânea for tentada).

Martin Smith
fonte
2
Esse último trecho sobre o potencial de falhas e a saída não estar em uma ordem específica torna as coisas um pouco mais interessantes para tentar correlacionar de volta à entrada. Suponho que uma ordem no aplicativo faça o trabalho, mas parece mais seguro e claro usar apenas o MERGE.
precisa saber é o seguinte
Use a OUTPUT ... INTO [#temp]sintaxe e SELECT ... FROM [#temp] ORDER BYgaranta a ordem de saída.
Max Vernon
Versão TL; DR: com o SQL Server, e acredito que as implementações do SQL em geral, a menos que haja uma cláusula ORDER BY, o pedido não é garantido.
Nateirvin #
Ré. lacunas: encerrar a Insertdeclaração em Transactionevitar lacunas?
Tom