Muitas pessoas sugerem que você use MERGE
, mas eu o aviso contra isso. Por padrão, ele não protege você das condições de concorrência e corrida mais do que várias declarações e apresenta outros perigos:
http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
Mesmo com essa sintaxe "mais simples" disponível, ainda prefiro essa abordagem (tratamento de erros omitido por questões de brevidade):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;
Muitas pessoas sugerem o seguinte:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
UPDATE ...
END
ELSE
INSERT ...
END
COMMIT TRANSACTION;
Mas tudo isso é garantir que você precise ler a tabela duas vezes para localizar as linhas a serem atualizadas. No primeiro exemplo, você só precisará localizar as linhas uma vez. (Nos dois casos, se nenhuma linha for encontrada na leitura inicial, ocorrerá uma inserção.)
Outros irão sugerir desta maneira:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
No entanto, isso é problemático se, por nenhum outro motivo, deixar o SQL Server capturar exceções que você poderia ter evitado em primeiro lugar é muito mais caro, exceto no cenário raro em que quase todas as inserções falham. Eu provo isso aqui: