sql >> Base de Datos >  >> RDS >> Sqlserver

¿Cómo implementar un procedimiento almacenado Upsert condicional?

Preparé el siguiente guión para probar este truco que usé en años anteriores. Si lo usa, deberá modificarlo para adaptarlo a sus propósitos. Siguen los comentarios:

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

El truco aquí es que puede establecer valores en variables locales dentro de una declaración de actualización. Arriba, el valor de "bandera" se establece solo si la actualización funciona (es decir, se cumplen los criterios de actualización); de lo contrario, no se cambiará (aquí, dejado en nulo), puede verificarlo y procesarlo en consecuencia.

En cuanto a la transacción y hacerla serializable, me gustaría saber más sobre lo que se debe encapsular dentro de la transacción antes de sugerir cómo proceder.

-- Addenda, seguimiento del segundo comentario a continuación -----------

Las ideas del Sr. Saffron son una forma completa y sólida de implementar esta rutina, ya que sus claves primarias se definen fuera y se pasan a la base de datos (es decir, no está usando columnas de identidad; me parece bien, a menudo se usan en exceso).

Hice algunas pruebas más (agregué una restricción de clave principal en la columna ContentId, envolví ACTUALIZAR e INSERTAR en una transacción, agregué la sugerencia serializable a la actualización) y sí, eso debería hacer todo lo que desea. La actualización fallida aplica un bloqueo de rango en esa parte del índice, y eso bloqueará cualquier intento simultáneo de insertar ese nuevo valor en la columna. Por supuesto, si se envían N solicitudes simultáneamente, la "primera" creará la fila, y la segunda, la tercera, etc. la actualizarán inmediatamente, a menos que configure el "bloqueo" en algún lugar de la línea. ¡Buen truco!

(Tenga en cuenta que sin el índice en la columna clave, bloquearía toda la tabla. Además, el bloqueo de rango puede bloquear las filas en "cualquier lado" del nuevo valor, o tal vez no lo hagan, no lo hice pruébalo. No debería importar, ya que la duración de la operación debería [?] estar en milisegundos de un solo dígito).