sql >> Base de Datos >  >> RDS >> Sqlserver

Usando una condición if en una inserción de SQL Server

El patrón es (sin manejo de errores):

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;

No necesita realizar una lectura adicional de la tabla #temp aquí. Ya lo estás haciendo al probar la actualización. Para protegerse de las condiciones de carrera, haga lo mismo que protegería cualquier bloque de dos o más declaraciones que desee aislar:lo envolvería en una transacción con un nivel de aislamiento apropiado (probablemente serializable aquí, aunque todo eso solo tiene sentido cuando no estamos hablando de una tabla #temp, ya que por definición está serializada).

No está más adelantado agregando un IF EXISTS verifique (y necesitaría agregar sugerencias de bloqueo para que sea seguro / serializable de todos modos), pero podría estar más atrasado, dependiendo de cuántas veces actualice las filas existentes versus inserte nuevas. Eso podría sumar una gran cantidad de E/S adicionales.

La gente probablemente te dirá que uses MERGE (que en realidad son múltiples operaciones detrás de escena, y también deben protegerse con serializable), le insto a que no lo haga. Expongo por qué aquí:

  • Tenga cuidado con la instrucción MERGE de SQL Server

Para un patrón de varias filas (como un TVP), manejaría esto de la misma manera, pero no hay una forma práctica de evitar la segunda lectura como se puede hacer con el caso de una sola fila. Y no, MERGE tampoco lo evita.

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;

Bueno, supongo que hay una manera de hacerlo, pero no lo he probado 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;

En cualquier caso, realice la actualización primero; de lo contrario, actualizará todas las filas que acaba de insertar, lo que sería un desperdicio.