Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Utilizzo di una condizione if in un SQL Server di inserimento

Il modello è (senza gestione degli errori):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
  WHERE ProductID = @ProductID;

IF @@ROWCOUNT = 0
BEGIN
  INSERT #TProductSales(ProductID, StockQTY, ETA1) 
    VALUES(@ProductID, @StockQTY, @ETA1);
END

COMMIT TRANSACTION;

Non è necessario eseguire una lettura aggiuntiva della tabella #temp qui. Lo stai già facendo provando l'aggiornamento. Per proteggerti dalle condizioni di gara, fai lo stesso che proteggeresti qualsiasi blocco di due o più istruzioni che desideri isolare:lo avvolgeresti in una transazione con un livello di isolamento appropriato (probabilmente serializzabile qui, anche se tutto solo ha senso quando non stiamo parlando di una tabella #temp, poiché per definizione è serializzata).

Non sei più avanti aggiungendo un IF EXISTS check (e dovresti aggiungere suggerimenti di blocco per renderlo sicuro / serializzabile comunque), ma potresti essere più indietro, a seconda di quante volte aggiorni le righe esistenti rispetto a inserirne di nuove. Ciò potrebbe comportare molti I/O extra.

Le persone probabilmente ti diranno di usare MERGE (che in realtà è più operazioni dietro le quinte e deve anche essere protetto con serializzabile), ti esorto a non farlo. Spiego perché qui:

  • Utilizzare Attenzione con l'istruzione MERGE di SQL Server

Per un modello a più righe (come un TVP), lo gestirei allo stesso modo, ma non esiste un modo pratico per evitare la seconda lettura come puoi con il caso a riga singola. E no, MERGE non lo evita neanche.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE t SET t.col = tvp.col
  FROM dbo.TargetTable AS t
  INNER JOIN @TVP AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols)
  SELECT ProductID, othercols
  FROM @TVP AS tvp
  WHERE NOT EXISTS
  (
    SELECT 1 FROM dbo.TargetTable
    WHERE ProductID = tvp.ProductID
  );

COMMIT TRANSACTION;

Bene, immagino che ci sia un modo per farlo, ma non l'ho testato a fondo:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

DECLARE @exist TABLE(ProductID int PRIMARY KEY);

UPDATE t SET t.col = tvp.col
  OUTPUT deleted.ProductID INTO @exist
  FROM dbo.TargetTable AS t
  INNER JOIN @tvp AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols) 
  SELECT ProductID, othercols 
  FROM @tvp AS t 
  WHERE NOT EXISTS 
  (
    SELECT 1 FROM @exist 
    WHERE ProductID = t.ProductID
  );

COMMIT TRANSACTION;

In entrambi i casi, esegui prima l'aggiornamento, altrimenti aggiornerai tutte le righe che hai appena inserito, il che sarebbe uno spreco.