Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment implémenter une procédure stockée Upsert conditionnelle ?

J'ai assemblé le script suivant pour prouver cette astuce que j'ai utilisée au cours des années passées. Si vous l'utilisez, vous devrez le modifier en fonction de vos besoins. Les commentaires suivent :

/*
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

L'astuce ici est que vous pouvez définir des valeurs dans des variables locales dans une instruction Update. Ci-dessus, la valeur "drapeau" n'est définie que si la mise à jour fonctionne (c'est-à-dire que les critères de mise à jour sont remplis) ; sinon, il ne sera pas modifié (ici, laissé à zéro), vous pouvez vérifier cela et traiter en conséquence.

En ce qui concerne la transaction et sa sérialisation, j'aimerais en savoir plus sur ce qui doit être encapsulé dans la transaction avant de suggérer comment procéder.

-- Addenda, suivi du deuxième commentaire ci-dessous -----------

Les idées de M. Safran sont un moyen complet et solide d'implémenter cette routine puisque vos clés primaires sont définies à l'extérieur et transmises à la base de données (c'est-à-dire que vous n'utilisez pas de colonnes d'identité - d'accord, elles sont souvent surutilisées).

J'ai fait quelques tests supplémentaires (ajout d'une contrainte de clé primaire sur la colonne ContentId, envelopper UPDATE et INSERT dans une transaction, ajouter l'indice sérialisable à la mise à jour) et oui, cela devrait faire tout ce que vous voulez. L'échec de la mise à jour bloque un verrou de plage sur cette partie de l'index, ce qui bloquera toute tentative simultanée d'insérer cette nouvelle valeur dans la colonne. Bien sûr, si N demandes sont soumises simultanément, la "première" créera la ligne, et elle sera immédiatement mise à jour par la deuxième, la troisième, etc., à moins que vous ne définissiez le "verrou" quelque part le long de la ligne. Bonne astuce !

(Notez que sans l'index sur la colonne clé, vous verrouilleriez toute la table. De plus, le verrou de plage peut verrouiller les lignes de "l'un ou l'autre côté" de la nouvelle valeur - ou peut-être qu'ils ne le feront pas, je ne l'ai pas fait testez celui-là. Cela ne devrait pas avoir d'importance, puisque la durée de l'opération devrait [?] être en millisecondes à un chiffre.)