Domanda

Supponi una struttura di tabella di MyTable (KEY, datafield1, datafield2 ...) .

Spesso voglio aggiornare un record esistente o inserire un nuovo record se non esiste.

In sostanza:

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

Qual è il modo migliore per scrivere questo?

È stato utile?

Soluzione

non dimenticare le transazioni. Le prestazioni sono buone, ma l'approccio semplice (SE ESISTE ..) è molto pericoloso.
Quando più thread tenteranno di eseguire l'inserimento o l'aggiornamento, puoi farlo facilmente ottenere la violazione della chiave primaria.

Soluzioni fornite da @Beau Crawford & amp; @Esteban mostra un'idea generale ma soggetta a errori.

Per evitare deadlock e violazioni di PK puoi usare qualcosa del genere:

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

o

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

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

Altri suggerimenti

Vedi la mia risposta dettagliata a una domanda precedente molto simile

@Beau Crawford's è un buon metodo in SQL 2005 e in basso, anche se se stai concedendo la reputazione dovrebbe andare al primo ragazzo a SO . L'unico problema è che per gli inserti sono ancora due operazioni di I / O.

MS Sql2008 introduce merge dallo standard 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, ... )

Ora è davvero solo un'operazione di I / O, ma un codice orribile :-(

Esegui un UPSERT:

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

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

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

Molte persone ti suggeriranno di usare MERGE , ma ti avverto di non farlo. Per impostazione predefinita, non ti protegge dalla concorrenza e dalle condizioni di gara più di più dichiarazioni, ma introduce altri pericoli:

http://www.mssqltips.com/sqlservertip/ 3074 / uso-cautela-con-SQL-server-merge-statement /

Anche con questo "più semplice" sintassi disponibile, preferisco ancora questo approccio (gestione degli errori omessa per brevità):

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;

Molte persone suggeriranno in questo modo:

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;

Ma tutto ciò è garantire che potrebbe essere necessario leggere due volte la tabella per individuare le righe da aggiornare. Nel primo esempio, dovrai sempre individuare le righe una sola volta. (In entrambi i casi, se non viene trovata alcuna riga dalla lettura iniziale, si verifica un inserimento.)

Altri suggeriranno in questo modo:

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

Tuttavia, ciò è problematico se non altro per il fatto che lasciare che SQL Server rilevi le eccezioni che si sarebbe potuto prevenire in primo luogo è molto più costoso, tranne nel raro scenario in cui quasi tutti gli inserimenti falliscono. Dimostro altrettanto qui:

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

Modifica:

Purtroppo, anche a mio svantaggio, devo ammettere che le soluzioni che lo fanno senza una scelta sembrano essere migliori dal momento che svolgono il compito con un passo in meno.

Se si desidera UPSERT più di un record alla volta, è possibile utilizzare l'istruzione DML ANSI SQL: 2003 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 ...])

Scopri Imitazione dell'istruzione MERGE in SQL Server 2005 .

Anche se è abbastanza tardi per commentare questo, voglio aggiungere un esempio più completo usando MERGE.

Tali istruzioni Insert + Update sono generalmente chiamate " Upsert " e possono essere implementate usando MERGE in SQL Server.

Un ottimo esempio è dato qui: http: //weblogs.sqlteam .com / dang / archive / 2009/01/31 / UPSERT-Race-Condizione-Con-MERGE.aspx

Quanto sopra spiega anche gli scenari di blocco e concorrenza.

Citerò lo stesso per riferimento:

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

Sostituisci i nomi delle tabelle e dei campi con qualsiasi cosa tu abbia bisogno. Prenditi cura della condizione utilizzando ON . Quindi impostare il valore appropriato (e digitare) per le variabili nella riga DECLARE.

Saluti.

Puoi utilizzare l'istruzione MERGE , questa istruzione viene utilizzata per inserire dati se non esistono o aggiornare se esistono.

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

Se vai al UPDATE se-no-righe-aggiornato, allora INSERT route, considera di fare prima INSERT per prevenire una race condition (supponendo che non intervenga 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

Oltre a evitare una condizione di competizione, se nella maggior parte dei casi il record esisterà già, ciò causerà il fallimento di INSERT, sprecando CPU.

L'uso di MERGE è probabilmente preferibile per SQL2008 in poi.

In SQL Server 2008 è possibile utilizzare l'istruzione MERGE

Dipende dal modello di utilizzo. Bisogna guardare il quadro generale sull'uso senza perdersi nei dettagli. Ad esempio, se il modello di utilizzo prevede aggiornamenti del 99% dopo la creazione del record, "UPSERT" è la soluzione migliore.

Dopo il primo inserimento (hit), saranno tutti gli aggiornamenti a singola istruzione, senza se e senza ma. La condizione "dove" sull'inserto è necessaria, altrimenti inserirà i duplicati e non si desidera gestire il blocco.

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 introduce l'istruzione MERGE, che credo faccia parte dello standard SQL: 2003. Come molti hanno dimostrato, non è un grosso problema gestire casi di una riga, ma quando si tratta di set di dati di grandi dimensioni, è necessario un cursore, con tutti i problemi di prestazioni che si presentano. La dichiarazione MERGE sarà molto apprezzata quando si tratta di set di dati di grandi dimensioni.

Prima che tutti saltino a HOLDLOCK per paura di questi utenti disonesti che gestiscono direttamente i tuoi sprocs :-) fammi sottolineare che devi garantire l'unicità dei nuovi PK in base alla progettazione (identità chiavi, generatori di sequenze in Oracle, indici univoci per ID esterni, query coperte da indici). Questo è l'alfa e l'omega del problema. Se non lo possiedi, nessun HOLDLOCK dell'universo ti salverà e se lo possiedi, non avrai bisogno di nulla oltre UPDLOCK alla prima selezione (o di utilizzare prima l'aggiornamento).

Gli Sprocs normalmente funzionano in condizioni molto controllate e con l'ipotesi di un chiamante di fiducia (livello intermedio). Ciò significa che se un semplice schema di upsert (aggiornamento + inserimento o unione) vede mai un PK duplicato significa un bug nella progettazione di livello intermedio o tabella ed è positivo che SQL urlerà un errore in questo caso e rifiuterà il record. Inserire un HOLDLOCK in questo caso equivale a mangiare eccezioni e a prendere dati potenzialmente difettosi, oltre a ridurre la tua perf.

Detto questo, usando MERGE o UPDATE, INSERT è più facile sul tuo server e meno soggetto a errori poiché non devi ricordare di aggiungere (UPDLOCK) per selezionare prima. Inoltre, se si stanno effettuando inserimenti / aggiornamenti in piccoli lotti, è necessario conoscere i propri dati per decidere se una transazione è appropriata o meno. È solo una raccolta di record non correlati, quindi ulteriori "avvolgenti" la transazione sarà dannosa.

Le condizioni di gara contano davvero se provi prima un aggiornamento seguito da un inserto? Diciamo che hai due thread che vogliono impostare un valore per la chiave chiave :

Discussione 1: valore = 1
Discussione 2: valore = 2

Esempio di scenario delle condizioni di gara

  1. chiave non è definito
  2. Discussione 1 non riuscita con aggiornamento
  3. Discussione 2 non riuscita con aggiornamento
  4. Esattamente uno dei thread 1 o 2 ha esito positivo con l'inserimento. Per esempio. thread 1
  5. L'altro thread ha esito negativo con insert (con chiave duplicata errore) - thread 2.

    • Risultato: il "primo" quot dei due gradini da inserire, decide il valore.
    • Risultato desiderato: l'ultimo dei 2 thread per scrivere i dati (aggiornare o inserire) dovrebbe decidere il valore

Ma; in un ambiente multithread, lo scheduler del sistema operativo decide l'ordine di esecuzione del thread: nello scenario sopra, dove abbiamo questa condizione di competizione, è stato il sistema operativo a decidere la sequenza di esecuzione. Vale a dire: è sbagliato dire che "thread 1" oppure "filetto 2" era "primo" dal punto di vista del sistema.

Quando il tempo di esecuzione è così vicino per il thread 1 e il thread 2, il risultato della race condition non ha importanza. L'unico requisito dovrebbe essere che uno dei thread definisca il valore risultante.

Per l'implementazione: se l'aggiornamento seguito da insert risulta in errore "chiave duplicata", questo dovrebbe essere considerato un successo.

Inoltre, ovviamente, non si dovrebbe mai presumere che il valore nel database sia lo stesso del valore scritto per ultimo.

Avevo provato la soluzione di seguito e funziona per me, quando si verifica una richiesta simultanea per l'istruzione 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

È possibile utilizzare questa query. Funziona in tutte le edizioni di SQL Server. È semplice e chiaro. Ma devi usare 2 query. Puoi usare se non puoi usare 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

NOTA: Spiegare i negativi delle risposte

Se si utilizza ADO.NET, DataAdapter gestisce questo.

Se vuoi gestirlo da solo, questo è il modo:

Assicurati che ci sia un vincolo chiave principale nella colonna chiave.

Quindi tu:

  1. Esegui l'aggiornamento
  2. Se l'aggiornamento non riesce perché esiste già un record con la chiave, eseguire l'inserimento. Se l'aggiornamento non fallisce, hai finito.

Puoi anche farlo al contrario, ovvero esegui prima l'inserimento e esegui l'aggiornamento se l'inserimento non riesce. Normalmente il primo modo è migliore, perché gli aggiornamenti vengono eseguiti più spesso degli inserti.

Fare un if esiste ... altrimenti ... implica fare almeno due richieste (una per verificare, una per agire). L'approccio seguente richiede solo uno in cui esiste il record, due se è richiesto un inserimento:

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

Di solito faccio quello che hanno detto molti altri manifesti riguardo a verificarne prima l'esistenza e poi fare qualunque sia il percorso corretto. Una cosa che dovresti ricordare quando fai questo è che il piano di esecuzione memorizzato nella cache da sql potrebbe non essere ottimale per un percorso o per l'altro. Credo che il modo migliore per farlo sia chiamare due diverse procedure memorizzate.

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

Ora, non seguo i miei consigli molto spesso, quindi prenderli con un granello di sale.

Effettua una selezione, se ottieni un risultato, aggiornalo, in caso contrario, crealo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top