738 Stimmen

Lösungen für INSERT OR UPDATE auf SQL Server

Nehmen wir eine Tabellenstruktur von MyTable(KEY, datafield1, datafield2...) .

Häufig möchte ich entweder einen vorhandenen Datensatz aktualisieren oder einen neuen Datensatz einfügen, wenn dieser noch nicht existiert.

Im Wesentlichen:

IF (key exists)
  run update command
ELSE
  run insert command

Wie kann man das am besten schreiben?

53 Stimmen

Für alle, die diese Frage zum ersten Mal stellen - bitte lesen Sie unbedingt alle Antworten und Kommentare. Das Alter kann manchmal zu irreführenden Informationen führen...

1 Stimmen

Erwägen Sie die Verwendung des EXCEPT-Operators, der in SQL Server 2005 eingeführt wurde.

446voto

aku Punkte 118808

Vergessen Sie nicht die Transaktionen. Die Leistung ist gut, aber der einfache Ansatz (IF EXISTS..) ist sehr gefährlich.
Wenn mehrere Threads versuchen, eine Einfügung oder Aktualisierung vorzunehmen, können Sie leicht Primärschlüsselverletzung erhalten.

Die von @Beau Crawford und @Esteban bereitgestellten Lösungen zeigen eine allgemeine Idee, sind aber fehleranfällig.

Um Deadlocks und PK-Verletzungen zu vermeiden, können Sie etwas wie dieses verwenden:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

oder

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

4 Stimmen

Gefragt wurde nach der leistungsfähigsten Lösung und nicht nach der sichersten. Eine Transaktion erhöht zwar die Sicherheit des Prozesses, verursacht aber auch einen Mehraufwand.

0 Stimmen

Sicher, aber wenn wir schon über die Stabilität von Anwendungen sprechen, gibt es noch viele andere Dinge zu bedenken.

42 Stimmen

Beide Methoden können jedoch fehlschlagen. Wenn zwei gleichzeitige Threads dieselbe Zeile einfügen, wird der erste erfolgreich sein, aber die zweite Einfügung wird aufgrund einer Primärschlüsselverletzung fehlschlagen. Eine Transaktion garantiert nicht, dass die Einfügung erfolgreich sein wird, selbst wenn die Aktualisierung fehlgeschlagen ist, weil der Datensatz bereits existiert. Um zu gewährleisten, dass eine beliebige Anzahl von gleichzeitigen Transaktionen erfolgreich ist, MÜSSEN Sie eine Sperre verwenden.

404voto

Keith Punkte 141163

Siehe meine ausführliche Antwort auf eine sehr ähnliche frühere Frage

@Beau Crawford's ist ein guter Weg in SQL 2005 und darunter, obwohl, wenn Sie gewähren rep es sollte auf die gehen der erste Typ, der es SO macht . Das einzige Problem ist, dass es sich bei Einfügungen immer noch um zwei IO-Operationen handelt.

MS Sql2008 stellt vor merge aus dem SQL:2003-Standard:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Jetzt ist es wirklich nur eine IO-Operation, aber ein furchtbarer Code :-(

11 Stimmen

@Ian Boyd - ja, das ist die Syntax des SQL:2003-Standards, nicht die des upsert die fast alle anderen DB-Anbieter stattdessen unterstützen. Die upsert Syntax ist eine weitaus schönere Möglichkeit, dies zu tun, so dass MS sie zumindest auch hätte unterstützen sollen - es ist ja nicht so, dass es das einzige nicht standardmäßige Schlüsselwort in T-SQL ist

0 Stimmen

In der Dokumentation wird eine Where-Klausel nicht als gültige Syntax angegeben: technet.microsoft.com/de-us/library/bb510625.aspx

1 Stimmen

Gibt es einen Kommentar zu dem Hinweis auf das Schloss in anderen Antworten? (ich werde es bald herausfinden, aber wenn es der empfohlene Weg ist, empfehle ich, ihn in die Antwort aufzunehmen)

214voto

Beau Crawford Punkte 2059

Führen Sie einen UPSERT durch:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

9 Stimmen

Primärschlüsselverletzungen sollten nicht auftreten, wenn Sie die richtigen eindeutigen Indexbeschränkungen angewendet haben. Der Sinn der Einschränkung besteht darin, doppelte Zeilen zu verhindern. Es spielt keine Rolle, wie viele Threads versuchen, etwas einzufügen, die Datenbank wird so viel serialisieren, wie nötig ist, um die Einschränkung durchzusetzen... und wenn sie das nicht tut, dann ist die Engine wertlos. Natürlich wäre es korrekter und weniger anfällig für Deadlocks oder fehlgeschlagene Einfügungen, dies in eine serialisierte Transaktion zu verpacken.

21 Stimmen

@Triynko, ich glaube, @Sam Saffron meinte, dass, wenn zwei+ Threads in der richtigen Reihenfolge ineinandergreifen, der Sql-Server werfen einen Fehler, der eine Primärschlüsselverletzung anzeigt hätte aufgetreten. Die Einbettung in eine serialisierbare Transaktion ist der richtige Weg, um Fehler in der obigen Anweisungsreihe zu vermeiden.

1 Stimmen

Selbst wenn Sie einen Primärschlüssel haben, der automatisch inkrementiert, müssen Sie sich um eindeutige Beschränkungen kümmern, die in der Tabelle vorhanden sein könnten.

142voto

Aaron Bertrand Punkte 259330

Viele Leute werden Ihnen vorschlagen, die MERGE aber ich warne Sie davor. Standardmäßig schützt es Sie nicht vor Gleichzeitigkeit und Race Conditions, genauso wenig wie mehrere Anweisungen, und es birgt andere Gefahren:

Selbst mit dieser "einfacheren" Syntax bevorzuge ich immer noch diesen Ansatz (Fehlerbehandlung der Kürze halber weggelassen):

BEGIN TRANSACTION;

UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE) 
  SET ... WHERE PK = @PK;

IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END

COMMIT TRANSACTION;

Viele Leute werden diesen Weg vorschlagen:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
BEGIN
  INSERT ...
END
COMMIT TRANSACTION;

Dies führt jedoch nur dazu, dass Sie die Tabelle möglicherweise zweimal lesen müssen, um die zu aktualisierende(n) Zeile(n) zu finden. Im ersten Beispiel brauchen Sie die Zeile(n) nur einmal zu suchen. (In beiden Fällen wird eine Einfügung vorgenommen, wenn beim ersten Lesen keine Zeile gefunden wird).

Andere werden diesen Weg vorschlagen:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

Dies ist jedoch problematisch, und zwar aus keinem anderen Grund als dem, dass es viel teurer ist, SQL Server Ausnahmen abfangen zu lassen, die Sie von vornherein hätten verhindern können, außer in dem seltenen Fall, dass fast jede Einfügung fehlschlägt. Ich beweise dies hier:

3 Stimmen

Wie verhält es sich mit dem Einfügen/Aktualisieren FROM einer Tabelle, die viele Datensätze einfügt/aktualisiert?

1 Stimmen

@user960567 Nun, UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);

5 Stimmen

Schön geantwortet nach mehr als 2 Jahren :)

75voto

Esteban Araya Punkte 28454
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Editar:

Leider muss ich, auch zu meinem eigenen Nachteil, zugeben, dass die Lösungen, die dies ohne eine Auswahl tun, besser zu sein scheinen, da sie die Aufgabe mit einem Schritt weniger erledigen.

7 Stimmen

Diese gefällt mir immer noch besser. Das Upsert wirkt eher wie eine Programmierung mit Nebeneffekt, und ich habe niemals Ich habe gesehen, dass der winzig kleine geclusterte Indexsuchlauf dieses ersten Selects in einer echten Datenbank Leistungsprobleme verursacht.

0 Stimmen

@EricZBeard Es geht nicht um Leistung (auch wenn es nicht so ist つねに eine Suche, die Sie redundant durchführen, je nachdem, was Sie prüfen, um ein Duplikat anzuzeigen). Das eigentliche Problem ist die Möglichkeit, die die zusätzliche Operation für Race Conditions und Deadlocks eröffnet (ich erkläre, warum in diesem Beitrag ).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X