it-swarm.com.de

Ist es sicher, sich auf die Reihenfolge der OUTPUT-Klausel eines INSERT zu verlassen?

Angesichts dieser Tabelle:

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 zwei leicht unterschiedlichen Szenarien möchte ich Zeilen einfügen und die Werte aus der Identitätsspalte zurückgeben.

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

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

Frage

Kann ich mich darauf verlassen, dass die zurückgegebenen Identitätswerte aus der Tabelleneinfügung dbo.Target In der Reihenfolge zurückgegeben werden, in der sie in der 1) VALUES -Klausel und 2) #Target Tabelle vorhanden waren Ich kann sie durch ihre Position im Ausgabe-Rowset wieder mit der ursprünglichen Eingabe korrelieren.

Als Referenz

Hier ist ein reduzierter C # -Code, der zeigt, was in der Anwendung passiert (Szenario 1, das bald für die Verwendung von SqlBulkCopy konvertiert wird):

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 => [email protected]"({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();
}
20
ErikE

Kann ich mich darauf verlassen, dass die zurückgegebenen Identitätswerte aus der dbo.Target-Tabelleneinfügung in der Reihenfolge zurückgegeben werden, in der sie in der 1) VALUES-Klausel und 2) #Target-Tabelle vorhanden waren, damit ich sie anhand ihrer Position im Ausgabe-Rowset zurück korrelieren kann zum ursprünglichen Eingang?

Nein, Sie können sich nicht darauf verlassen, dass etwas ohne eine tatsächlich dokumentierte Garantie garantiert wird. In der Dokumentation ausdrücklich angegeben gibt es keine solche Garantie.

SQL Server garantiert nicht die Reihenfolge, in der Zeilen von DML-Anweisungen unter Verwendung der OUTPUT-Klausel verarbeitet und zurückgegeben werden. Es ist Sache der Anwendung, eine geeignete WHERE-Klausel aufzunehmen, die die gewünschte Semantik garantieren kann, oder zu verstehen, dass es keine garantierte Reihenfolge gibt, wenn sich mehrere Zeilen für die DML-Operation qualifizieren.

Dies würde auf vielen undokumentierten Annahmen beruhen

  1. Die Reihenfolge, in der die Zeilen vom konstanten Scan ausgegeben werden, liegt in derselben Reihenfolge wie die Werteklausel (ich habe nie gesehen, dass sie sich unterscheiden, aber AFAIK dies ist nicht garantiert).
  2. Die Reihenfolge, in der die Zeilen eingefügt werden, entspricht der Reihenfolge, in der sie vom konstanten Scan ausgegeben werden (definitiv nicht immer der Fall).
  3. Wenn Sie einen "breiten" Ausführungsplan (pro Index) verwenden, werden die Werte aus der Ausgabeklausel aus dem Clustered-Index-Aktualisierungsoperator und nicht aus den Sekundärindizes abgerufen.
  4. Dass die Reihenfolge danach garantiert erhalten bleibt - z. when Verpackungszeilen für die Übertragung über das Netzwerk .
  5. Selbst wenn die Reihenfolge jetzt vorhersehbar erscheint, werden Implementierungsänderungen an Funktionen wie dem parallelen Einfügen die Reihenfolge in Zukunft nicht ändern (derzeit wenn die OUTPUT-Klausel in der INSERT… SELECT-Anweisung angegeben ist, um Ergebnisse an den Client zurückzugeben). dann sind parallele Pläne im Allgemeinen deaktiviert, einschließlich INSERTs )

Ein Beispiel für einen Fehler in Punkt zwei (unter der Annahme einer Cluster-PK von (Color, Action)) Ist zu sehen, wenn Sie der VALUES -Klausel 600 Zeilen hinzufügen. Dann hat der Plan vor dem Einfügen einen Sortieroperator, wodurch Ihre ursprüngliche Reihenfolge in der VALUES -Klausel verloren geht.

Es gibt jedoch einen dokumentierten Weg, um Ihr Ziel zu erreichen. Dies besteht darin, der Quelle eine Nummerierung hinzuzufügen und MERGE anstelle von INSERT zu verwenden

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; 

(enter image description here

@ a_horse_with_no_name

Ist die Zusammenführung wirklich notwendig? Könnten Sie nicht einfach eine insert into ... select ... from (values (..)) t (...) order by sourceid ausführen?

Ja du könntest. Bestellgarantien in SQL Server… gibt das an

INSERT-Abfragen, die SELECT mit ORDER BY zum Auffüllen von Zeilen verwenden, garantieren, wie Identitätswerte berechnet werden, nicht jedoch die Reihenfolge, in der die Zeilen eingefügt werden

Also könntest du verwenden

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

(enter image description here

Dies würde garantieren, dass die Identitätswerte in der Reihenfolge t.SourceId Zugewiesen werden, jedoch nicht, dass sie in einer bestimmten Reihenfolge ausgegeben werden oder dass die zugewiesenen Identitätsspaltenwerte keine Lücken aufweisen (z. B. wenn eine gleichzeitige Einfügung versucht wird).

23
Martin Smith