Frage

Gehen Sie von einer Tabellenstruktur aus 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

Wie schreibt man das am besten?

War es hilfreich?

Lösung

vergessen Sie nicht über Transaktionen. Die Leistung ist gut, aber einfach (wenn vorhanden ..) Ansatz ist sehr gefährlich.
Wenn mehrere Threads versuchen, Insert-oder-Update durchführen können Sie einfach erhalten primäre Schlüsselverletzung.

Lösungen zur Verfügung gestellt von @Beau Crawford & @Esteban zeigen allgemeine Idee, aber fehleranfällig.

Zur Vermeidung von Deadlocks und PK Verletzungen Sie so etwas wie diese verwenden können:

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

Andere Tipps

Sehen Sie mein detaillierte Antwort auf eine sehr ähnliche vorherige Frage

@Beau Crawford ist eine gute Möglichkeit in SQL 2005 und unten, aber wenn Sie rep sind Gewährung gehen sollte zum ersten Mann zu SO es . Das einzige Problem ist, dass es nach wie vor zwei IO-Operationen für Einsätze.

MS SQL2008 führt merge vom 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 ein IO-Betrieb, aber schrecklich Code: - (

Führen Sie eine UPSERT:

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

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

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

Viele Leute werden Ihnen die Verwendung empfehlen MERGE, aber ich warne Sie davor.Standardmäßig schützt es Sie nicht mehr vor Parallelität und Race-Bedingungen als mehrere Anweisungen, birgt jedoch andere Gefahren:

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

Auch wenn diese „einfachere“ Syntax verfügbar ist, bevorzuge ich immer noch diesen Ansatz (der Kürze halber wurde die Fehlerbehandlung 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;

Dadurch wird jedoch lediglich sichergestellt, dass Sie die Tabelle möglicherweise zweimal lesen müssen, um die zu aktualisierende(n) Zeile(n) 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 Lesevorgang keine Zeilen gefunden wurden.)

Andere werden diesen Weg vorschlagen:

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

Dies ist jedoch problematisch, wenn es aus keinem anderen Grund, als das Abfangen von Ausnahmen durch SQL Server zuzulassen, viel teurer ist, außer in dem seltenen Fall, dass fast jede Einfügung fehlschlägt.Das beweise ich hier:

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

Edit:

Ach, auch zu meinem eigenen Schaden, muss ich die Lösungen zugeben, die dies tun, ohne eine ausgewählte scheinen besser zu sein, da sie die Aufgabe mit einem Schritt weniger erreichen.

Wenn Sie mehr als einen Datensatz zu einem Zeitpunkt UPSERT möchten, können Sie die ANSI-SQL verwenden. 2003 und DML-Anweisung MERGE

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 ...])

Schauen Sie sich Imitieren MERGE-Anweisung in SQL Server 2005 .

Obwohl seine ziemlich spät zu kommentieren, dies ich ein vollständigeres Beispiel unter Verwendung von MERGE hinzufügen möchten.

Eine solche Insert + Update-Anweisungen werden in der Regel "Upsert" Aussagen bezeichnet und kann unter Verwendung von MERGE in SQL Server implementiert werden.

Ein sehr gutes Beispiel ist hier gegeben: http: //weblogs.sqlteam .com / dingt / Archiv / 2009/01/31 / UPSERT-Rennen-Condition-With-MERGE.aspx

Das oben erklärt Sperren und Concurrency-Szenarien als auch.

ich sein wird, die gleiche Referenz zitiert:

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;
/*
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 Tabellen- und Feldnamen durch alles, was Sie brauchen. Achten Sie auf die mit ON Zustand. Setzen Sie dann den entsprechenden Wert (und Typen) für die Variablen auf der DECLARE Linie.

Prost.

Sie können MERGE Statement verwenden, wird diese Anweisung wird Daten einfügen, wenn nicht vorhanden oder zu aktualisieren, falls nicht vorhanden ist.

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

Wenn geht das UPDATE wenn-no-Zeilen-aktualisiert dann Route INSERT, sollten Sie die INSERT tun zunächst eine Race-Bedingung zu verhindern (vorausgesetzt, keine dazwischen DELETE)

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

Neben der Race-Bedingung zu vermeiden, wenn in den meisten Fällen der Datensatz bereits vorhanden sein wird, dann wird dies die INSERT fehlschlagen, verschwendet CPU.

Mit MERGE wahrscheinlich bevorzugt SQL2008 ab.

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

Das hängt vom Nutzungsmuster. Man hat bei der Benutzung großen Bild zu sehen, ohne in den Details zu verlieren. wenn die Nutzungsmuster von 99% Updates Zum Beispiel ist nach der Datensatz erstellt wurde, dann ist die ‚UPSERT‘ ist die beste Lösung.

Nach dem ersten Einsatz (Hit), wird es all einzelne Anweisung Updates, ohne Wenn und Aber sein. Der ‚wo‘ Zustand an dem Einsatz ist notwendig, sonst wird es Duplikate einfügen, und Sie wollen nicht mit Sperren umgehen.

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

MS SQL Server 2008 führt die MERGE-Anweisung, die ich glaube, einen Teil des SQL: 2003-Standard. Wie viele haben gezeigt, keine große Sache ist eine Reihe Fälle zu behandeln, aber wenn sie mit großen Datenmengen zu tun, muss man einen Cursor, mit allen Performance-Problemen, die zusammen kommen. Die MERGE-Anweisung wird viel zusätzlich zu begrüßen, wenn sie mit großen Datenmengen zu tun hat.

Vor jeder springt aus Angst aus diesen nafarious Nutzer holdlock-s Ihre sprocs läuft direkt :-) lassen Sie mich darauf hinweisen, dass Sie haben durch Design garantieren Einzigartigkeit neuer PK-s (Identität Schlüssel, Sequenzgeneratoren in Oracle, eindeutiger Indizes für externe ID-s, abgedeckt Abfragen durch Indizes). Das ist das A und O des Problems. Wenn Sie das nicht haben, keine HOLDLOCK-s des Universums gehen Sie zu sparen und wenn Sie das tun haben, dann brauchen Sie nichts über UPDLOCK auf das erste Auswahl (oder Update verwenden zuerst).

Sprocs normalerweise unter sehr kontrollierten Bedingungen durchgeführt und unter der Annahme eines vertrauenswürdigen Anrufer (Mid-Tier). Was bedeutet, dass, wenn ein einfaches Upsert Muster (Update + einfügen oder fusionieren) jemals duplizieren sieht PK, die einen Fehler in Ihrem Mid-Tier-oder Tisch-Design bedeutet, und es ist gut, dass SQL einen Fehler in einem solchen Fall schreien wird und lehnen den Rekord. Platzieren einen HOLDLOCK in diesem Fall ist gleich Ausnahmen zu essen und in potentiell fehlerhaften Daten nimmt, neben Ihrem perf zu reduzieren.

Having said that, MERGE, oder UPDATE dann INSERT ist einfacher, auf dem Server und weniger fehleranfällig, da müssen Sie sich nicht erinnern, wählen Sie hinzufügen (UPDLOCK) zum ersten. Auch, wenn Sie Einsätze tun / Updates in kleinen Chargen müssen Sie Ihre Daten, um zu wissen, um zu entscheiden, ob eine Transaktion angemessen ist oder nicht. Es es ist nur eine Sammlung von nicht verwandten Aufzeichnungen dann zusätzliche „umhüllende“ Transaktion schädlich sein wird.

Ist egal, die Rennbedingungen wirklich, wenn Sie zum ersten Mal von einem Einsatz folgte ein Update versuchen? Lassen Sie uns sagen Sie zwei Threads haben, die einen Wert für die Taste Taste setzen wollen:

Thread 1: value = 1 | Thema 2: Wert = 2

Beispiel Racebedingung Szenario

  1. Taste ist nicht definiert
  2. Thread 1 nicht mit update
  3. Thread 2 schlägt mit update
  4. genau ein Faden 1 oder Gewinde 2 gelingt es mit Einsatz. Z.B. Faden 1
  5. Der andere Thread nicht mit Einsatz (mit Fehlern doppelten Schlüsseln.) - Gewinde 2

    • . Ergebnis: Die „erste“ der beiden Stufen einzusetzen, entscheidet Wert
    • Gesucht Ergebnis: Die letzte der zwei Threads Daten (Update oder Insert) zu schreiben, sollte Wert entscheiden

Aber; in einer Multithread-Umgebung entscheiden die OS-Scheduler auf der Reihenfolge der Thread-Ausführung - in dem obigen Szenario, in dem wir diese Racebedingung haben, war es das Betriebssystem, das auf der Reihenfolge der Ausführung entschieden. D. h. Es ist falsch zu sagen, dass „Thema 1“ oder „Faden 2“ war „erster“ aus Systemsicht

Wenn die Zeit der Ausführung für Gewinde 1 und Faden 2 so nahe ist, wird der Ausgang des Rennens Zustand keine Rolle. Die einzige Voraussetzung sollte sein, dass einer der Threads sollte den resultierenden Wert definieren.

Für die Implementierung: Wenn Update durch Einsatz Ergebnisse in Fehler „doppelten Schlüssel“ gefolgt, sollte dies als Erfolg behandelt werden.

Auch ein natürlich niemals diesen Wert in der Datenbank übernehmen ist der gleiche wie der Wert, den Sie schrieb zuletzt.

Ich hatte unter Lösung ausprobiert und es funktioniert für mich, wenn gleichzeitige Anforderungen für Insert-Anweisung auftreten.

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

Sie können diese Abfrage verwenden. Arbeitet in allen Editionen von SQL Server. Es ist einfach und klar. Aber Sie müssen zwei Abfragen verwenden. Sie können, wenn Sie nicht MERGE verwenden

    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 Antwort Negativ

Wenn Sie ADO.NET verwenden, die Dataadapter diese behandelt.

Wenn Sie es selbst behandeln möchten, ist dies die Art und Weise:

Stellen Sie sicher, dass es eine Primärschlüsselbedingung auf Schlüsselspalte ist.

Dann Sie:

  1. Sie das Update
  2. Wenn das Update fehlschlägt, weil ein Datensatz mit dem Schlüssel bereits vorhanden ist, macht den Einsatz. Wenn das Update nicht fehlschlagen, sind Sie fertig.

Sie können es auch tun, umgekehrt, das heißt tun, den Einsatz zuerst, und das Update, wenn der Einsatz nicht. Normalerweise ist der erste Weg ist besser, weil Updates häufiger als Einsätze fertig ist.

Doing ein, wenn existiert ... else ... beinhaltet dabei zwei Anfragen Minimum (ein zu prüfen, eine Aktion zu nehmen). Der folgende Ansatz erfordert nur eine, wo der Datensatz vorhanden, zwei, wenn ein Einsatz erforderlich:

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

ich in der Regel tun, was einige der anderen Plakate mit Bezug gesagt zu prüft es zunächst bestehenden und dann tun, was der richtige Weg ist. Eine Sache, die Sie sollten daran denken, wenn dies zu tun, ist, dass der Ausführungsplan von SQL-Cache gespeicherten für einen Weg oder das andere nicht optimal sein könnte. Ich glaube, der beste Weg, dies zu tun ist, zwei verschiedene gespeicherte Prozeduren aufrufen.

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

Nun, ich meine eigenen Rat nicht folgen sehr oft, so nehmen Sie es mit einem Körnchen Salz.

Starten Sie eine Auswahl, wenn Sie ein Ergebnis bekommen, aktualisieren Sie es, wenn nicht, erstellen Sie es.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top