Implementação do SQL Server 2005 do MySQL REPLACE INTO?

86

O MySQL tem esse REPLACE INTOcomando SQL incrivelmente útil, porém proprietário .

Isso pode ser facilmente emulado no SQL Server 2005?

Iniciar uma nova Transação, fazer um Select()e então ou UPDATEou INSERTe COMMITsempre é um pouco chato, especialmente ao fazer isso no aplicativo e, portanto, sempre mantendo 2 versões da instrução.

Eu me pergunto se existe uma maneira fácil e universal de implementar essa função no SQL Server 2005?

Michael Stum
fonte

Respostas:

60

Isso é algo que me irrita no MSSQL ( discurso retórico no meu blog ). Desejo suporte a MSSQLupsert .

O código de @Dillie-O é uma boa maneira em versões mais antigas do SQL (+1 voto), mas ainda é basicamente duas operações de IO (o existse depois o updateouinsert )

Há uma maneira um pouco melhor neste post , basicamente:

--try an update
update tablename 
set field1 = 'new value',
    field2 = 'different value',
    ...
where idfield = 7

--insert if failed
if @@rowcount = 0 and @@error = 0
    insert into tablename 
           ( idfield, field1, field2, ... )
    values ( 7, 'value one', 'another value', ... )

Isso reduz a uma operação de IO se for uma atualização, ou duas se for uma inserção.

MS Sql2008 apresenta a mergepartir do padrão SQL: 2003:

merge tablename as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Agora é realmente apenas uma operação de IO, mas um código horrível :-(

Keith
fonte
Ótimo, obrigado! Salva o Select e muitas vezes nem precisa de uma transação em situações em que posso ter certeza que entre o Update e o "meu" insert, não existe outro insert para aquela chave.
Michael Stum
2
@Michael É melhor você ter um índice exclusivo nesta tabela e lidar com erros de chave duplicada se for usar esta solução.
Sam Saffron
3
@Keith Sua declaração de mesclagem não funciona. MERGEnão oferece suporte à WHEREcláusula, você deve reescrever usando USINGe ON. Além disso, a menos que você adicione WITH (HOLDLOCK), há uma corrida e INSERTs concorrentes podem acontecer, com um deles falhando devido ao conflito de chave.
Evgeniy Berezovsky
Sim, como apontado aqui: weblogs.sqlteam.com/dang/archive/2009/01/31/… MERGE não é atômico. Ele tira um bloqueio de atualização implícito, mas o libera antes de executar uma inserção, o que causa uma condição de corrida que pode resultar em violações de chave primária. Você deve usar um HOLDLOCK explícito além do UPDLOCK implícito para que a operação seja atômica. Tal como está, não é atômico, apesar de parecer ser uma declaração única.
Triynko
1
A sintaxe MERGE está errada e foi corrigida em uma resposta mais recente do mesmo autor: stackoverflow.com/a/243670/24472
Larry
21

A funcionalidade que você está procurando é tradicionalmente chamada de UPSERT. Pelo menos saber como é chamado pode ajudá-lo a encontrar o que procura.

Não acho que o SQL Server 2005 tenha boas maneiras de fazer isso. 2008 apresenta a instrução MERGE que pode ser usada para fazer isso, conforme mostrado em: http://www.databasejournal.com/features/mssql/article.php/3739131 ou http://blogs.conchango.com/davidportas/archive/ 14/11/2007 / SQL-Server-2008-MERGE.aspx

O Merge estava disponível na versão beta de 2005, mas foi removido na versão final.

Karl Seguin
fonte
18

O que o upsert / merge está fazendo é algo no sentido de ...

IF EXISTS (SELECT * FROM [Table] WHERE Id = X)
   UPDATE [Table] SET...
ELSE
   INSERT INTO [Table]

Portanto, esperançosamente, a combinação desses artigos e este pseudo código pode fazer as coisas andarem.

Dillie-O
fonte
10

Eu escrevi uma postagem no blog sobre esse problema.

O resultado final é que se você deseja atualizações baratas e deseja estar seguro para uso simultâneo, tente:

update t
set hitCount = hitCount + 1
where pk = @id

if @@rowcount < 1 
begin 
   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
end

Desta forma, você tem 1 operação para atualizações e um máximo de 3 operações para inserções. Portanto, se você costuma atualizar, esta é uma opção segura e barata.

Eu também teria muito cuidado para não usar nada que não seja seguro para uso simultâneo. É realmente fácil obter violações de chave primária ou linhas duplicadas na produção.

Sam Saffron
fonte