Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Usando uma condição if em um SQL Server de inserção


O padrão é (sem tratamento de erros):
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;

Você não precisa realizar uma leitura adicional da tabela #temp aqui. Você já está fazendo isso tentando a atualização. Para proteger de condições de corrida, você faz o mesmo que protegeria qualquer bloco de duas ou mais instruções que deseja isolar:você o envolveria em uma transação com um nível de isolamento apropriado (provavelmente serializável aqui, embora tudo apenas faz sentido quando não estamos falando de uma tabela #temp, já que por definição é serializada).

Você não está mais adiantado adicionando um IF EXISTS check (e você precisaria adicionar dicas de bloqueio para torná-lo seguro/serializável de qualquer maneira), mas você pode estar mais atrasado, dependendo de quantas vezes você atualizar as linhas existentes versus inserir novas. Isso poderia adicionar um monte de E/S extra.

As pessoas provavelmente dirão para você usar MERGE (que na verdade é várias operações nos bastidores e também precisa ser protegida com serializável), peço que você não o faça. Eu coloco o porquê aqui:
  • Tenha cuidado com a instrução MERGE do SQL Server

Para um padrão de várias linhas (como um TVP), eu lidaria com isso da mesma maneira, mas não há uma maneira prática de evitar a segunda leitura como você pode com o caso de uma única linha. E não, MERGE também não 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;

Bem, acho que há uma maneira de fazer isso, mas não testei isso completamente:
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;

Em ambos os casos, você executa a atualização primeiro, caso contrário, atualizará todas as linhas que acabou de inserir, o que seria um desperdício.