Pergunta

Eu escrevi um procedimento armazenado que fará uma atualização se existir um registro, caso contrário, fará uma inserção.Parece algo assim:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Minha lógica por trás de escrevê-lo dessa maneira é que a atualização executará uma seleção implícita usando a cláusula where e se retornar 0, a inserção ocorrerá.

A alternativa para fazer dessa forma seria fazer uma seleção e, com base no número de linhas retornadas, fazer uma atualização ou inserção.Isso eu considerei ineficiente porque se você for fazer uma atualização causará 2 seleções (a primeira chamada select explícita e a segunda implícita no where da atualização).Se o processo fizesse uma inserção, não haveria diferença na eficiência.

Minha lógica está correta aqui?É assim que você combinaria uma inserção e uma atualização em um processo armazenado?

Foi útil?

Solução

Sua suposição está correta, esta é a maneira ideal de fazer isso e é chamada inserir/mesclar.

Importância do UPSERT - de sqlservercentral.com:

Para cada atualização no caso mencionado acima, estamos removendo uma leitura adicional da tabela se usarmos o upsert em vez de existir.Infelizmente, para uma inserção, tanto o UpSert quanto se existirem métodos usam o mesmo número de leituras na tabela.Portanto, a verificação da existência só deve ser feita quando houver um motivo muito válido para justificar a E/S adicional.A maneira otimizada de fazer as coisas é garantir que você tenha pequenas leituras possível no banco de dados.

A melhor estratégia é tentar a atualização.Se nenhuma linha for afetada pela atualização, insira.Na maioria das circunstâncias, a linha já existirá e apenas uma E/S será necessária.

Editar:Por favor verifique esta resposta e a postagem do blog vinculada para saber mais sobre os problemas desse padrão e como fazê-lo funcionar com segurança.

Outras dicas

Por favor leia o postar no meu blog para um padrão bom e seguro que você possa usar.Há muitas considerações e a resposta aceita para esta questão está longe de ser segura.

Para uma resposta rápida, tente o seguinte padrão.Funcionará bem no SQL 2000 e superior.O SQL 2005 oferece tratamento de erros que abre outras opções e o SQL 2008 oferece um comando MERGE.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

Se for usado com o SQL Server 2000/2005, o código original precisa ser incluído na transação para garantir que os dados permaneçam consistentes no cenário simultâneo.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Isto incorrerá em custos adicionais de desempenho, mas garantirá a integridade dos dados.

Adicione, como já sugerido, MERGE deve ser usado quando disponível.

A propósito, MERGE é um dos novos recursos do SQL Server 2008.

Você não só precisa executá-lo na transação, mas também precisa de um alto nível de isolamento.Na verdade, o nível de isolamento padrão é Read Commited e esse código precisa de Serializable.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Talvez adicionar também a verificação e reversão de @@ erros possa ser uma boa ideia.

Se você não estiver fazendo uma mesclagem no SQL 2008, deverá alterá-lo para:

se @@rowcount = 0 e @@error=0

caso contrário, se a atualização falhar por algum motivo, ele tentará uma inserção posteriormente porque o número de linhas em uma instrução com falha é 0

Grande fã do UPSERT, realmente reduz o código para gerenciar.Aqui está outra maneira de fazer isso:Um dos parâmetros de entrada é o ID, se o ID for NULL ou 0, você sabe que é um INSERT, caso contrário é uma atualização.Assume que o aplicativo sabe se existe um ID, portanto não funcionará em todas as situações, mas reduzirá as execuções pela metade se você fizer isso.

Sua lógica parece correta, mas você pode considerar adicionar algum código para evitar a inserção se tiver passado uma chave primária específica.

Caso contrário, se você está sempre inserindo se a atualização não afetou nenhum registro, o que acontece quando alguém exclui o registro antes da execução do "UPSERT"?Agora o registro que você estava tentando atualizar não existe, então ele criará um registro.Esse provavelmente não é o comportamento que você estava procurando.

Postagem modificada de Dima Malenko:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Você pode interceptar o erro e enviar o registro para uma tabela de inserção com falha.
Eu precisava fazer isso porque estamos pegando todos os dados enviados via WSDL e, se possível, corrigindo-os internamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top