Rozwiązania do INSERT lub UPDATE na SQL Server
Przyjmij strukturę tabeli MyTable(KEY, datafield1, datafield2...)
.
Często chcę albo zaktualizować istniejący rekord, albo wstawić nowy rekord, jeśli nie istnieje.
Zasadniczo:
IF (key exists)
run update command
ELSE
run insert command
Jaki jest najlepszy sposób na napisanie tego?
21 answers
Nie zapomnij o transakcjach. Wydajność jest dobra, ale prosta (jeśli istnieje..) podejście jest bardzo niebezpieczne.
Gdy wiele wątków spróbuje wykonać Insert-or-update, możesz łatwo
zdobądź podstawowe naruszenie klucza.
Rozwiązania dostarczone przez @ Beau Crawford & @Esteban pokazują ogólną ideę, ale podatne na błędy.
Aby uniknąć impasów 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-28 16:24:16
Zobacz moją szczegółową odpowiedź na bardzo podobne poprzednie pytanie
@Beau Crawford ' s jest dobrym sposobem w SQL 2005 i poniżej, choć jeśli przyznajesz rep powinien iść do pierwszy facet, który tak to . Jedynym problemem jest to, że dla wkładek to jeszcze dwie operacje IO.
MS SQL2008 wprowadza merge
ze 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:- (
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:24
Zrób UPSERT:
UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key IF @@ROWCOUNT = 0 INSERT INTO MyTable (FieldA) VALUES (@FieldA)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 15:04:03
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ścigu tak samo jak wiele instrukcji, ale wprowadza inne zagrożenia:
Http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
Nawet z tą "prostszą" składnią, nadal preferuję takie podejście (pomijanie obsługi błędów dla zwięzłości):
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;
Wiele osób zaproponuje 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 wszystko to sprawia, że może być konieczne dwukrotne przeczytanie tabeli, aby zlokalizować wiersz(y) do aktualizacji. W pierwszej próbce wystarczy tylko raz zlokalizować wiersz(y). (W obu przypadkach, jeśli nie znaleziono wierszy od początkowego odczytu, następuje wstawka.)
Inni będą sugerować w ten sposób:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
Jest to jednak problematyczne, jeśli nie z innego powodu niż pozwolenie SQL Server na wyłapywanie WYJĄTKÓW, którym można było zapobiec w pierwszej kolejności, jest znacznie droższe, z wyjątkiem rzadkiego scenariusza, w którym prawie każda wkładka zawodzi. Udowadniam tu tyle:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 20:21:46
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
Edit:
Niestety, nawet na własną niekorzyść, muszę przyznać, że rozwiązania, które robią to bez wyboru, wydają się lepsze, ponieważ wykonują zadanie o jeden krok mniej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-31 03:20:38
Jeśli chcesz UPSERTOWAĆ 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 ...])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-02 15:46:14
Chociaż jest dość późno, aby skomentować to chcę dodać bardziej kompletny przykład za pomocą MERGE.
Takie instrukcje Insert+Update są zwykle nazywane instrukcjami "Upsert" i mogą być zaimplementowane za pomocą MERGE w SQL Server.
Bardzo dobry przykład podano tutaj: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
Powyższe wyjaśnia również scenariusze blokowania i współbieżności.
Będę cytował to samo dla odniesienie:
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;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 05:47:51
/*
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. Zadbaj o stan using ON. Następnie ustaw odpowiednią wartość (i typ) dla zmiennych w linii DECLARE.
Zdrówko.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-28 15:28:45
Można użyć instrukcji MERGE
, Ta instrukcja służy do wstawiania danych, jeśli nie istnieją lub aktualizacji, jeśli istnieją.
MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-12 18:17:22
Jeśli będzie aktualizacja if-no-rows - updated to Wstaw trasę, rozważ najpierw wstawienie, aby zapobiec Warunkom wyścigu (zakładając, że nie interweniuje 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 unikania warunku wyścigowego, jeśli w większości przypadków rekord już istnieje, spowoduje to awarię wkładki, marnując procesor.
Używanie MERGE jest prawdopodobnie lepsze dla sql2008.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-18 06:15:48
W SQL Server 2008 można użyć polecenia MERGE
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 22:02:36
To zależy od wzorca użycia. Trzeba spojrzeć na wykorzystanie big picture bez zagubienia się w szczegółach. Na przykład, jeśli wzorzec użycia jest aktualizowany w 99% po utworzeniu rekordu, to "UPSERT" jest najlepszym rozwiązaniem.
Po pierwszym wstawieniu (trafieniu), będą to wszystkie aktualizacje pojedynczych instrukcji, bez IFS lub ale. Warunek "gdzie" na wkładce jest konieczny, w przeciwnym razie wstawia 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-13 23:24:23
MS SQL Server 2008 wprowadza polecenie MERGE, które moim zdaniem jest częścią standardu SQL:2003. Jak wielu pokazało, nie jest to wielka sprawa, aby obsłużyć jeden wiersz przypadków, ale w przypadku dużych zbiorów danych, trzeba kursor, ze wszystkimi problemami wydajności, które pojawiają się. Instrukcja MERGE będzie bardzo mile widzianym dodatkiem w przypadku dużych zbiorów danych.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-12 12:25:23
Zanim wszyscy przeskoczą do HOLDLOCK-s ze strachu przed tymi nafaszerowanymi użytkownikami, którzy bezpośrednio uruchamiają Twoje sprocks: -) pozwól, że zwrócę uwagę, że musisz zagwarantować wyjątkowość nowych PK-S według projektu (klucze tożsamości, Generatory sekwencji w Oracle, unikalne indeksy dla zewnętrznych ID-s, zapytania objęte indeksami). To jest alfa i omega problemu. Jeśli nie masz tego, żaden blokada wszechświata cię nie uratuje, a jeśli masz to, nie potrzebujesz niczego poza UPDLOCK na pierwszym wybierz (lub najpierw użyj aktualizacji).
Koła zębate normalnie działają w bardzo kontrolowanych warunkach i z założeniem zaufanego rozmówcy (mid tier). Oznacza to, że jeśli prosty wzorzec upsert (update+insert lub merge) kiedykolwiek zobaczy duplikat PK, oznacza to błąd w projekcie średniej warstwy lub tabeli i dobrze, że SQL wykrzyczy błąd w takim przypadku i odrzuci rekord. Umieszczenie blokady w tym przypadku równa się zjadaniu wyjątków i przyjmowaniu potencjalnie wadliwych danych, poza zmniejszeniem perf.
Powiedziawszy, że używanie MERGE, lub UPDATE then INSERT jest łatwiejsze na twoim serwerze i mniej podatne na błędy, ponieważ nie musisz pamiętać, aby dodać (UPDLOCK), aby najpierw wybrać. Ponadto, jeśli robisz wstawianie / aktualizacje w małych partiach, musisz znać swoje dane, aby zdecydować, czy transakcja jest odpowiednia, czy nie. To tylko zbiór niepowiązanych rekordów, wtedy dodatkowa transakcja "kopertowania" będzie szkodliwa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-21 01:19:07
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 :
Thread 1: value = 1
Thread 2: value = 2
Przykładowy scenariusz rasy
- Klucz nie jest zdefiniowany
- Thread 1 fails with update
- Thread 2 fails with update
- dokładnie jeden z wątków 1 lub wątek 2 kończy się wstawką. Np. wątek 1
-
Drugi wątek nie działa z insert (z błędnym klawiszem duplicate) - wątek 2.
- wynik:" pierwszy " z dwóch stopni do wstawienia decyduje o wartości.
- Wanted result: ostatni z 2 wątków do zapisu danych (update lub insert) powinien decydować o wartości
Ale; w środowisku wielowątkowym, OS scheduler decyduje o kolejności wykonania wątku - w powyższym scenariuszu, gdzie mamy ten warunek wyścigu, to OS, który zadecydował o kolejności egzekucji. Ie: błędem jest stwierdzenie, że "wątek 1" lub " wątek 2 "był" pierwszy " z punktu widzenia systemu.
Kiedy czas wykonania jest tak bliski dla wątku 1 i wątku 2, wynik wyścigu nie ma znaczenia. Jedynym wymogiem powinno być to, aby jeden z wątków zdefiniował wynikową wartość.
Dla implementacji: jeśli aktualizacja, po której następuje insert, spowoduje błąd "duplicate key", powinno to być traktowane jako sukces.
Także, nie należy oczywiście zakładać, że wartość w bazie danych jest taka sama jak wartość, którą ostatnio napisałeś.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-20 20:44:00
Próbowałem poniżej rozwiązania i działa dla mnie, gdy występuje równoczesne żądanie polecenia 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-03 12:34:14
Możesz użyć tego zapytania. Praca we wszystkich edycjach 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ć negatywy odpowiedzi
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-01 13:51:08
Jeśli używasz ADO.NET, DataAdapter zajmuje się tym.
Jeśli chcesz to załatwić sam, to tak:
Upewnij się, że w kolumnie klucza znajduje się ograniczenie klucza głównego.
Wtedy ty:
- wykonaj aktualizację
- Jeśli aktualizacja nie powiedzie się, ponieważ rekord z kluczem już istnieje, wykonaj insert. Jeśli aktualizacja się nie powiedzie, jesteś skończony.
Można też zrobić to na odwrót, tzn. najpierw zrobić insert, a potem update jeśli insert nie powiodło się. Zwykle pierwszy sposób jest lepszy, ponieważ aktualizacje są wykonywane częściej niż wstawki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 15:08:51
Sprawdzanie, czy istnieje ... else ... wymaga wykonania minimum dwóch żądań (jeden do sprawdzenia, jeden do podjęcia działań). Następujące podejście wymaga tylko jednego, jeśli rekord istnieje, 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')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 15:07:36
Zwykle robię to, o czym mówiło kilka innych plakatów, w odniesieniu do sprawdzenia, czy istnieje, a następnie robię cokolwiek, co jest właściwą ścieżką. Jedną z rzeczy, o których powinieneś 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: If Exists Call SecondSP (UpdateProc) Else Call ThirdSP (InsertProc)Nie stosuję się do własnych rad zbyt często, więc potraktuj to z przymrużeniem oka.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 15:08:03
Wybierz, jeśli otrzymasz wynik, zaktualizuj go, jeśli nie, Utwórz.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-20 15:02:26