Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Utilisation d'une condition if dans un insert SQL Server

Le modèle est (sans gestion des erreurs) :

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;

Vous n'avez pas besoin d'effectuer une lecture supplémentaire de la table #temp ici. Vous le faites déjà en essayant la mise à jour. Pour vous protéger des conditions de concurrence, vous faites la même chose que vous protégeriez n'importe quel bloc de deux déclarations ou plus que vous souhaitez isoler :vous l'envelopperiez dans une transaction avec un niveau d'isolement approprié (probablement sérialisable ici, bien que tout ne soit est logique lorsqu'il ne s'agit pas d'une table #temp, puisqu'elle est par définition sérialisée).

Vous n'êtes pas plus avancé en ajoutant un IF EXISTS check (et vous auriez besoin d'ajouter des indices de verrouillage pour rendre cela sûr/sérialisable de toute façon), mais vous pourriez être plus en retard, selon le nombre de fois que vous mettez à jour les lignes existantes par rapport à l'insertion de nouvelles. Cela pourrait représenter un grand nombre d'E/S supplémentaires.

Les gens vous diront probablement d'utiliser MERGE (qui est en fait de multiples opérations en coulisses, et doit également être protégé par sérialisable), je vous exhorte à ne pas le faire. J'explique pourquoi ici :

  • Soyez prudent avec l'instruction MERGE de SQL Server

Pour un motif à plusieurs lignes (comme un TVP), je traiterais cela de la même manière, mais il n'y a pas de moyen pratique d'éviter la deuxième lecture comme vous le pouvez avec le cas à une seule ligne. Et non, MERGE ne l'évite pas non plus.

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;

Eh bien, je suppose qu'il y a un moyen de le faire, mais je ne l'ai pas testé à fond :

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;

Dans les deux cas, vous effectuez d'abord la mise à jour, sinon vous mettrez à jour toutes les lignes que vous venez d'insérer, ce qui serait du gaspillage.