Rozwiązania dla INSERT OR UPDATE na SQL Server

597

Załóżmy strukturę tabeli MyTable(KEY, datafield1, datafield2...).

Często chcę zaktualizować istniejący rekord lub wstawić nowy, jeśli nie istnieje.

Głównie:

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

Jak najlepiej to napisać?

Chris Cudmore
źródło
26
Wszystkim, którzy napotkają to pytanie po raz pierwszy - przeczytaj wszystkie odpowiedzi i ich komentarze. Wiek może czasem prowadzić do wprowadzających w błąd informacji ...
Aaron Bertrand
1
Zastanów się nad użyciem operatora EXCEPT, który został wprowadzony w SQL Server 2005.
Tarzan

Odpowiedzi:

370

nie zapomnij o transakcjach. Wydajność jest dobra, ale proste (JEŚLI ISTNIEJE) podejście jest bardzo niebezpieczne.
Gdy wiele wątków będzie próbowało wykonać wstawianie lub aktualizację, możesz łatwo uzyskać naruszenie klucza podstawowego.

Rozwiązania dostarczone przez @Beau Crawford i @Esteban pokazują ogólny pomysł, ale są podatne na błędy.

Aby uniknąć zakleszczeń i naruszeń PK, możesz użyć czegoś takiego:

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

lub

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

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran
aku
źródło
Pytanie zadaje najbardziej wydajne rozwiązanie, a nie najbezpieczniejsze. Podczas gdy transakcja zwiększa bezpieczeństwo procesu, powoduje również dodatkowe koszty.
Luke Bennett,
31
Obie te metody mogą nadal zawieść. Jeśli dwa współbieżne wątki robią to samo w tym samym wierszu, pierwszy odniesie sukces, ale drugi wkład nie powiedzie się z powodu naruszenia klucza podstawowego. Transakcja nie gwarantuje, że wstawienie się powiedzie, nawet jeśli aktualizacja się nie powiedzie, ponieważ rekord istnieje. Aby zagwarantować, że dowolna liczba jednoczesnych transakcji się powiedzie, MUSISZ użyć blokady.
Jean Vincent
7
@aku z jakiegokolwiek powodu, w którym użyłeś podpowiedzi do tabeli („z (xxxx)”) w przeciwieństwie do „USTAWIĆ POZIOM IZOLACJI TRANSAKCJI SERYJNIE” tuż przed ROZPOCZĘCIEM TRANZY?
EBarr
4
@CashCow, ostatnie wygrane, to właśnie ma zrobić INSERT lub UPDATE: pierwszy wstawia, drugi aktualizuje rekord. Dodanie blokady pozwala na to w bardzo krótkim czasie, co zapobiega błędom.
Jean Vincent
1
Zawsze uważałem, że użycie wskazówek blokujących jest złe i powinniśmy pozwolić, aby wewnętrzny silnik Microsoft dyktował blokady. Czy to pozorny wyjątek od reguły?
381

Zobacz moją szczegółową odpowiedź na bardzo podobne poprzednie pytanie

@Beau Crawford's jest dobrym sposobem w SQL 2005 i niższych, ale jeśli udzielasz rep, powinien przejść do pierwszego faceta, który go SO . Jedynym problemem jest to, że dla wstawek są to nadal dwie operacje IO.

MS Sql2008 wprowadza mergeze standardu SQL: 2003:

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

Teraz to naprawdę tylko jedna operacja IO, ale okropny kod :-(

Keith
źródło
10
@Ian Boyd - tak, to jest składnia standardu SQL: 2003, a nie upsertże wszyscy inni dostawcy DB zdecydowali się na wsparcie. upsertSkładnia jest o wiele ładniejszy sposób, aby to zrobić, więc w najgorszym MS powinien wspierać go zbyt - to nie jest tak, że to tylko niestandardowe kluczowe w T-SQL
Keith
1
jakiś komentarz na temat wskazówki blokady w innych odpowiedziach? (dowie się wkrótce, ale jeśli jest to zalecany sposób, polecam dodanie go do odpowiedzi)
eglasius
25
Zobacz tutaj weblogs.sqlteam.com/dang/archive/2009/01/31/..., aby dowiedzieć się, jak zapobiegać powodowaniu błędów przez rasy podczas błędów, które mogą wystąpić nawet przy użyciu MERGEskładni.
Seph
5
@Seph to prawdziwa niespodzianka - poniekąd porażka Microsoftu: - Zgadnij, co oznacza, że ​​potrzebujesz HOLDLOCKoperacji scalania w sytuacjach o wysokiej współbieżności.
Keith,
11
Ta odpowiedź naprawdę wymaga aktualizacji, aby uwzględnić komentarz Seph, że nie jest bezpieczny w wątkach bez HOLDLOCK. Zgodnie z połączonym postem MERGE domyślnie usuwa blokadę aktualizacji, ale zwalnia ją przed wstawieniem wierszy, co może powodować warunki wyścigu i naruszenia klucza podstawowego podczas wstawiania. Przy użyciu HOLDLOCK blokady są utrzymywane do momentu pojawienia się wstawki.
Triynko,
169

Wykonaj UPSERT:

AKTUALIZACJA MyTable SET FieldA = @ FieldA WHERE Key = @ Key

IF @@ ROWCOUNT = 0
   WSTAWIĆ DO WARTOŚCI MyTable (FieldA) (@FieldA)

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

Beau Crawford
źródło
7
Naruszenie klucza podstawowego nie powinno wystąpić, jeśli zastosowano odpowiednie unikalne ograniczenia indeksu. Cały sens ograniczenia polega na zapobieganiu powielaniu wierszy przed każdym zdarzeniem. Nie ma znaczenia, ile wątków próbuje wstawić, baza danych serializuje w razie potrzeby, aby wymusić ograniczenie ... a jeśli nie, silnik jest bezwartościowy. Oczywiście zawinięcie tego w serializowaną transakcję sprawi, że będzie to bardziej poprawne i mniej podatne na zakleszczenia lub nieudane wstawienia.
Triynko
19
@Triynko, myślę @Sam Saffron oznaczało, że jeśli dwa wątki przeplatają + w odpowiedniej kolejności, a następnie serwer SQL będzie rzucać się błąd wskazujący naruszenie klucza podstawowego byłby miejsce. Zawijanie go w transakcję możliwą do serializacji jest prawidłowym sposobem uniknięcia błędów w powyższym zestawie instrukcji.
EBarr
1
Nawet jeśli masz klucz główny, który jest autodeterminacją, twoimi obawami będą wszelkie unikalne ograniczenia, które mogą występować na stole.
Seph
1
baza danych powinna zająć się podstawowymi zagadnieniami kluczowymi. Mówisz, że jeśli aktualizacja się nie powiedzie, a pierwszy proces dojdzie do tego z wkładką, wstawienie się nie powiedzie. W takim razie i tak masz wyścig. Blokowanie nie zmieni faktu, że warunek końcowy będzie taki, że jeden z procesów, który próbuje pisać, uzyska wartość.
CashCow
93

Wiele osób zasugeruje ci użycie MERGE, ale ostrzegam cię przed tym. Domyślnie nie chroni cię przed współbieżnością i warunkami wyścigowymi więcej niż wiele oświadczeń, ale wprowadza inne niebezpieczeństwa:

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

Nawet przy tej dostępnej „prostszej” składni nadal wolę to podejście (pominięcie obsługi błędów ze względu na zwięzłość):

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;

Wielu ludzi zasugeruje w ten sposób:

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;

Ale to wszystko zapewnia, że ​​konieczne może być dwukrotne przeczytanie tabeli w celu zlokalizowania wierszy, które mają zostać zaktualizowane. W pierwszej próbce będziesz musiał tylko zlokalizować rząd (wiersze) tylko raz. (W obu przypadkach, jeśli nie zostaną znalezione wiersze z początkowego odczytu, nastąpi wstawienie).

Inni sugerują w ten sposób:

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

Jest to jednak problematyczne, jeśli tylko z innego powodu niż zezwolenie SQL Serverowi na wychwytywanie wyjątków, którym można było zapobiec, jest znacznie droższe, z wyjątkiem rzadkiego scenariusza, w którym prawie każda wstawka ulega awarii. Udowadniam tutaj tyle samo:

Aaron Bertrand
źródło
3
Co z wstawieniem / aktualizacją z tabeli tem, która wstawia / aktualizuje wiele rekordów?
user960567
@ user960567 Cóż,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
miło odpowiedział po ponad 2 latach :)
user960567
12
@ user960567 Niestety, nie zawsze łapię powiadomienia o komentarzach w czasie rzeczywistym.
Aaron Bertrand
60
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Edytować:

Niestety, nawet na własną szkodę, muszę przyznać, że rozwiązania, które dokonują tego bez wyboru, wydają się lepsze, ponieważ wykonują zadanie o jeden krok mniej.

Esteban Araya
źródło
6
Nadal lubię ten bardziej. Upsert wydaje się bardziej przypominać programowanie efektem ubocznym, a nigdy nie widziałem, żeby to mizerne wyszukiwanie indeksu klastrowego tej początkowej selekcji powodowało problemy z wydajnością w prawdziwej bazie danych.
Eric Z Beard,
38

Jeśli chcesz przesłać więcej niż jeden rekord na raz, możesz użyć instrukcji ANSI SQL: 2003 DML 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 ...])

Sprawdź naśladowanie instrukcji MERGE w SQL Server 2005 .

Eric Weilnau
źródło
1
Wydaje mi się, że w Oracle wydawanie instrukcji MERGE blokuje tabelę. Czy to samo dzieje się w SQL * Server?
Mike McAllister,
13
MERGE jest podatny na warunki wyścigu (patrz weblogs.sqlteam.com/dang/archive/2009/01/31/... ), chyba że sprawisz, że zablokuje on certyfikaty Certian . Spójrz także na wydajność MERGE w SQL Profiler ... uważam, że jest on zwykle wolniejszy i generuje więcej odczytów niż alternatywne rozwiązania.
EBarr
@EBarr - Dzięki za link na blokadach. Zaktualizowałem swoją odpowiedź, aby zawierała sugerowaną wskazówkę dotyczącą blokowania.
Eric Weilnau,
Sprawdź także mssqltips.com/sqlservertip/3074/…
Aaron Bertrand
10

Chociaż jest dość późno, aby to skomentować, chcę dodać bardziej kompletny przykład za pomocą MERGE.

Takie instrukcje Insert + Update są zwykle nazywane instrukcjami „Upsert” i można je zaimplementować za pomocą funkcji MERGE w programie SQL Server.

Bardzo dobry przykład podano tutaj: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

Powyżej wyjaśnia również scenariusze blokowania i współbieżności.

Będę cytować to samo w celach informacyjnych:

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;
użytkownik243131
źródło
1
Z MERGE są jeszcze inne rzeczy do zmartwienia: 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

Zamień nazwy tabel i pól na dowolne potrzebne. Dbać o stosowania na stanie. Następnie ustaw odpowiednią wartość (i typ) dla zmiennych w wierszu DECLARE.

Twoje zdrowie.

Denver
źródło
7

Można użyć MERGEinstrukcji, instrukcja służy do wstawiania danych, jeśli nie istnieje, lub aktualizacji, jeśli istnieje.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
Daniel Acosta
źródło
@RamenChef Nie rozumiem. Gdzie są klauzule WHEN MATCHED?
likejudo
@likejudo Nie napisałem tego; Tylko to poprawiłem. Zapytaj użytkownika, który napisał post.
RamenChef
5

W przypadku aktualizacji UPDATE, jeśli nie zaktualizowano wierszy, a następnie INSERT, należy najpierw rozważyć wprowadzenie instrukcji INSERT, aby zapobiec warunkom wyścigu (zakładając, że nie ma potrzeby usuwania USUŃ)

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

Oprócz uniknięcia wyścigu, jeśli w większości przypadków rekord już istnieje, spowoduje to awarię INSERT, marnując procesor.

Używanie MERGE prawdopodobnie jest lepsze dla SQL2008 i późniejszych.

Kristen
źródło
Ciekawy pomysł, ale niepoprawna składnia. SELECT potrzebuje FROM <źródło_tabeli> i TOP 1 (chyba że wybrane źródło_tabeli ma tylko 1 wiersz).
jk7
Dzięki. Zmieniłem to na NIE ISTNIEJE. Zawsze będzie tylko jeden pasujący wiersz z powodu testu „klucza” według O / P (chociaż może to wymagać klucza wieloczęściowego :))
Kristen
4

To zależy od wzorca użytkowania. Trzeba patrzeć na duży obraz użytkowania bez zagubienia się w szczegółach. Na przykład, jeśli wzorzec użycia to 99% aktualizacji po utworzeniu rekordu, najlepszym rozwiązaniem jest „UPSERT”.

Po pierwszym wstawieniu (trafieniu) będą to wszystkie aktualizacje pojedynczej instrukcji, bez ifs lub ale. Warunek „gdzie” na wkładce jest konieczny, w przeciwnym razie wstawi duplikaty i nie chcesz zajmować się blokowaniem.

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
Saleh Najar
źródło
2

MS SQL Server 2008 wprowadza instrukcję MERGE, która moim zdaniem jest częścią standardu SQL: 2003. Jak wielu pokazało, nie jest wielką sprawą zajmowanie się przypadkami z jednym wierszem, ale w przypadku dużych zestawów danych potrzebny jest kursor wraz ze wszystkimi problemami z wydajnością. Instrukcja MERGE będzie mile widzianym dodatkiem w przypadku dużych zestawów danych.

bjorsig
źródło
1
Nigdy nie potrzebowałem używać kursora, aby robić to z dużymi zestawami danych. Potrzebujesz tylko aktualizacji, która aktualizuje pasujące rekordy, i wstawki z klauzulą ​​select zamiast wartości, która pozostawia połączenia do tabeli.
HLGEM
1

Zanim wszyscy przejdą do HOLDLOCK-ów ze strachu przed tymi okropnymi użytkownikami, którzy bezpośrednio uruchamiają twoje sproki :-) pozwól mi zaznaczyć, że musisz zagwarantować unikalność nowych PK-ów według projektu (klucze tożsamości, generatory sekwencji w Oracle, unikalne indeksy dla zewnętrzne identyfikatory, zapytania objęte indeksami). To jest alfa i omega problemu. Jeśli tego nie masz, żadne HOLDLOCK-y wszechświata cię nie uratują, a jeśli tak, to nie potrzebujesz niczego poza UPDLOCK przy pierwszym wyborze (lub najpierw użyj aktualizacji).

Sprocs zwykle działają w ściśle kontrolowanych warunkach i przy założeniu, że osoba dzwoniąca jest zaufana (poziom środkowy). Oznacza to, że jeśli prosty wzorzec wstawiania (aktualizacja + wstawianie lub scalanie) kiedykolwiek zobaczy duplikat PK, oznacza to błąd w projekcie warstwy pośredniej lub tabeli i dobrze, że SQL wykrzyknie błąd w takim przypadku i odrzuci rekord. Umieszczenie HOLDLOCK w tym przypadku jest równoznaczne z jedzeniem wyjątków i przyjmowaniem potencjalnie błędnych danych, oprócz zmniejszenia wydajności.

Powiedziawszy to, używając MERGE lub UPDATE, INSERT jest łatwiejszy na twoim serwerze i mniej podatny na błędy, ponieważ nie musisz pamiętać, aby dodać (UPDLOCK), aby najpierw wybrać. Ponadto, jeśli robisz wstawki / aktualizacje w małych partiach, musisz znać swoje dane, aby zdecydować, czy transakcja jest odpowiednia, czy nie. Jest to tylko zbiór niepowiązanych zapisów, a wówczas dodatkowa transakcja „otaczania” będzie szkodliwa.

ZXX
źródło
1
Jeśli po prostu zrobisz aktualizację, a następnie wstawisz bez żadnej blokady lub podwyższonej izolacji, wtedy dwóch użytkowników może spróbować przekazać te same dane z powrotem (nie uważam tego za błąd na środkowej warstwie, jeśli dwóch użytkowników spróbuje przesłać te same informacje na jednocześnie - zależy bardzo od kontekstu, prawda?). Obaj wprowadzają aktualizację, która zwraca 0 wierszy dla obu, a następnie próbują wstawić. Jeden wygrywa, drugi otrzymuje wyjątek. Tego zwykle ludzie starają się uniknąć.
Aaron Bertrand
1

Czy warunki wyścigu naprawdę mają znaczenie, jeśli najpierw spróbujesz aktualizacji, a następnie wstawki? Powiedzmy, że masz dwa wątki, które chcą ustawić wartość dla klucza klucza :

Wątek 1: wartość = 1
Wątek 2: wartość = 2

Przykładowy scenariusz wyścigu

  1. klucz nie jest zdefiniowany
  2. Aktualizacja 1 nie powiedzie się
  3. Aktualizacja 2 nie powiedzie się
  4. Dokładnie jeden z wątku 1 lub wątku 2 kończy się powodzeniem. Np. Wątek 1
  5. Drugi wątek kończy się niepowodzeniem przy wstawianiu (z błędnym duplikatem klucza) - wątek 2.

    • Wynik: „Pierwszy” z dwóch stopni do wstawienia decyduje o wartości.
    • Pożądany wynik: ostatni z 2 wątków do zapisu danych (aktualizacja lub wstawienie) powinien zdecydować o wartości

Ale; w środowisku wielowątkowym harmonogram systemu operacyjnego decyduje o kolejności wykonania wątku - w powyższym scenariuszu, w którym mamy ten warunek wyścigu, to system operacyjny zdecydował o kolejności wykonania. Tzn .: Błędem jest twierdzenie, że „wątek 1” lub „wątek 2” był „pierwszy” z systemowego punktu widzenia.

Kiedy czas wykonania jest tak blisko dla wątku 1 i wątku 2, wynik warunków wyścigu nie ma znaczenia. Jedynym wymaganiem powinno być, aby jeden z wątków zdefiniował wynikową wartość.

Do wdrożenia: jeśli aktualizacja, po której następuje wstawienie, powoduje błąd „duplikowanie klucza”, należy to traktować jako sukces.

Oczywiście nie należy oczywiście zakładać, że wartość w bazie danych jest taka sama, jak wartość, którą zapisałeś jako ostatni.

runec
źródło
1

W SQL Server 2008 można użyć instrukcji MERGE

Bart
źródło
11
to jest komentarz. przy braku rzeczywistego przykładowego kodu jest to tak jak wiele innych komentarzy na stronie.
swasheck
Bardzo stary, ale przykład byłby miły.
Matt McCabe,
0

Wypróbowałem poniższe rozwiązanie i działa ono dla mnie, gdy pojawia się współbieżne żądanie instrukcji insert.

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
Dev
źródło
0

Możesz użyć tego zapytania. Praca we wszystkich wersjach SQL Server. To proste i jasne. Ale potrzebujesz użyć 2 zapytań. Możesz użyć, jeśli nie możesz użyć MERGE

    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

UWAGA: Proszę wyjaśnić odpowiedzi przeczące

Victor Sanchez
źródło
Zgaduję brak blokady?
Zeek2
Nie brakuje blokady ... Używam „TRAN”. Domyślne transakcje serwera SQL mają blokadę.
Victor Sanchez,
-2

Jeśli używasz ADO.NET, DataAdapter obsługuje to.

Jeśli chcesz sobie z tym poradzić, jest to następujący sposób:

Upewnij się, że kolumna klucza zawiera ograniczenie klucza podstawowego.

Więc ty:

  1. Wykonaj aktualizację
  2. Jeśli aktualizacja się nie powiedzie, ponieważ rekord z kluczem już istnieje, wykonaj operację wstawiania. Jeśli aktualizacja się nie powiedzie, jesteś skończony.

Możesz to również zrobić w drugą stronę, tj. Najpierw wstawić i wykonać aktualizację, jeśli wstawienie się nie powiedzie. Zwykle pierwszy sposób jest lepszy, ponieważ aktualizacje są wykonywane częściej niż wstawki.

Nruessmann
źródło
... i wykonanie wkładki jako pierwszej (wiedząc, że czasami się nie powiedzie) jest drogie dla SQL Server. sqlperformance.com/2012/08/t-sql-queries/error-handling
Aaron Bertrand
-3

Wykonanie if istnieje ... w przeciwnym razie ... wymaga wykonania co najmniej dwóch żądań (jednego do sprawdzenia, drugiego do podjęcia działania). Poniższe podejście wymaga tylko jednego, w którym istnieje rekord, dwóch, jeśli wymagana jest wstawka:

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')
Luke Bennett
źródło
-3

Zazwyczaj robię to, co powiedziało kilka innych plakatów, odnosząc się najpierw do sprawdzenia, czy istnieje, a potem robię wszystko, co jest właściwe. Jedną z rzeczy, o których należy pamiętać, jest to, że plan wykonania buforowany przez sql może być nieoptymalny dla jednej lub drugiej ścieżki. Uważam, że najlepszym sposobem na to jest wywołanie dwóch różnych procedur składowanych.

FirstSP:
Jeśli istnieje
   Zadzwoń SecondSP (UpdateProc)
Jeszcze
   Zadzwoń do ThirdSP (InsertProc)

Teraz nie stosuję się często do moich rad, więc weź to z odrobiną soli.

Micky McQuade
źródło
Może to mieć znaczenie w starożytnych wersjach SQL Server, ale współczesne wersje mają kompilację na poziomie instrukcji. Widelce itp. Nie stanowią problemu, a zastosowanie osobnych procedur dla tych rzeczy nie rozwiązuje żadnego z problemów nieodłącznie związanych z dokonywaniem wyboru między aktualizacją a wstawką ...
Aaron Bertrand
-10

Wybierz, jeśli otrzymasz wynik, zaktualizuj go, jeśli nie, utwórz go.

Clint Ecker
źródło
3
To dwa połączenia z bazą danych.
Chris Cudmore,
3
Nie widzę z tym problemu.
Clint Ecker,
10
Problemem są dwa wywołania do DB, w końcu podwajasz liczbę objazdów do DB. Jeśli aplikacja trafi do bazy danych z dużą ilością wstawek / aktualizacji, pogorszy to wydajność. UPSERT to lepsza strategia.
Kev
5
to także tworzy warunek wyścigu nie?
niico