È sicuro fare affidamento sull'ordine della clausola OUTPUT di INSERT?


19

Data questa tabella:

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

In due scenari leggermente diversi voglio inserire righe e restituire i valori dalla colonna identità.

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

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

Domanda

Posso fare affidamento sui valori di identità restituiti dall'inserto della dbo.Targettabella per essere restituiti nell'ordine in cui esistevano nella VALUESclausola 1) e nella #Targettabella 2) , in modo da poterli correlare in base alla loro posizione nel set di righe di output all'input originale?

Per riferimento

Ecco un po 'di codice C # ridotto che dimostra cosa sta succedendo nell'applicazione (scenario 1, che presto sarà convertito in uso 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();
}

Risposte:


22

Posso fare affidamento sui valori di identità restituiti dall'inserto della tabella dbo.Target da restituire nell'ordine in cui esistevano nella clausola 1) VALUES e nella tabella 2) #Target, in modo da poterli correlare in base alla loro posizione nel set di righe di output. all'input originale?

No, non puoi fare affidamento su qualcosa da garantire senza una reale garanzia documentata. La documentazione afferma esplicitamente che non esiste tale garanzia.

SQL Server non garantisce l'ordine in cui le righe vengono elaborate e restituite dalle istruzioni DML mediante la clausola OUTPUT. Spetta all'applicazione includere una clausola WHERE appropriata in grado di garantire la semantica desiderata o comprendere che quando più righe possono qualificarsi per l'operazione DML, non esiste un ordine garantito.

Ciò farebbe affidamento su molte ipotesi non documentate

  1. L'ordine in cui le righe vengono emesse dalla scansione costante è nello stesso ordine della clausola dei valori (non li ho mai visti differire ma AFAIK non è garantito).
  2. L'ordine in cui le righe vengono inserite sarà lo stesso dell'ordine in cui vengono emesse dalla scansione costante (sicuramente non sempre il caso).
  3. Se si utilizza un piano di esecuzione "ampio" (per indice), i valori della clausola di output verranno estratti dall'operatore di aggiornamento dell'indice cluster e non quello di alcun indice secondario.
  4. La garanzia che l'ordine venga conservato in seguito, ad es. Durante il confezionamento di file in fila per la trasmissione in rete .
  5. Che anche se l'ordine sembra prevedibile ora l'implementazione delle modifiche a funzionalità come l'inserimento parallelo non cambierà l'ordine in futuro (attualmente se la clausola OUTPUT è specificata nell'istruzione INSERT ... SELECT per restituire risultati al client, i piani paralleli sono disabilitato in generale, inclusi INSERT )

Un esempio di errore del secondo punto (supponendo PK in cluster di (Color, Action)) può essere visto se si aggiungono 600 righe alla VALUESclausola. Quindi il piano ha un operatore di ordinamento prima dell'inserimento, quindi perdi il tuo ordine originale nella VALUESclausola.

C'è un modo documentato per raggiungere il tuo obiettivo e questo è quello di aggiungere una numerazione alla fonte e utilizzare MERGEinvece diINSERT

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; 

inserisci qui la descrizione dell'immagine

@un cavallo senza nome

La fusione è davvero necessaria? Non potresti semplicemente fare un insert into ... select ... from (values (..)) t (...) order by sourceid?

Sì, potresti. Ordinare garanzie in SQL Server ... afferma che

Le query INSERT che utilizzano SELECT con ORDER BY per popolare le righe garantiscono il modo in cui vengono calcolati i valori di identità ma non l'ordine in cui vengono inserite le righe

Quindi potresti usare

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

inserisci qui la descrizione dell'immagine

Ciò garantirebbe che i valori di identità vengano assegnati in ordine t.SourceIdma non che vengano emessi in un ordine particolare o che i valori della colonna di identità assegnati non abbiano spazi vuoti (ad es. Se si tenta un inserimento simultaneo).


2
Quest'ultimo bit sul potenziale di lacune e l'output non in un ordine particolare rende le cose un po 'più interessanti per provare a ricollegare all'input. Suppongo che un ordine nell'applicazione farebbe il lavoro, ma sembra più sicuro e più chiaro usare solo il MERGE.
ErikE

Utilizzare la OUTPUT ... INTO [#temp]sintassi, quindi SELECT ... FROM [#temp] ORDER BYper garantire l'ordine di output.
Max Vernon,

TL; versione DR: con SQL Server e credo che le implementazioni SQL in generale, a meno che non sia presente una clausola ORDER BY, l'ordine non è garantito.
nateirvin,

Ri. lacune: racchiuderebbe la Insertdichiarazione in modo da Transactionprevenire le lacune?
Tom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.