Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como implementar um procedimento armazenado condicional Upsert?


Eu montei o script a seguir para provar esse truque que usei nos últimos anos. Se você usá-lo, precisará modificá-lo para atender às suas finalidades. Seguem comentários:
/*
CREATE TABLE Item
 (
   Title      varchar(255)  not null
  ,Teaser     varchar(255)  not null
  ,ContentId  varchar(30)  not null
  ,RowLocked  bit  not null
)


UPDATE item
 set RowLocked = 1
 where ContentId = 'Test01'

*/


DECLARE
  @Check varchar(30)
 ,@pContentID varchar(30)
 ,@pTitle varchar(255)
 ,@pTeaser varchar(255)

set @pContentID = 'Test01'
set @pTitle     = 'TestingTitle'
set @pTeaser    = 'TestingTeasier'

set @check = null

UPDATE dbo.Item
 set
   @Check = ContentId
  ,Title  = @pTitle
  ,Teaser = @pTeaser
 where ContentID = @pContentID
  and RowLocked = 0

print isnull(@check, '<check is null>')

IF @Check is null
    INSERT dbo.Item (ContentID, Title, Teaser, RowLocked)
     values (@pContentID, @pTitle, @pTeaser, 0)

select * from Item

O truque aqui é que você pode definir valores em variáveis ​​locais dentro de uma instrução Update. Acima, o valor "flag" é definido apenas se a atualização funcionar (ou seja, os critérios de atualização forem atendidos); caso contrário, ele não será alterado (aqui, deixado em null), você pode verificar isso e processar de acordo.

Quanto à transação e torná-la serializável, gostaria de saber mais sobre o que deve ser encapsulado dentro da transação antes de sugerir como proceder.

-- Adendos, continuação do segundo comentário abaixo -----------

As idéias do Sr. Saffron são uma maneira completa e sólida de implementar essa rotina, já que suas chaves primárias são definidas fora e passadas para o banco de dados (ou seja, você não está usando colunas de identidade - tudo bem para mim, elas são frequentemente usadas em excesso).

Fiz mais alguns testes (adicionei uma restrição de chave primária na coluna ContentId, envolva o UPDATE e INSERT em uma transação, adicionei a dica serializável à atualização) e sim, isso deve fazer tudo o que você deseja. A atualização com falha aplica um bloqueio de intervalo nessa parte do índice e isso bloqueará todas as tentativas simultâneas de inserir esse novo valor na coluna. Obviamente, se N solicitações forem enviadas simultaneamente, a "primeira" criará a linha e será atualizada imediatamente pela segunda, terceira etc. - a menos que você defina o "bloqueio" em algum lugar ao longo da linha. Bom truque!

(Observe que sem o índice na coluna de chave, você bloquearia a tabela inteira. Além disso, o bloqueio de intervalo pode bloquear as linhas em "ambos os lados" do novo valor - ou talvez não, eu não teste esse. Não deve importar, pois a duração da operação deve ser [?] em milissegundos de um dígito.)