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


598

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

Oft möchte ich entweder einen vorhandenen Datensatz aktualisieren oder einen neuen Datensatz einfügen, wenn dieser nicht vorhanden ist.

Im Wesentlichen:

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

Was ist die beste Art, dies zu schreiben?



27
Wenn Sie zum ersten Mal auf diese Frage stoßen, lesen Sie bitte alle Antworten und Kommentare. Alter kann manchmal zu irreführenden Informationen führen ...
Aaron Bertrand

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

Antworten:


370

Transaktionen nicht vergessen. Die Leistung ist gut, aber ein einfacher Ansatz (WENN EXISTIERT ..) ist sehr gefährlich.
Wenn mehrere Threads versuchen, Einfügen oder Aktualisieren durchzuführen, kann dies leicht zu einer Verletzung des Primärschlüssels führen.

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

Um Deadlocks und PK-Verstöße zu vermeiden, können Sie Folgendes 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

1
Die Frage wurde eher nach der performantesten als nach der sichersten Lösung gestellt. Eine Transaktion erhöht zwar die Sicherheit des Prozesses, erhöht jedoch auch den Overhead.
Luke Bennett

31
Beide Methoden können immer noch fehlschlagen. Wenn zwei gleichzeitige Threads in derselben Zeile dasselbe tun, ist der erste erfolgreich, die zweite Einfügung schlägt jedoch aufgrund einer Verletzung des Primärschlüssels fehl. Eine Transaktion garantiert nicht, dass die Einfügung erfolgreich ist, auch wenn die Aktualisierung fehlgeschlagen ist, weil der Datensatz vorhanden war. Um sicherzustellen, dass eine beliebige Anzahl von gleichzeitigen Transaktionen erfolgreich ist, MÜSSEN Sie eine Sperre verwenden.
Jean Vincent

7
@aku aus irgendeinem Grund haben Sie Tabellenhinweise ("mit (xxxx)") im Gegensatz zu "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" kurz vor Ihrem BEGIN TRAN verwendet?
EBarr

4
@CashCow, der Letzte gewinnt, das soll INSERT oder UPDATE tun: Der erste fügt ein, der zweite aktualisiert den Datensatz. Durch Hinzufügen einer Sperre kann dies in sehr kurzer Zeit geschehen, wodurch ein Fehler vermieden wird.
Jean Vincent

1
Ich habe immer gedacht, dass die Verwendung von Sperrhinweisen schlecht ist, und wir sollten die Microsoft Internal Engine Sperren diktieren lassen. Ist dies die offensichtliche Ausnahme von der Regel?

381

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

@Beau Crawford's ist ein guter Weg in SQL 2005 und darunter, aber wenn Sie rep gewähren, sollte es an den ersten gehen, der es SO macht . Das einzige Problem ist, dass es für Einfügungen immer noch zwei E / A-Operationen sind.

MS Sql2008 führt mergeaus dem SQL: 2003-Standard ein:

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 E / A-Operation, aber schrecklicher Code :-(


10
@ Ian Boyd - Ja, das ist die Syntax des SQL: 2003-Standards, nicht die upsert, die fast alle anderen DB-Anbieter stattdessen unterstützen wollten. Die upsertSyntax ist ein weitaus besserer Weg, dies zu tun, also hätte MS sie zumindest auch unterstützen sollen - es ist nicht so, als wäre es das einzige nicht standardmäßige Schlüsselwort in T-SQL
Keith

1
Gibt es einen Kommentar zum Sperrhinweis in anderen Antworten? (wird es bald herausfinden, aber wenn es der empfohlene Weg ist, empfehle ich, es der Antwort hinzuzufügen)
eglasius

25
Unter weblogs.sqlteam.com/dang/archive/2009/01/31/… finden Sie Antworten dazu, wie Sie verhindern können, dass Rennbedingungen Fehler verursachen, die auch bei Verwendung der MERGESyntax auftreten können.
Seph

5
@Seph, das ist eine echte Überraschung - ein Fehler von Microsoft: -SIch denke, das bedeutet, dass Sie HOLDLOCKfür Zusammenführungsvorgänge in Situationen mit hoher Parallelität einen benötigen .
Keith

11
Diese Antwort muss wirklich aktualisiert werden, um den Kommentar von Seph zu berücksichtigen, dass sie ohne HOLDLOCK nicht threadsicher ist. Laut dem verlinkten Beitrag hebt MERGE implizit eine Aktualisierungssperre auf, gibt sie jedoch vor dem Einfügen von Zeilen frei, was beim Einfügen zu einer Race-Bedingung und Verstößen gegen den Primärschlüssel führen kann. Bei Verwendung von HOLDLOCK bleiben die Sperren bis nach dem Einsetzen erhalten.
Triynko

169

Mach ein UPSERT:

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

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

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


7
Primärschlüsselverletzungen sollten nicht auftreten, wenn Sie die richtigen eindeutigen Indexbeschränkungen angewendet haben. Der springende Punkt der Einschränkung ist, zu verhindern, dass bei jedem Auftreten doppelte Zeilen auftreten. Es spielt keine Rolle, wie viele Threads einfügen möchten, die Datenbank wird nach Bedarf serialisiert, um die Einschränkung durchzusetzen ... und wenn dies nicht der Fall ist, ist die Engine wertlos. Wenn Sie dies in eine serialisierte Transaktion einschließen, wird dies natürlich korrekter und weniger anfällig für Deadlocks oder fehlgeschlagene Einfügungen.
Triynko

19
@Triynko, ich denke, @Sam Saffron bedeutete, dass der SQL Server einen Fehler auslöst , der darauf hinweist, dass eine Verletzung des Primärschlüssels aufgetreten wäre , wenn zwei + Threads in der richtigen Reihenfolge verschachtelt sind . Das Umschließen in eine serialisierbare Transaktion ist der richtige Weg, um Fehler in den obigen Anweisungen zu vermeiden.
EBarr

1
Selbst wenn Sie einen Primärschlüssel haben, der automatisch inkrementiert wird, sind alle eindeutigen Einschränkungen, die möglicherweise in der Tabelle enthalten sind, Ihr Problem.
Seph

1
Die Datenbank sollte sich um die wichtigsten Probleme kümmern. Was Sie sagen ist, dass wenn die Aktualisierung fehlschlägt und ein anderer Prozess zuerst mit einer Einfügung dort ankommt, Ihre Einfügung fehlschlägt. In diesem Fall haben Sie sowieso eine Rennbedingung. Das Sperren ändert nichts an der Tatsache, dass die Nachbedingung darin besteht, dass einer der Prozesse, die versuchen zu schreiben, den Wert erhält.
CashCow

93

Viele Leute werden vorschlagen, dass Sie verwenden MERGE, aber ich warne Sie davor. Standardmäßig schützt es Sie nicht mehr als mehrere Anweisungen vor Parallelität und Race-Bedingungen und bringt andere Gefahren mit sich:

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table 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
  INSERT ...
END
COMMIT TRANSACTION;

Damit können Sie jedoch sicherstellen, dass Sie die Tabelle möglicherweise zweimal lesen müssen, um die zu aktualisierenden Zeilen zu finden. Im ersten Beispiel müssen Sie die Zeile (n) immer nur einmal suchen. (In beiden Fällen erfolgt eine Einfügung, wenn beim ersten Lesen keine Zeilen gefunden wurden.)

Andere schlagen diesen Weg vor:

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

Dies ist jedoch problematisch, wenn es aus keinem anderen Grund viel teurer ist, SQL Server Ausnahmen abfangen zu lassen, die Sie an erster Stelle hätten verhindern können, außer in dem seltenen Szenario, in dem fast jede Einfügung fehlschlägt. Das beweise ich hier:


3
Was ist mit dem Einfügen / Aktualisieren aus einer Tem-Tabelle, die viele Datensätze einfügt / aktualisiert?
user960567

@ 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);
Aaron Bertrand

4
nett antwortete nach mehr als 2 Jahren :)
user960567

12
@ user960567 Entschuldigung, ich fange nicht immer Kommentare in Echtzeit ab.
Aaron Bertrand

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

Bearbeiten:

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


6
Ich mag dieses immer noch besser. Der Upsert scheint eher eine Nebeneffektprogrammierung zu sein, und ich habe noch nie gesehen, dass die kleinste Cluster-Index-Suche dieser anfänglichen Auswahl Leistungsprobleme in einer realen Datenbank verursacht.
Eric Z Beard

38

Wenn Sie mehr als einen Datensatz gleichzeitig UPSERTIEREN möchten, können Sie die ANSI SQL: 2003 DML-Anweisung MERGE verwenden.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

Lesen Sie die Mimicking MERGE-Anweisung in SQL Server 2005 .


1
In Oracle, eine MERGE - Anweisung I Ausgabe denken sperrt den Tisch. Passiert das auch in SQL * Server?
Mike McAllister

13
MERGE ist anfällig für Rennbedingungen (siehe weblogs.sqlteam.com/dang/archive/2009/01/31/… ), es sei denn, Sie sorgen dafür, dass es Certian-Sperren enthält. Schauen Sie sich auch die Leistung von MERGE in SQL Profiler an ... Ich finde, dass es normalerweise langsamer ist und mehr Lesevorgänge generiert als alternative Lösungen.
EBarr

@EBarr - Danke für den Link auf den Schlössern. Ich habe meine Antwort aktualisiert, um den vorgeschlagenen Sperrhinweis aufzunehmen.
Eric Weilnau


10

Obwohl es ziemlich spät ist, dies zu kommentieren, möchte ich mit MERGE ein vollständigeres Beispiel hinzufügen.

Solche Insert + Update-Anweisungen werden normalerweise als "Upsert" -Anweisungen bezeichnet und können mit MERGE in SQL Server implementiert werden.

Ein sehr gutes Beispiel finden Sie hier: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

Im obigen Abschnitt werden auch Sperr- und Parallelitätsszenarien erläutert.

Ich werde dasselbe als Referenz zitieren:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;

1
Es gibt andere Dinge, über die Sie sich bei MERGE Sorgen machen müssen: mssqltips.com/sqlservertip/3074/…
Aaron Bertrand

8
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

Ersetzen Sie Tabellen- und Feldnamen durch die gewünschten Elemente. Achten Sie auf den EIN- Zustand. Stellen Sie dann den entsprechenden Wert (und Typ) für die Variablen in der Zeile DECLARE ein.

Prost.


7

Sie können MERGEAnweisung verwenden. Diese Anweisung wird verwendet, um Daten einzufügen, wenn sie nicht vorhanden sind, oder um sie zu aktualisieren, wenn sie vorhanden sind.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

@RamenChef Ich verstehe nicht. Wo sind die WHEN MATCHED-Klauseln?
Likejudo

@likejudo Ich habe das nicht geschrieben; Ich habe es nur überarbeitet. Fragen Sie den Benutzer, der den Beitrag geschrieben hat.
RamenChef

5

Wenn Sie das UPDATE ausführen, wenn keine Zeilen aktualisiert wurden, und dann die INSERT-Route, sollten Sie zuerst das INSERT ausführen, um eine Racebedingung zu verhindern (vorausgesetzt, es wird kein dazwischen liegendes DELETE verwendet).

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

Abgesehen von der Vermeidung einer Race-Bedingung führt dies in den meisten Fällen dazu, dass der INSERT fehlschlägt und die CPU verschwendet wird.

Die Verwendung von MERGE ist wahrscheinlich ab SQL2008 vorzuziehen.


Interessante Idee, aber falsche Syntax. Das SELECT benötigt eine FROM <Tabellenquelle> und eine TOP 1 (es sei denn, die ausgewählte Tabellenquelle hat nur 1 Zeile).
jk7

Vielen Dank. Ich habe es in NOT EXISTS geändert. Aufgrund des Tests für "Schlüssel" gemäß O / P wird es immer nur eine übereinstimmende Zeile geben (obwohl dies möglicherweise ein mehrteiliger Schlüssel sein muss :))
Kristen,

4

Das hängt vom Nutzungsmuster ab. Man muss das Gesamtbild der Nutzung betrachten, ohne sich in den Details zu verlieren. Wenn das Verwendungsmuster beispielsweise zu 99% aktualisiert wird, nachdem der Datensatz erstellt wurde, ist "UPSERT" die beste Lösung.

Nach dem ersten Einfügen (Treffer) werden alle einzelnen Anweisungen aktualisiert, ohne Wenn und Aber. Die 'where'-Bedingung für die Einfügung ist erforderlich, da sonst Duplikate eingefügt werden und Sie sich nicht mit dem Sperren befassen möchten.

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END

2

MS SQL Server 2008 führt die MERGE-Anweisung ein, die meines Erachtens Teil des SQL: 2003-Standards ist. Wie viele gezeigt haben, ist es keine große Sache, Fälle mit einer Zeile zu behandeln, aber wenn man sich mit großen Datenmengen befasst, benötigt man einen Cursor mit allen damit verbundenen Leistungsproblemen. Die MERGE-Anweisung wird beim Umgang mit großen Datenmengen sehr begrüßt.


1
Ich musste nie einen Cursor verwenden, um dies mit großen Datenmengen zu tun. Sie benötigen lediglich ein Update, mit dem die übereinstimmenden Datensätze aktualisiert werden, und eine Einfügung mit einer Auswahl anstelle einer Werteklausel, die Links zur Tabelle verknüpft.
HLGEM

1

Bevor alle aus Angst vor diesen nafarious Benutzern, die Ihre Sprocs direkt ausführen, zu HOLDLOCK-s springen :-) Lassen Sie mich darauf hinweisen, dass Sie die Eindeutigkeit neuer PK-s durch Design garantieren müssen (Identitätsschlüssel, Sequenzgeneratoren in Oracle, eindeutige Indizes für externe IDs, Abfragen, die von Indizes abgedeckt werden). Das ist das A und O des Problems. Wenn Sie das nicht haben, werden Sie keine HOLDLOCK-s des Universums retten, und wenn Sie das haben, brauchen Sie bei der ersten Auswahl nichts anderes als UPDLOCK (oder um zuerst das Update zu verwenden).

Sprocs werden normalerweise unter sehr kontrollierten Bedingungen und unter der Annahme eines vertrauenswürdigen Anrufers (Mid Tier) ausgeführt. Das heißt, wenn ein einfaches Upsert-Muster (Aktualisieren + Einfügen oder Zusammenführen) jemals doppelte PK sieht, bedeutet dies einen Fehler in Ihrem Mid-Tier- oder Tabellen-Design und es ist gut, dass SQL in einem solchen Fall einen Fehler schreit und den Datensatz ablehnt. Das Platzieren eines HOLDLOCK ist in diesem Fall gleichbedeutend mit Essensausnahmen und der Aufnahme potenziell fehlerhafter Daten sowie der Reduzierung Ihrer Leistung.

Wenn Sie jedoch MERGE oder UPDATE verwenden, ist INSERT auf Ihrem Server einfacher und weniger fehleranfällig, da Sie nicht daran denken müssen, UPDLOCK hinzuzufügen, um es zuerst auszuwählen. Wenn Sie Einfügungen / Aktualisierungen in kleinen Stapeln durchführen, müssen Sie Ihre Daten kennen, um zu entscheiden, ob eine Transaktion angemessen ist oder nicht. Es ist nur eine Sammlung von nicht verwandten Aufzeichnungen, dann wird eine zusätzliche "Umhüllung" der Transaktion nachteilig sein.


1
Wenn Sie nur ein Update durchführen und dann ohne Sperren oder erhöhte Isolation einfügen, könnten zwei Benutzer versuchen, dieselben Daten zurückzugeben (ich würde es nicht als Fehler in der mittleren Ebene betrachten, wenn zwei Benutzer versuchen würden, genau dieselben Informationen an zu senden zur gleichen Zeit - hängt sehr vom Kontext ab, nicht wahr?). Beide geben das Update ein, das für beide 0 Zeilen zurückgibt, und versuchen dann beide, es einzufügen. Einer gewinnt, der andere bekommt eine Ausnahme. Dies ist, was die Leute normalerweise zu vermeiden versuchen.
Aaron Bertrand

1

Sind die Rennbedingungen wirklich wichtig, wenn Sie zuerst ein Update gefolgt von einer Einfügung versuchen? Lassen Sie uns sagen Sie zwei Threads haben , die einen Wert für Schlüssel festlegen möchten Schlüssel :

Thread 1: Wert = 1
Thread 2: Wert = 2

Beispiel für ein Szenario mit Rennbedingungen

  1. Schlüssel ist nicht definiert
  2. Thread 1 schlägt mit dem Update fehl
  3. Thread 2 schlägt mit dem Update fehl
  4. Genau einer von Thread 1 oder Thread 2 ist mit dem Einfügen erfolgreich. ZB Thread 1
  5. Der andere Thread schlägt mit Einfügen fehl (mit Fehler-Duplikatschlüssel) - Thread 2.

    • Ergebnis: Das "erste" der beiden einzufügenden Laufflächen entscheidet über den Wert.
    • Gewünschtes Ergebnis: Der letzte der 2 Threads, die Daten schreiben (aktualisieren oder einfügen), sollte den Wert bestimmen

Aber; In einer Multithread-Umgebung entscheidet der OS-Scheduler über die Reihenfolge der Thread-Ausführung. Im obigen Szenario, in dem diese Race-Bedingung vorliegt, hat das Betriebssystem über die Reihenfolge der Ausführung entschieden. Dh: Es ist falsch zu sagen, dass "Thread 1" oder "Thread 2" aus Systemsicht "zuerst" war.

Wenn die Ausführungszeit für Thread 1 und Thread 2 so kurz ist, spielt das Ergebnis der Race-Bedingung keine Rolle. Die einzige Anforderung sollte sein, dass einer der Threads den resultierenden Wert definiert.

Für die Implementierung: Wenn eine Aktualisierung gefolgt von Einfügen zu dem Fehler "Schlüssel duplizieren" führt, sollte dies als Erfolg behandelt werden.

Außerdem sollte man natürlich niemals davon ausgehen, dass der Wert in der Datenbank mit dem Wert übereinstimmt, den Sie zuletzt geschrieben haben.


1

In SQL Server 2008 können Sie die MERGE-Anweisung verwenden


11
Dies ist ein Kommentar. In Ermangelung eines tatsächlichen Beispielcodes ist dies genau wie bei vielen anderen Kommentaren auf der Site.
Swasheck

Sehr alt, aber ein Beispiel wäre schön.
Matt McCabe

0

Ich hatte unten die Lösung ausprobiert und es funktioniert für mich, wenn gleichzeitig eine Anforderung zum Einfügen einer Anweisung auftritt.

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

0

Sie können diese Abfrage verwenden. Arbeiten Sie in allen SQL Server-Editionen. Es ist einfach und klar. Sie müssen jedoch 2 Abfragen verwenden. Sie können verwenden, wenn Sie MERGE nicht verwenden können

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

HINWEIS: Bitte erläutern Sie die Antwortnegative


Ich vermute, dass es an Sperren mangelt?
Zeek2

Kein Mangel an Sperren ... Ich benutze "TRAN". Standard-SQL Server-Transaktionen sind gesperrt.
Victor Sanchez

-2

Wenn Sie ADO.NET verwenden, übernimmt der DataAdapter dies.

Wenn Sie es selbst erledigen möchten, ist dies der Weg:

Stellen Sie sicher, dass Ihre Schlüsselspalte eine Primärschlüsseleinschränkung enthält.

Dann Sie:

  1. Mach das Update
  2. Wenn die Aktualisierung fehlschlägt, weil bereits ein Datensatz mit dem Schlüssel vorhanden ist, führen Sie die Einfügung durch. Wenn das Update nicht fehlschlägt, sind Sie fertig.

Sie können es auch umgekehrt machen, dh zuerst die Einfügung durchführen und die Aktualisierung durchführen, wenn die Einfügung fehlschlägt. Normalerweise ist der erste Weg besser, da Aktualisierungen häufiger durchgeführt werden als Einfügungen.


... und das Einfügen zuerst (in dem Wissen, dass es manchmal fehlschlägt) ist für SQL Server teuer. sqlperformance.com/2012/08/t-sql-queries/error-handling
Aaron Bertrand

-3

Wenn ein if vorhanden ist ... else ... müssen mindestens zwei Anforderungen ausgeführt werden (eine zum Überprüfen, eine zum Ergreifen von Maßnahmen). Der folgende Ansatz erfordert nur einen, wenn der Datensatz vorhanden ist, zwei, wenn eine Einfügung erforderlich ist:

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')

-3

Normalerweise mache ich das, was einige der anderen Poster gesagt haben, um zuerst zu überprüfen, ob es vorhanden ist, und dann, was auch immer der richtige Weg ist. Eine Sache, die Sie dabei beachten sollten, ist, dass der von SQL zwischengespeicherte Ausführungsplan für den einen oder anderen Pfad möglicherweise nicht optimal ist. Ich glaube, der beste Weg, dies zu tun, besteht darin, zwei verschiedene gespeicherte Prozeduren aufzurufen.

FirstSP:
Wenn vorhanden
   Rufen Sie SecondSP (UpdateProc) auf.
Sonst
   Rufen Sie ThirdSP (InsertProc) auf.

Jetzt folge ich meinen eigenen Rat nicht sehr oft, also nimm es mit einem Körnchen Salz.


Dies mag in alten Versionen von SQL Server relevant gewesen sein, aber moderne Versionen verfügen über eine Kompilierung auf Anweisungsebene. Gabeln usw. sind kein Problem, und die Verwendung separater Verfahren für diese Dinge löst keines der Probleme, die mit der Wahl zwischen einem Update und einer Einfügung verbunden sind ...
Aaron Bertrand,

-10

Wählen Sie aus, wenn Sie ein Ergebnis erhalten, aktualisieren Sie es. Wenn nicht, erstellen Sie es.


3
Das sind zwei Aufrufe der Datenbank.
Chris Cudmore

3
Ich sehe kein Problem damit.
Clint Ecker

10
Es sind zwei Aufrufe an die DB, die das Problem darstellen. Am Ende verdoppeln Sie die Anzahl der Roundtrips zur DB. Wenn die App die Datenbank mit vielen Einfügungen / Updates erreicht, wird die Leistung beeinträchtigt. UPSERT ist eine bessere Strategie.
Kev

5
es schafft auch eine Rennbedingung nein?
Niico
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.