A continuación comparto la plantilla de código para la creación de procedimientos almacenados en SQL Server 2005/2008 con control de transacciones que utilizamos en la empresa:
CREATEPROCEDURE [dbo].[Objeto_Accion]
-- Parámetros de salida
[<@paramOUT> int OUTPUT[, <@paramOUTMSG> varchar(255) OUTPUT]
-- Parámetros de entrada (obligatorios)
<@param1> <datatype>,<@param2> <datatype>, ...
-- Parámetros de entrada (opcionales)
<@param11> <datatype> = <default value>,<@param12> <datatype> = <default value>, ...
AS
-- Bloque de declaración de variablesDECLARE<@ValorDevuelto> int [DECLARE<@MensajeDevuelto> varchar(255)]
-- Bloque de declaración de constantes
DECLARE<@ValorDevuelto> int
BEGIN-- Bloque de sentencias SELECT
-- Asignación de variables.-- Validaciones. -- Condición de salida previa
[IF<condición de salida previa> BEGINSET<@ValorDevuelto> = -1 [SET<@MensajeDevuelto> = 'Detalle de condición de salida'] [SET<@paramOUT> = <@ValorDevuelto>] [SET<@paramOUTMSG> = <@MensajeDevuelto>]SELECT<@ValorDevuelto>[, <@MensajeDevuelto>]
RAISEERROR (50001, 25, ‘Salida sin ejecución’)
-- Salida sin ejecución
RETURN(-1)END …]
[SET TRANSACTION ISOLATION LEVEL ]
SET XACT_ABORT ONBEGIN TRYBEGINTRANSACTION-- Bloque de sentencias DML
-- ...-- ...-- Recuperar el ID que nos interesa devolver a la aplicación
SET<@ValorDevuelto> = SCOPE_IDENTITY()-- Bloque de auditoria
SET ... INSERT INTO AuditoriaCOMMITTRANSACTION [SET<@MensajeDevuelto> = 'Mensaje de ejecución OK'] [SET<@paramOUT> = <@ValorDevuelto>] [SET<@paramOUTMSG> = <@MensajeDevuelto>]SELECT<@ValorDevuelto>[, <@MensajeDevuelto>]-- Salida con ejecución exitosa
RETURN (1)END TRYBEGIN CATCH-- Obtener info del error. Escribir en tabla de track de Errores
SET @ERROR_NUMBER = ERROR_NUMBER() SET @ERROR_SEVERITY = ERROR_SEVERITY() SET @ERROR_STATE = ERROR_STATE() SET @ERROR_LINE = ERROR_LINE() SET @ERROR_PROCEDURE = ERROR_PROCEDURE() SET @ERROR_MESSAGE = ERROR_MESSAGE()
-- Test XACT_STATE()
-- If 1, la XACT es "committable". COMMIT-- If -1, la XACT es "uncommittable". ROLLBACK-- If 0, no hay XACT abierta.IF (XACT_STATE()) = -1ROLLBACKTRANSACTIONIF (XACT_STATE()) = 1COMMITTRANSACTION
-- Ante error SQL se devuelve a la aplicación el ID del registro conteniendo el error en la tabla ErrorInfo
-- INSERT INTOErrorInfo-- (ErrorNumber, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure, ErrorMessage)-- VALUES-- (@ERROR_NUMBER, @ERROR_SEVERITY, @ERROR_STATE, @ERROR_LINE, @ERROR_PROCEDURE, @ERROR_MESSAGE)-- RAISEERROR (@ERROR_NUMBER, @ERROR_SEVERITY, @ERROR_MESSAGE);
SET<@ValorDevuelto> = SCOPE_IDENTITY() [SET<@MensajeDevuelto> = {'Detalle del Error' | @ERROR_MESSAGE | ...}] [SET<@paramOUT> = <@ValorDevuelto>] [SET<@paramOUTMSG> = <@MensajeDevuelto>]
SELECT<@ValorDevuelto>[, <@MensajeDevuelto>]-- Salida con error
RETURN (0) END CATCHEND GO
Paso a explicar en detalle las secciones más importantes de la plantilla.
Parámetros de salida
[<@paramOUT> OUTPUT[, <@paramOUTMSG> OUTPUT]]
Si bien figura como un bloque opcional, es aconsejable devolver tanto el valor de salida del procedimiento como el mensaje explicativo de éxito o error como parámetros de salida además de como parte del SELECT de resultados.
Bloque de sentencias SELECT
En este bloque deben incluirse todas las asignaciones de variables de trabajo que puedan consultarse en forma independiente a la transacción. De esta manera se reduce el tiempo de bloqueos exclusivos requerido por el procedimiento.
Si existen variables cuyos valores influyen directamente en la transacción deberán asignarse dentro del bloque BEGIN TRANSACTION … END TRANSACTION configurando para ello el nivel de aislamiento deseado previo al mismo:
[SETTRANSACTIONISOLATIONLEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE | SNAPSHOT}]
Condición de salida previa
Si el procedimiento debe incluir algunas verificaciones para detectar casos que impidan la ejecución del mismo, las mismas deben programarse de acuerdo al esquema propuesto en la plantilla. En este caso <@ValorDevuelto> = -1 y <@MensajeDevuelto> puede contener un detalle de porque se abandonó el procedimiento sin ejecutarlo.
El valor de retorno puede ser 0 ó –1. Este último caso permitiría diferenciar el estado de ejecución del procedimiento:
- “1” indicaría ejecución correcta.
- “<1” indicaría ejecución incorrecta.
- “0” ejecución con error.
- “-1” no ejecución por pre-condición no cumplida.
SET XACT_ABORT ON
El uso de esta sentencia ha generado mucha discusión en nuestras reuniones de capacitación. Finalmente concluimos que este valor es indispensable configurar previo a la ejecución todo bloque BEGIN TRY / BEGIN TRANSACTION … COMMIT TRANSACTION / END TRY.
La explicación más exacta del por qué el uso de esta configuración, es porque su utilización permite que cualquier error que se produzca dentro del bloque BEGIN TRY … END TRY marque como inválida a la transacción.
Bloque de sentencias DML
Siempre dentro del bloque BEGIN TRY / BEGIN TRANSACTION … COMMIT TRANSACTION / END TRY.
Bloque de auditoria
Si no hay implementado algún otro mecanismo de auditoria (aplicación, desencadenadores, etc.) se lo debe incluir dentro del bloque BEGIN TRY / BEGIN TRANSACTION … COMMIT TRANSACTION / END TRY.
Bloque BEGIN CATCH… END CATCH
Captura errores producidos dentro del bloque BEGIN TRY … END TRY y confirma o deshace la transacción de acuerdo al valor de la función XACT_STATE().
Plantilla Simple con devolución de errores únicamente por RETURN.
CREATEPROCEDURE [dbo].[Objeto_Accion]
-- Parámetros de entrada (obligatorios)
<@param1> <datatype>,<@param2> <datatype>, ...
-- Parámetros de entrada (opcionales)
<@param11> <datatype> = <default value>,<@param12> <datatype> = <default value>, ...
AS
-- Bloque de declaración de variables-- Bloque de declaración de constantes
BEGIN-- Bloque de sentencias SELECT
-- Asignación de variables, validaciones, etc.
SET XACT_ABORT ONBEGIN TRYBEGINTRANSACTION-- Bloque de sentencias DML
-- Bloque de auditoria
COMMITTRANSACTION-- Salida con ejecución exitosa
RETURN (1)END TRYBEGIN CATCH
-- Test XACT_STATE()
IF (XACT_STATE()) = -1ROLLBACKTRANSACTIONIF (XACT_STATE()) = 1COMMITTRANSACTION-- Salida con error
RETURN (0) END CATCHEND GO