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).