Buscar este blog

miércoles, 30 de noviembre de 2011

Identificación de Errores - Construcciones Try Catch..

Amigos,

Voy a hablarles de una herramienta que el Sql Server nos ofrece desde su versión 2005, y que lamentablemente, no es utilizada con la asiduidad que debiera.

Se trata de las rutinas de Try Catch, veamos...

Supongamos que, dentro de un Store Procedure, queremos identificar y guardar el detalle de un error que se produzca en tiempo de ejecución del sp...

Hasta la versión 2000 del sql, sólo contábamos con la variable del sistema @@ERROR, la cual nos devuelve un nº de error, solo si, la instrucción inmediatamente anterior lo produce.

Ejemplo:

USE AdventureWorks2008R2;
GO
UPDATE HumanResources.EmployeePayHistory
SET PayFrequency = 4
WHERE BusinessEntityID = 1;
IF @@ERROR = 547
PRINT N'A 'Violación de Constraint.';
GO

El gran problema de @@ERROR es que se borra y se restablece con cada instrucción ejecutada... El valor que nos indica el nº de error no permanece... debemos guardarlo en una variable y en tal caso analizarlo luego.

Por suerte Microsoft se acordó de nosotros..., de quienes tenemos extensas rutinas programadas las cuales requieren de algo similar a lo que en C# O C++ es un control de excepciones...

Preciamente TRY...CATCH es un mecanismo de control de errores para T-SQL. Su funcionamiento es sencillo... Se incluyen un grupo de intrucciones dentro de un bloque TRY. Si se produce un error dentro del bloque TRY, el control es trasferido a otro grupo de intrucciones incluido en el bloque CATCH.

Algo muy interesante de este mecanismo es que nos da la posibilidad de utilizar funciones del sistema adicionales que permiten obtener distintos parámetros relacionados con el error, hablo de las funciones sgtes:

ERROR_NUMBER() devuelve el número del error.

ERROR_SEVERITY() devuelve la gravedad.

ERROR_STATE() devuelve el número de estado del error.

ERROR_PROCEDURE() devuelve el nombre del procedimiento almacenado o desencadenador donde se produjo el error.

ERROR_LINE() devuelve el número de línea de la rutina que provocó el error.

ERROR_MESSAGE() devuelve el texto completo del mensaje de error. Este texto incluye los valores suministrados para los parámetros reemplazables, como longitudes, nombres de objetos u horas

Pero mejor.. veamos un ejemplo práctico ...

EJEMPLO Nº 1

------------------------------------------------------------
-- Descipción: Store Procedure con Catcheo de Errores --
-- Autor: Gustavo Herrera --
-- Sql Server Para Todos
------------------------------------------------------------
Create Procedure [dbo].[sp_eg_trycatch] as
Begin
BEGIN TRY
-- Blog de Instrucciones del Sp, en este caso error forzado
Select 1/0
END TRY

BEGIN CATCH
-- Declara Variables p/Catcheo de Errores
DECLARE
@ErrorMessage NVARCHAR(4000),
@ErrorSeverity INT,
@ErrorState INT,
@ErrorNumber INT,
@ErrorLine INT

-- Guarda Errores en Variables
SET @ErrorNumber = ERROR_NUMBER();
SET @ErrorMessage = ERROR_MESSAGE();
SET @ErrorSeverity = ERROR_SEVERITY();
SET @ErrorState = ERROR_STATE();
SET @ErrorLine = ERROR_LINE();

-- Inserta el log de errores en una tabla
INSERT INTO BD.dbo.errores
(Nombre, Fecha, [Error_Number],[Error_Message], [Error_Line])
VALUES ('Nombre sp', getdate(), @ErrorNumber,
@ErrorMessage, @ErrorLine)
END CATCH;
End
RETURN


Y la cosa aún mejor más... Qué les parece el controlar la cantidad de intentos de ejecución ante un eventual error?

Si sres... en este blog, para todo hay un ejemplo, vamos con El...

EJEMPLO Nº 2

------------------------------------------------------------
-- Descipción: Store Procedure con Catcheo de Errores --
-- Autor: Gustavo Herrera --
-- Sql Server para todos --
-- ---------------------------------------------------------
Create Procedure [dbo].[sp_eg_trycatch_con_reintentos] as
BEGIN
Declare
@retry tinyint,
@retrymax tinyint,
@retrycount tinyint;

set @retry = 1;
set @retrymax = 1;
set @retrycount = 0;

-- Se ejecuta el sp hasta el nº max de reintentos
WHILE @retry = 1 and @retrycount <= @retrymax


BEGIN

SET @retry = 0;
BEGIN TRY
-- codigo del sp --
Codigo del Store_Procedure
END TRY

BEGIN CATCH
BEGIN
SET @retrycount = @retrycount + 1;
IF @retrycount = 2
-- Sigue el error habiendo alcanzado el max.nº de reintentos
BEGIN
Declare
@ErrorMessage NVARCHAR(4000),
@ErrorSeverity INT,
@ErrorState INT,
@ErrorNumber INT,
@ErrorLine INT

-- Guarda Errores en Variables
SET @ErrorNumber = ERROR_NUMBER();
SET @ErrorMessage = ERROR_MESSAGE();
SET @ErrorSeverity = ERROR_SEVERITY();
SET @ErrorState = ERROR_STATE();
SET @ErrorLine = ERROR_LINE();

-- Inserta el log de errores en una tabla
INSERT INTO BD.dbo.errores
(Nombre, Fecha, [Error_Number], [Error_Message], Error_Line])
VALUES ('Nombre sp', getdate(), @ErrorNumber,
@ErrorMessage, @ErrorLine)
EXIT
END
ELSE
-- Va por un nuevo reintento de ejecución
BEGIN
SET @retry = 1;
WAITFOR DELAY '00:02:01'
END
END
END CATCH;
END
RETURN
End

Observaciones:

Una construcción TRY...CATCH detecta todos los errores de ejecución que tienen una gravedad mayor de 10 y que no cierran la conexión de la base de datos.

Un bloque TRY debe ir seguido inmediatamente por un bloque CATCH asociado. Si se incluye cualquier otra instrucción entre las instrucciones END TRY y BEGIN CATCH se genera un error de sintaxis.

Una construcción TRY…CATCH no puede abarcar varios lotes. Una construcción TRY…CATCH no puede abarcar varios bloques de instrucciones Transact-SQL. Por ejemplo, una construcción TRY…CATCH no puede abarcar dos bloques BEGIN…END de instrucciones Transact-SQL y no puede abarcar una construcción IF…ELSE.

Si no hay errores en el código incluido en un bloque TRY, cuando la última instrucción de este bloque ha terminado de ejecutarse, el control se transfiere a la instrucción inmediatamente posterior a la instrucción END CATCH asociada. Si hay un error en el código incluido en un bloque TRY, el control se transfiere a la primera instrucción del bloque CATCH asociado. Si la instrucción END CATCH es la última instrucción de un procedimiento almacenado o desencadenador, el control se devuelve a la instrucción que llamó al procedimiento almacenado o activó el desencadenador.

Cuando finaliza el código del bloque CATCH, el control se transfiere a la instrucción inmediatamente posterior a la instrucción END CATCH. Los errores capturados por un bloque CATCH no se devuelven a la aplicación que realiza la llamada


Errores no afectados por la construcción Try Catch:

Advertencias o mensajes informativos que tienen una gravedad 10 o inferior.

Cuando el admin. del sistema finaliza la sesión mediante la instrucción KILL.

Cuando se producen errores de sintaxis o de resolución de nombres en el mismo nivel de ejecución de una construcción Try Catch

Respecto a este último punto bien vale un ejemplo:

-- Ejemplo error no catcheable:

USE AdventureWorks2008R2;
GO

BEGIN TRY
-- Tabla no existe, error de resolución de objeto
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH

Para hacer a este error catcheable, debemos incluirlo en un sp, ejecutando la
intrucción Select dentro del mismo. De este modo, al ejecutar el sp, el error
se produce en un nivel inferior a la construcción Try Catch, pudiéndose controlar
el mismo satisfactoriamente.

-- Ejemplo error catcheable:


CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO


BEGIN TRY
EXECUTE usp_ExampleProc;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;