Pergunta

Suponha uma estrutura de tabela de MyTable(KEY, datafield1, datafield2...).

Muitas vezes eu querer ou atualização de um registro existente ou inserir um novo registro se ele não existe.

Essencialmente:

IF (key exists)
  run update command
ELSE
  run insert command

O que é a melhor maneira de executar a escrever este?

Foi útil?

Solução

não se esqueça sobre transações. O desempenho é bom, mas simples (se houver ..) abordagem é muito perigoso.
Quando vários segmentos irá tentar executar Insert-ou-update você pode facilmente obter violação de chave primária.

As soluções oferecidas por @Beau Crawford & @Esteban mostrar ideia geral, mas propenso a erros.

Para evitar impasses e as violações de PK você pode usar algo como isto:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

ou

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

Outras dicas

Ver o meu resposta detalhada a uma pergunta anterior muito semelhante

de

@Beau Crawford é uma boa maneira no SQL 2005 e abaixo, mas se você está concedendo rep ele deve ir para o primeiro cara para SO-lo . O único problema é que para inserções ainda é duas operações de IO.

MS SQL2008 introduz merge do SQL: 2003 standard:

merge tablename with(HOLDLOCK) 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 IO, mas o código terrível: - (

Faça um UPSERT:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

Muitas pessoas vão sugerir que você use MERGE, mas eu adverti-lo contra ela. Por padrão, ele não protegê-lo de condições de concorrência e raça mais do que várias instruções, mas não introduzir outros perigos:

http://www.mssqltips.com/sqlservertip/ 3074 / uso-cuidado-com-sql-servidores-merge-statement /

Mesmo com este "simples" sintaxe disponível, eu ainda preferem esta abordagem (manipulação de erro omitido por 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;

Um monte de gente vai sugerir desta maneira:

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 isto consegue é garantir que você pode precisar de ler a tabela duas vezes para localizar a linha (s) a ser atualizado. Na primeira amostra, você só vai precisar localizar a linha (s) uma vez. (Em ambos os casos, se nenhuma linha são encontrados a partir da leitura inicial, uma inserção ocorre.)

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 nenhuma outra razão do que deixar exceções captura SQL Server que você poderia ter evitado, em primeiro lugar é muito mais caro, exceto no cenário raro onde quase todos os inserção falha. I provar tanto aqui:

IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Editar:

Infelizmente, mesmo ao meu próprio prejuízo, devo admitir as soluções que fazem isso sem um seleto parecem ser melhor, uma vez que realizar a tarefa com menos um passo.

Se você quiser UPSERT mais de um registro de cada vez que você pode usar o ANSI SQL:., 2003 instrução DML MERGE

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

Confira Imitando Declaração MERGE no SQL Server 2005 .

Apesar de sua muito tarde a comentar este eu quero adicionar um exemplo mais completo usando MERGE.

Essas declarações Insert + atualização são geralmente chamados de declarações "Upsert" e pode ser implementado usando MERGE no SQL Server.

Um bom exemplo é dado aqui: http: //weblogs.sqlteam .com / Dang / Arquivo / 2009/01/31 / UPSERT-Race-Estado-Com-MERGE.aspx

O acima explica o bloqueio e cenários de concorrência também.

Eu estarei citando o mesmo para referência:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

Substituir nomes de tabela e campo por tudo o que você precisa. Tome cuidado com a usando ON condição. Em seguida, defina o valor apropriado (e tipo) para as variáveis ??na linha DECLARE.

Felicidades.

Você pode usar Declaração MERGE, esta afirmação é usada para inserir dados se não existe ou atualização se existe.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

Se vai a atualização se-no-linhas atualizado rota seguida, INSERT, considere fazer o INSERT primeiro para evitar uma condição de corrida (assumindo que não há intervenção EXCLUIR)

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

Além de evitar uma condição de corrida, se na maioria dos casos o registro já existe, então isso vai fazer com que o INSERT para falhar, desperdiçando CPU.

Usando MERGE provavelmente preferível para SQL2008 em diante.

No SQL Server 2008, você pode usar a instrução MERGE

Isso depende do padrão de uso. Um tem que olhar para o retrato grande de uso, sem se perder nos detalhes. Por exemplo, se o padrão de uso é de 99% atualizações após o registro foi criado, em seguida, o 'UPSERT' é a melhor solução.

Depois da primeira inserção (hit), será todas as atualizações dos relatórios individuais, sem reticências. A condição 'onde' no encarte é necessário caso contrário ele irá inserir duplicatas, e você não quer lidar com bloqueio.

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END

MS SQL Server 2008 introduz a instrução MERGE, que eu acredito que é parte do SQL: 2003. Como muitos têm mostrado que não é um grande negócio para lidar com um casos fila, mas quando se trata de grandes conjuntos de dados, é necessário um cursor, com todos os problemas de desempenho que vêm junto. A instrução MERGE será muito bem-vinda adição ao lidar com grandes conjuntos de dados.

Antes de todo mundo pula para HOLDLOCK-S por medo de esses usuários nafarious executando o seu sprocs diretamente :-) deixe-me salientar que você tem que singularidade garantia de nova PK-S pelo projeto (identidade teclas, geradores de sequência no Oracle, índices únicos para bilhetes de identidade, s externos, consultas cobertos por índices). Esse é o alfa e omega da questão. Se você não tem isso, não HOLDLOCK-s do universo estão indo para salvar você e se você não tem isso, então você não precisa de nada além UPDLOCK na primeira escolha (ou a atualização primeira utilização).

Sprocs normalmente executada sob condições muito controladas e com o pressuposto de um chamador de confiança (meados de camada). O que significa que se um padrão upsert simples (update + inserção ou fusão) nunca vê PK duplicado que significa um bug em seu mid-tier ou tabela de projeto e é bom que o SQL vai gritar uma falha em tal caso e rejeitar o registro. Colocar uma HOLDLOCK neste caso é igual a comer exceções, e tendo em dados potencialmente defeituosas, além de reduzir o seu perf.

Dito isto, Usando MERGE ou UPDATE em seguida, insira é mais fácil em seu servidor e menos propenso a erros desde que você não tem que lembrar de adicionar (UPDLOCK) a primeira escolha. Além disso, se você está fazendo inserções / atualizações em pequenos lotes que você precisa saber os seus dados, a fim de decidir se uma transação é apropriado ou não. Ele é apenas uma coleção de registros não relacionados, em seguida, adicional transação "envolvente" será prejudicial.

Será que as condições de corrida realmente importa se você tente primeiro uma atualização seguido de uma inserção? Vamos dizer que você tem dois tópicos que deseja definir um valor para a chave tecla :

Thread 1: valor = 1 | Thread 2: valor = 2

cenário Exemplo condição de corrida

  1. tecla não está definido
  2. Thread 1 falha com update
  3. Thread 2 falha com update
  4. Exactamente uma rosca de uma ou de fios de dois sucede com o inserto. Por exemplo. Thread 1
  5. O outro segmento falhar com inserção (com erro de chave duplicada.) - rosca 2

    • Resultado:. O "primeiro" dos dois passos para inserir, decide valor
    • resultado Wanted: O último dos 2 fios para gravar dados (atualização ou inserção) deve decidir o valor

Mas; em um ambiente multithread, o programador OS decide sobre a ordem da execução do thread - no cenário acima, onde temos essa condição de corrida, foi o sistema operacional que decidiu sobre a seqüê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 perto para a linha 1 e linha 2, o resultado da condição de corrida não importa. O único requisito deve ser que um dos tópicos deve definir o valor resultante.

Para a implementação: Se update seguido por inserção resulta em erro "chave duplicada", este deve ser tratado como sucesso.

Além disso, deve-se, naturalmente, nunca assumir que o valor no banco de dados é o mesmo que o valor que você escreveu passado.

Eu tinha tentado abaixo solução e ele funciona para mim, quando pedido simultâneo para instrução de inserção ocorre.

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran

Você pode usar essa consulta. Trabalho em todas as edições do SQL Server. É simples e clara. Mas você precisa usar 2 consultas. Você pode usar se você não pode usar MERGE

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

NOTA: Por favor, explique negativos resposta

Se você usar ADO.NET, o DataAdapter lida com isso.

Se você quiser lidar com isso mesmo, este é o caminho:

Certifique-se há uma restrição de chave primária em sua coluna de chave.

Então você:

  1. Faça o update
  2. Se a atualização falhar porque um registro com a chave já existe, fazer a inserção. Se a atualização não falha, você está acabado.

Você também pode fazê-lo de outra maneira, ou seja, fazer a inserção primeira, e fazer a atualização se a inserção falha. Normalmente, a primeira maneira é melhor, porque as atualizações são feitas mais frequentemente do que inserções.

Fazendo uma se existe ... else ... envolve fazer dois pedidos mínimos (um para verificar, uma para agir). A seguinte abordagem requer apenas um, onde existe o registro, dois se uma inserção é necessária:

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')

Eu costumo fazer o que vários dos outros cartazes disse no que diz respeito à verificação de que existente em primeiro lugar e, em seguida, fazer o que o caminho correto é. Uma coisa que você deve se lembrar quando fazer isso é que o plano de execução em cache pelo SQL poderia ser não-ótima para um caminho ou outro. Eu acredito que a melhor maneira de fazer isso é chamar dois procedimentos armazenados diferentes.

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

Agora, eu não seguir o meu próprio conselho, muitas vezes, para levá-lo com um grão de sal.

Faça um seleto, se você conseguir um resultado, atualizá-lo, se não, criá-lo.

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