Suponha uma estrutura de tabela de MyTable(KEY, datafield1, datafield2...)
.
Frequentemente, eu quero atualizar um registro existente ou inserir um novo registro, se ele não existir.
Essencialmente:
IF (key exists)
run update command
ELSE
run insert command
Qual é a melhor maneira de escrever isso?
Respostas:
não se esqueça de transações. O desempenho é bom, mas a abordagem simples (SE EXISTE ..) é muito perigosa.
Quando vários threads tentam executar a inserção ou atualização, você pode facilmente obter uma violação da chave primária.
As soluções fornecidas pelo @Beau Crawford e pelo @Esteban mostram idéias gerais, mas sujeitas a erros.
Para evitar conflitos e violações de PK, você pode usar algo como isto:
ou
fonte
Veja minha resposta detalhada a uma pergunta anterior muito semelhante
O @Beau Crawford's é uma boa maneira no SQL 2005 e abaixo, embora se você estiver concedendo um representante, ele deve ir para o primeiro funcionário a fazê-lo . O único problema é que, para insertos, ainda são duas operações de E / S.
O MS Sql2008 apresenta a
merge
partir do padrão SQL: 2003:Agora é realmente apenas uma operação de E / S, mas um código terrível :-(
fonte
upsert
que quase todos os outros provedores de banco de dados decidiram dar suporte. Aupsert
sintaxe é uma maneira muito mais agradável para fazer isso, então no mínimo MS deveria ter suportado também - não é como se fosse a única palavra-chave não padrão em T-SQLMERGE
sintaxe.HOLDLOCK
de operações de mesclagem em situações de alta simultaneidade.Faça um UPSERT:
http://en.wikipedia.org/wiki/Upsert
fonte
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):
Muitas pessoas sugerem o seguinte:
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:
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:
fonte
UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
Editar:
Infelizmente, mesmo para meu próprio prejuízo, devo admitir que as soluções que fazem isso sem um seleto parecem ser melhores, pois realizam a tarefa com um passo a menos.
fonte
Se você deseja UPSERT mais de um registro por vez, pode usar a instrução DML ANSI SQL: 2003 MERGE.
Confira Imitando a instrução MERGE no SQL Server 2005 .
fonte
Embora seja muito tarde para comentar sobre isso, quero adicionar um exemplo mais completo usando MERGE.
Essas instruções Insert + Update são geralmente chamadas de instruções "Upsert" e podem ser implementadas usando MERGE no SQL Server.
Um exemplo muito bom é dado aqui: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
O exposto acima também explica os cenários de bloqueio e simultaneidade.
Vou citar o mesmo para referência:
fonte
Substitua os nomes de tabela e campo pelo que você precisar. Tome cuidado com a condição ON . Em seguida, defina o valor (e o tipo) apropriado para as variáveis na linha DECLARE.
Felicidades.
fonte
Você pode usar
MERGE
Declaração, esta declaração é usada para inserir dados, se não existir, ou atualizar, se existir.fonte
Se você seguir a rota UPDATE se não houver linhas atualizadas e depois INSERIR, considere fazer o INSERT primeiro para evitar uma condição de corrida (supondo que não haja DELETE)
Além de evitar uma condição de corrida, se na maioria dos casos o registro já existir, isso fará com que o INSERT falhe, desperdiçando a CPU.
O uso do MERGE provavelmente é preferível para o SQL2008 em diante.
fonte
Isso depende do padrão de uso. É preciso olhar para o panorama geral do uso sem se perder nos detalhes. Por exemplo, se o padrão de uso for 99% de atualizações após a criação do registro, o 'UPSERT' é a melhor solução.
Após a primeira inserção (hit), serão todas as atualizações de instrução únicas, sem ifs ou buts. A condição 'where' na inserção é necessária, caso contrário, ela inserirá duplicatas e você não deseja lidar com o bloqueio.
fonte
O MS SQL Server 2008 apresenta a instrução MERGE, que acredito ser parte do padrão SQL: 2003. Como muitos demonstraram, não é grande coisa lidar com casos de uma linha, mas ao lidar com grandes conjuntos de dados, é necessário um cursor, com todos os problemas de desempenho que surgem. A declaração MERGE será uma adição muito bem-vinda ao lidar com grandes conjuntos de dados.
fonte
Antes que todo mundo pule para o HOLDLOCK-s por medo desses usuários nefastos executando seus sprocs diretamente :-), deixe-me salientar que você deve garantir a exclusividade dos novos PK-s por design (chaves de identidade, geradores de sequência no Oracle, índices exclusivos para IDs externos, consultas cobertas por índices). Esse é o alfa e o ômega do problema. Se você não tiver isso, nenhum HOLDLOCK-s do universo salvará você e, se você tiver, não precisará de nada além de UPDLOCK na primeira seleção (ou para usar a atualização primeiro).
Os Sprocs normalmente são executados sob condições muito controladas e com a suposição de um chamador confiável (camada intermediária). Isso significa que, se um padrão simples de upsert (atualização + inserção ou mesclagem) vir uma PK duplicada, isso significa um bug no design da camada intermediária ou da tabela e é bom que o SQL grite uma falha nesse caso e rejeite o registro. Colocar um HOLDLOCK nesse caso equivale a comer exceções e receber dados potencialmente defeituosos, além de reduzir seu desempenho.
Dito isto, usar MERGE ou UPDATE e INSERT é mais fácil no servidor e menos propenso a erros, pois você não precisa se lembrar de adicionar (UPDLOCK) à primeira seleção. Além disso, se você estiver inserindo / atualizando em pequenos lotes, precisará conhecer seus dados para decidir se uma transação é apropriada ou não. É apenas uma coleção de registros não relacionados, e as transações adicionais "envolventes" serão prejudiciais.
fonte
As condições da corrida realmente importam se você tentar primeiro uma atualização seguida por uma inserção? Digamos que você tenha dois threads que desejam definir um valor para a chave da chave :
Tópico 1: valor = 1
Tópico 2: valor = 2
Exemplo de cenário de condição de corrida
O outro encadeamento falha com a inserção (com chave duplicada de erro) - encadeamento 2.
Mas; em um ambiente multithread, o agendador do SO decide a ordem de execução do encadeamento - no cenário acima, onde temos essa condição de corrida, foi o SO que decidiu a sequência de execução. Ou seja: É errado dizer que "thread 1" ou "thread 2" foi "primeiro" do ponto de vista do sistema.
Quando o tempo de execução é tão próximo para o segmento 1 e o segmento 2, o resultado da condição de corrida não importa. O único requisito deve ser que um dos encadeamentos defina o valor resultante.
Para a implementação: Se a atualização seguida pela inserção resultar no erro "chave duplicada", isso deve ser tratado como êxito.
Além disso, é claro que nunca se deve assumir que o valor no banco de dados é o mesmo que você escreveu por último.
fonte
No SQL Server 2008, você pode usar a instrução MERGE
fonte
Eu tentei a solução abaixo e funciona para mim, quando ocorre solicitação simultânea de instrução de inserção.
fonte
Você pode usar esta consulta. Trabalhe em todas as edições do SQL Server. É simples e claro. Mas você precisa usar 2 consultas. Você pode usar se não puder usar MERGE
NOTA: Por favor, explique as respostas negativas
fonte
Se você usa o ADO.NET, o DataAdapter lida com isso.
Se você quiser lidar com isso sozinho, é assim:
Verifique se há uma restrição de chave primária na sua coluna de chave.
Então você:
Você também pode fazer o contrário, ou seja, faça a inserção primeiro e faça a atualização se a inserção falhar. Normalmente, a primeira maneira é melhor, porque as atualizações são feitas com mais frequência do que as inserções.
fonte
Fazer um if existe ... else ... envolve fazer no mínimo duas solicitações (uma para verificar, uma para executar). A abordagem a seguir requer apenas uma onde o registro existe, duas se uma inserção for necessária:
fonte
Eu costumo fazer o que vários dos outros pôsteres disseram com relação a verificar se ele existe primeiro e depois fazer o que for o caminho correto. Uma coisa que você deve se lembrar ao fazer isso é que o plano de execução armazenado em cache pelo sql pode não ser o ideal para um caminho ou outro. Acredito que a melhor maneira de fazer isso é chamar dois procedimentos armazenados diferentes.
Agora, não sigo meu próprio conselho com muita frequência, então tome-o com um grão de sal.
fonte
Faça uma seleção, se você obtiver um resultado, atualize-o; caso contrário, crie-o.
fonte