Ich versuche, eine Tabelle mit einem Array von Werten zu aktualisieren. Jedes Element im Array enthält Informationen, die mit einer Zeile in einer Tabelle in der SQL Server-Datenbank übereinstimmen. Wenn die Zeile bereits in der Tabelle vorhanden ist, aktualisieren wir diese Zeile mit den Informationen im angegebenen Array. Andernfalls fügen wir eine neue Zeile in die Tabelle ein. Ich habe im Grunde Upsert beschrieben.
Jetzt versuche ich dies in einer gespeicherten Prozedur zu erreichen, die einen XML-Parameter verwendet. Der Grund, warum ich XML und keine tabellenwertigen Parameter verwende, ist, dass ich in letzterem Fall einen benutzerdefinierten Typ in SQL erstellen und diesen Typ der gespeicherten Prozedur zuordnen muss. Wenn ich jemals etwas an meiner gespeicherten Prozedur oder meinem Datenbankschema ändern würde, müsste ich sowohl die gespeicherte Prozedur als auch den benutzerdefinierten Typ wiederholen. Ich möchte diese Situation vermeiden. Außerdem ist die Überlegenheit, die TVP gegenüber XML hat, für meine Situation nicht nützlich, da meine Datenarraygröße niemals 1000 überschreitet. Dies bedeutet, dass ich die hier vorgeschlagene Lösung nicht verwenden kann: So fügen Sie mehrere Datensätze mit XML in SQL Server 2008 ein
Eine ähnliche Diskussion hier ( UPSERT - Gibt es eine bessere Alternative zu MERGE oder @@ rowcount? ) Unterscheidet sich von dem, was ich frage, weil ich versuche, mehrere Zeilen in eine Tabelle einzufügen .
Ich hatte gehofft, dass ich einfach die folgenden Abfragen verwenden würde, um die Werte aus der XML zu erhöhen. Aber das wird nicht funktionieren. Dieser Ansatz soll nur funktionieren, wenn die Eingabe eine einzelne Zeile ist.
begin tran
update table with (serializable) set select * from xml_param
where key = @key
if @@rowcount = 0
begin
insert table (key, ...) values (@key,..)
end
commit tran
Die nächste Alternative besteht darin, ein erschöpfendes IF EXISTS oder eine seiner Variationen der folgenden Form zu verwenden. Ich lehne dies jedoch aus Gründen der suboptimalen Effizienz ab:
IF (SELECT COUNT ... ) > 0
UPDATE
ELSE
INSERT
Die nächste Option war die Verwendung der Merge-Anweisung wie hier beschrieben: http://www.databasejournal.com/features/mssql/using-the-merge-statement-to-perform-an-upsert.html . Aber dann habe ich hier über Probleme mit der Zusammenführungsabfrage gelesen: http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/ . Aus diesem Grund versuche ich, Merge zu vermeiden.
Meine Frage lautet nun: Gibt es eine andere Option oder einen besseren Weg, um mithilfe von XML-Parametern in gespeicherten SQL Server 2008-Prozeduren mehrere Upsert zu erzielen?
Bitte beachten Sie, dass die Daten im XML-Parameter möglicherweise einige Datensätze enthalten, die nicht UPSERTed werden sollten, da sie älter als der aktuelle Datensatz sind. ModifiedDate
Sowohl in der XML- als auch in der Zieltabelle gibt es ein Feld, das verglichen werden muss, um festzustellen, ob der Datensatz aktualisiert oder verworfen werden soll.
quelle
MERGE
, auf die Bertrand hinweist, sind meistens Randfälle und Ineffizienzen, keine Stopper - MS hätte es nicht veröffentlicht, wenn es ein echtes Minenfeld gewesen wäre. Sind Sie sicher, dass die Windungen, die Sie vermeidenMERGE
möchten, nicht mehr potenzielle Fehler verursachen als sie speichern?MERGE
. Die Schritte INSERT und UPDATE von MERGE werden weiterhin separat verarbeitet. Der Hauptunterschied in meinem Ansatz ist die Tabellenvariable, die die aktualisierten Datensatz-IDs enthält, und die DELETE-Abfrage, die diese Tabellenvariable verwendet, um diese Datensätze aus der temporären Tabelle der eingehenden Daten zu entfernen. Und ich nehme an, die QUELLE könnte direkt von @ XMLparam.nodes () stammen, anstatt in eine temporäre Tabelle zu kopieren, aber das ist nicht viel zusätzliches Zeug, um sich keine Sorgen machen zu müssen, jemals in einem dieser Randfälle zu sein; - ).Antworten:
Ob die Quelle XML oder ein TVP ist, macht keinen großen Unterschied. Der Gesamtbetrieb ist im Wesentlichen:
Sie tun dies in dieser Reihenfolge, denn wenn Sie zuerst EINFÜGEN, sind alle Zeilen vorhanden, um das UPDATE zu erhalten, und Sie werden wiederholt für alle Zeilen arbeiten, die gerade eingefügt wurden.
Darüber hinaus gibt es verschiedene Möglichkeiten, dies zu erreichen, und verschiedene Möglichkeiten, um die Effizienz zu steigern.
Beginnen wir mit dem Nötigsten. Da das Extrahieren des XML wahrscheinlich einer der teureren Teile dieses Vorgangs ist (wenn nicht der teuerste), möchten wir dies nicht zweimal tun müssen (da wir zwei Vorgänge ausführen müssen). Also erstellen wir eine temporäre Tabelle und extrahieren die Daten aus dem XML in diese:
Von dort aus machen wir das UPDATE und dann das INSERT:
Nachdem wir die grundlegende Operation ausgeführt haben, können wir einige Maßnahmen zur Optimierung ergreifen:
Erfassen Sie @@ ROWCOUNT der Einfügung in die temporäre Tabelle und vergleichen Sie sie mit @@ ROWCOUNT des UPDATE. Wenn sie gleich sind, können wir das INSERT überspringen
Erfassen Sie die über die OUTPUT-Klausel aktualisierten ID-Werte und löschen Sie diese aus der temporären Tabelle. Dann braucht das INSERT das nicht
WHERE NOT EXISTS(...)
Wenn die eingehenden Daten Zeilen enthalten, die nicht synchronisiert (dh weder eingefügt noch aktualisiert) werden sollen, sollten diese Datensätze vor dem UPDATE entfernt werden
Ich habe dieses Modell mehrmals für Importe / ETLs verwendet, die entweder weit über 1000 Zeilen oder vielleicht 500 in einem Stapel von insgesamt 20.000 Zeilen enthalten - über eine Million Zeilen. Ich habe jedoch den Leistungsunterschied zwischen dem LÖSCHEN der aktualisierten Zeilen aus der temporären Tabelle und dem Aktualisieren des Felds [IsUpdate] nicht getestet.
Bitte beachten Sie die Entscheidung, XML über TVP zu verwenden, da höchstens 1000 Zeilen gleichzeitig importiert werden müssen (siehe Frage):
Wenn dies hier und da einige Male aufgerufen wird, ist der geringfügige Leistungsgewinn in TVP möglicherweise die zusätzlichen Wartungskosten nicht wert (Sie müssen den Prozess löschen, bevor Sie den benutzerdefinierten Tabellentyp ändern, den App-Code ändern usw.). . Wenn Sie jedoch 4 Millionen Zeilen importieren und jeweils 1000 senden, sind dies 4000 Ausführungen (und 4 Millionen XML-Zeilen zum Parsen, unabhängig davon, wie sie aufgeteilt sind), und selbst ein geringfügiger Leistungsunterschied, wenn sie nur einige Male ausgeführt werden summieren sich zu einem spürbaren Unterschied.
Abgesehen davon ändert sich die von mir beschriebene Methode nicht, außer dass SELECT FROM @XmlInputParam durch SELECT FROM @TVP ersetzt wird. Da TVPs schreibgeschützt sind, können Sie sie nicht löschen. Ich denke, Sie könnten einfach ein
WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)
letztes SELECT (gebunden an das INSERT) anstelle des einfachen hinzufügenWHERE IsUpdate = 0
. Wenn Sie die@UpdateIDs
Tabellenvariable auf diese Weise verwenden würden, könnten Sie sogar davonkommen, die eingehenden Zeilen nicht in die temporäre Tabelle zu kopieren.quelle