it-swarm-eu.dev

Řešení pro INSERT OR UPDATE na serveru SQL

Předpokládejme strukturu tabulky MyTable(KEY, datafield1, datafield2...).

Často chci buď aktualizovat existující záznam, nebo vložit nový záznam, pokud neexistuje.

V podstatě:

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

Jaký je nejlepší způsob, jak to napsat?

540
Chris Cudmore

nezapomeňte na transakce. Výkon je dobrý, ale jednoduchý (IF EXISTS ..) přístup je velmi nebezpečný.
Když se několik podprocesů pokusí provést Insert-or-update, můžete snadno získat narušení primárního klíče.

Řešení poskytovaná @Beau Crawford & @Esteban ukazují obecnou myšlenku, ale náchylnou k chybám.

Chcete-li se vyhnout zablokování a narušení PK, můžete použít něco takového:

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

nebo

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

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

Viz moje podrobná odpověď na velmi podobnou předchozí otázk

@ Beau Crawford je je dobrý způsob, jak v SQL 2005 a níže, i když, pokud jste udělení rep to by mělo jít na první chlap na SO it . Jediným problémem je, že pro vložky je to stále dvě operace IO.

MS Sql2008 zavádí 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, ... )

Nyní je to opravdu jen jedna operace IO, ale hrozný kód :-(

368
Keith

Proveďte službu UPSERT:

 UPDATE MyTable SET FieldA = @ FieldA WHERE Key = @ Key 
 
 IF @@ ROWCOUNT = 0 
 INSERT INTO MyTable (FieldA) VALUES (@FieldA) [@ FieldA] .____.]

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

156
Beau Crawford

Mnoho lidí navrhne použití MERGE, ale upozorňuji vás na to. Ve výchozím nastavení vás nechrání před souběžnými podmínkami a podmínkami závodů více než více příkazů, ale zavádí další nebezpečí:

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

I když je tato "jednodušší" syntaxe k dispozici, stále dávám přednost tomuto přístupu (vynechání chybného zpracování chyb pro stručnost):

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;

Spousta lidí to navrhne takto:

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;

To vše však znamená, že budete muset dvakrát přečíst tabulku, abyste našli řádek, který chcete aktualizovat. V prvním vzorku budete muset pouze jednou najít řádky. (V obou případech, pokud nejsou nalezeny žádné řádky z počátečního čtení, dojde k vložení.)

Ostatní navrhnou takto:

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

To je však problematické, pokud z žádného jiného důvodu, než nechat SQL Server výjimky chytit, že byste mohli zabránit v první řadě je mnohem dražší, s výjimkou vzácných scénář, kde téměř každý vložení se nezdaří. Zde dokazuji tolik:

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

Edit:

Bohužel, i na svou vlastní újmu, musím přiznat, že řešení, která to dělají bez výběru, se zdají být lepší, protože splňují úkol s jedním méně krokem.

51
Esteban Araya

Pokud chcete UPSERT více než jeden záznam najednou, můžete použít příkaz ANSI SQL: 2003 DML příkaz 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 ...])

Podívejte se na Napodobování MERGE prohlášení v SQL Server 2005 .

36
Eric Weilnau

Ačkoli jeho dost pozdě na to komentovat chci přidat více kompletní příklad pomocí MERGE.

Tyto příkazy Insert + Update se obvykle nazývají příkazy "Upsert" a lze je implementovat pomocí služby MERGE v serveru SQL Server.

Velmi dobrý příklad je uveden zde: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

Výše uvedené vysvětluje scénáře zamykání a souběžnosti.

Budu citovat totéž pro odkaz:

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;
10
user243131
/*
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

Nahraďte názvy tabulek a polí podle toho, co potřebujete. Postarejte se o podmínku pomocí ON. Pak nastavte příslušnou hodnotu (a typ) pro proměnné na řádku DECLARE.

Na zdraví.

8
Denver

Můžete použít příkaz MERGE Příkaz, Tento příkaz se používá k vložení dat, pokud neexistují, nebo pokud existují, aktualizovat.

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

Pokud přejdete na cestu UPDATE if-no-rows-then then INSERT, zvažte nejprve provedení INSERT, abyste zabránili podmínce rasy (za předpokladu, že neprovedete žádnou akci 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 [email protected]
   WHERE [email protected]
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

Kromě vyhýbání se podmínkám závodu, pokud ve většině případů záznam již existuje, pak to způsobí, že INSERT selže, ztrácí CPU.

Pravděpodobně výhodnější je použití služby MERGE pro SQL2008.

4
Kristen

To závisí na způsobu použití. Člověk se musí podívat na velký obraz použití, aniž by se ztratil v detailech. Pokud je například vzor použití 99% aktualizací po vytvoření záznamu, pak je řešení „UPSERT“ nejlepším řešením.

Po prvním vložení (hit) to budou všechny aktualizace jednotlivých příkazů, ne ifs nebo buts. Podmínka „kde“ na vložce je nezbytná, jinak vloží duplikáty a nechcete se zabývat zamykáním.

UPDATE <tableName> SET <field>[email protected] WHERE [email protected];

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

V SQL Server 2008 můžete použít výraz MERGE

3
Bart

MS SQL Server 2008 zavádí výraz MERGE, který je podle mého názoru součástí standardu SQL: 2003. Jak mnozí ukázali, že to není velký problém zvládnout jeden řádek případy, ale když se jedná o velké datové sady, člověk potřebuje kurzor, se všemi problémy výkonu, které přicházejí spolu. Příkaz MERGE bude velmi vítán při řešení velkých souborů dat.

2
bjorsig

Než všichni skočí do HOLDLOCK-s ze strachu z těchto nafarious uživatelů běží vaše sprocs přímo :-) dovolte mi upozornit, že musíte zaručit jedinečnost nových PK-s designem (klíče identity, sekvence generátory v Oracle, jedinečné indexy pro externí ID, dotazy pokryté indexy). To je alfa a omega problému. Pokud to nemáte, žádný HOLDLOCK-s ve vesmíru vás nezachrání a pokud máte, pak nepotřebujete nic nad rámec UPDLOCK na první volbě (nebo použijte nejprve aktualizaci).

Sprocs běžně běží za velmi kontrolovaných podmínek as předpokladem důvěryhodného volajícího (střední vrstva). Znamená to, že pokud jednoduchý vzor vzestupu (update + insert nebo merge) někdy uvidí duplicitní PK, což znamená chybu ve vašem mid-tieru nebo designu tabulky a je dobré, že SQL v takovém případě zavolá chybu a záznam odmítne. Umístění HOLDLOCKu v tomto případě se rovná výjimkám v jídle a přijímání potenciálně chybných dat, kromě snížení výkonu.

To znamená, že pomocí MERGE, nebo UPDATE pak INSERT je jednodušší na vašem serveru a méně chyb náchylné, protože nemusíte pamatovat přidat (UPDLOCK) pro první výběr. Také pokud provádíte vkládání/aktualizace v malých dávkách, musíte znát svá data, abyste se mohli rozhodnout, zda je transakce vhodná nebo ne. Je to jen sbírka nesouvisejících záznamů a další „obálková“ transakce bude škodlivá.

1
ZXX

Nezáleží na podmínkách závodu, pokud si nejdříve vyzkoušíte aktualizaci následovanou vložkou? Řekněme, že máte dvě vlákna, která chtějí nastavit hodnotu klíče klíč:

Závit 1: hodnota = 1
Závit 2: hodnota = 2

Příklad scénáře podmínky závodu

  1. klíč není definován
  2. Vlákno 1 se nezdaří s aktualizací
  3. Vlákno 2 se nezdaří s aktualizací
  4. Přesně jeden z nití 1 nebo nitě 2 je úspěšný s vložkou. Např. závit 1
  5. Druhý podproces selže s vložením (s duplikovaným klíčem chyby) - vlákno 2.

    • Výsledek: "První" z obou běhounu se vloží, rozhodne hodnotu.
    • Hledaný výsledek: Poslední z 2 vláken pro zápis dat (aktualizace nebo vložení) by měla rozhodnout o hodnotě

Ale; v prostředí s více podprocesy plánovač operačního systému rozhoduje o pořadí provádění podprocesů - ve výše uvedeném scénáři, kde máme tuto podmínku závodu, to byl OS, který rozhodl o pořadí provádění. Tj .: Je špatné říkat, že "vlákno 1" nebo "vlákno 2" bylo "první" z pohledu systému.

Když je doba provedení tak blízko pro závit 1 a vlákno 2, nezáleží na výsledku závodu. Jediným požadavkem by mělo být, aby jedna z vláken definovala výslednou hodnotu.

Pro implementaci: Pokud aktualizace následovaná vložit výsledky v chybě "duplicitní klíč", by mělo být považováno za úspěch.

Také by se samozřejmě nemělo předpokládat, že hodnota v databázi je stejná jako hodnota, kterou jste napsali naposledy.

1
runec

Snažil jsem se pod řešení a funguje to pro mě, když se objeví souběžný požadavek na vložení výpisu.

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
Dev