Inserisci Update stored proc su SQL Server


104

Ho scritto un proc memorizzato che farà un aggiornamento se esiste un record, altrimenti farà un inserimento. Assomiglia a questo:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

La mia logica dietro a scriverlo in questo modo è che l'aggiornamento eseguirà una selezione implicita utilizzando la clausola where e se restituisce 0, l'inserimento avrà luogo.

L'alternativa a farlo in questo modo sarebbe eseguire una selezione e quindi, in base al numero di righe restituite, eseguire un aggiornamento o un inserimento. Questo l'ho considerato inefficiente perché se devi fare un aggiornamento causerà 2 selezioni (la prima chiamata di selezione esplicita e la seconda implicita nel dove dell'aggiornamento). Se il proc dovesse eseguire un inserimento, non ci sarebbe alcuna differenza di efficienza.

La mia logica suona qui? È questo il modo in cui combineresti un inserimento e un aggiornamento in un processo memorizzato?

Risposte:


61

La tua ipotesi è giusta, questo è il modo ottimale per farlo e si chiama upsert / merge .

Importanza di UPSERT - da sqlservercentral.com :

Per ogni aggiornamento nel caso menzionato sopra, stiamo rimuovendo una lettura aggiuntiva dalla tabella se usiamo UPSERT invece di EXISTS. Sfortunatamente per un inserimento, entrambi i metodi UPSERT e IF EXISTS utilizzano lo stesso numero di letture nella tabella. Pertanto, il controllo dell'esistenza dovrebbe essere effettuato solo quando esiste una ragione molto valida per giustificare l'I / O aggiuntivo. Il modo ottimizzato di fare le cose è assicurarsi di avere poche letture possibili sul DB.

La migliore strategia è tentare l'aggiornamento. Se nessuna riga è interessata dall'aggiornamento, inserire. Nella maggior parte dei casi, la riga esisterà già e sarà richiesto un solo I / O.

Modifica : controlla questa risposta e il post del blog collegato per conoscere i problemi con questo modello e come farlo funzionare in sicurezza.


1
Beh, almeno ha risposto a una domanda, credo. E non ho aggiunto il codice perché il codice nella domanda mi sembrava già giusto per me. Sebbene l'avrei inserito in una transazione, non ho preso in considerazione il livello di isolamento per l'aggiornamento. Grazie per averlo sottolineato nella tua risposta!
binOr

54

Per favore leggi il post sul mio blog per un buon modello sicuro che puoi usare. Ci sono molte considerazioni e la risposta accettata a questa domanda è tutt'altro che sicura.

Per una risposta rapida, prova il seguente schema. Funzionerà bene su SQL 2000 e versioni successive. SQL 2005 ti dà la gestione degli errori che apre altre opzioni e SQL 2008 ti dà un comando MERGE.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

1
Nel tuo post sul blog concludi con l'uso del suggerimento WITH (updlock, serializable) nel controllo dell'esistenza. Tuttavia, la lettura di MSDN è: "UPDLOCK - Specifica che i blocchi di aggiornamento devono essere presi e conservati fino al completamento della transazione." Questo significa che il suggerimento serializzabile è superfluo in quanto il blocco dell'aggiornamento verrà comunque mantenuto per il resto della transazione o ho frainteso qualcosa?
Dan Def

10

Se per essere utilizzato con SQL Server 2000/2005 il codice originale deve essere racchiuso nella transazione per assicurarsi che i dati rimangano coerenti nello scenario simultaneo.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Ciò comporterà costi aggiuntivi per le prestazioni, ma garantirà l'integrità dei dati.

Aggiungi, come già suggerito, MERGE dovrebbe essere utilizzato dove disponibile.



6

Non solo è necessario eseguirlo in transazione, ma richiede anche un elevato livello di isolamento. Infatti il ​​livello di isolamento predefinito è Read Commited e questo codice necessita di Serializable.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Forse aggiungere anche il controllo degli errori @@ e il rollback potrebbe essere una buona idea.


@Munish Goyal Perché nel database più comandi e precedenti vengono eseguiti in parallelo. Quindi un altro thread può inserire una riga subito dopo l'esecuzione dell'aggiornamento e prima dell'esecuzione dell'inserimento.
Tomas Tintera

5

Se non stai facendo un'unione in SQL 2008, devi cambiarla in:

se @@ conteggio righe = 0 e @@ errore = 0

altrimenti se l'aggiornamento fallisce per qualche motivo, proverà a un inserimento in seguito perché il conteggio delle righe su un'istruzione fallita è 0


3

Grande fan di UPSERT, riduce davvero il codice da gestire. Ecco un altro modo in cui lo faccio: uno dei parametri di input è ID, se l'ID è NULL o 0, sai che è un INSERT, altrimenti è un aggiornamento. Si presume che l'applicazione sappia se esiste un ID, quindi non funzionerà in tutte le situazioni, ma se lo farai taglierà le esecuzioni a metà.


2

Post di Dima Malenko modificato:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

È possibile intercettare l'errore e inviare il record a una tabella di inserimento non riuscita.
Avevo bisogno di farlo perché stiamo prendendo tutti i dati inviati tramite WSDL e, se possibile, correggendoli internamente.


1

La tua logica sembra corretta, ma potresti prendere in considerazione l'aggiunta di un codice per impedire l'inserimento se hai passato una chiave primaria specifica.

Altrimenti, se esegui sempre un inserimento se l'aggiornamento non ha interessato alcun record, cosa succede quando qualcuno elimina il record prima che venga eseguito "UPSERT"? Ora il record che stavi cercando di aggiornare non esiste, quindi creerà un record. Probabilmente non è il comportamento che stavi cercando.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.