viernes, 12 de febrero de 2016

TRY-CATCH en SQL (Manejo de Excepciones)

Buenos días queridos lectores, feliz jueves ya prefin de semana, el día de hoy hablaremos de el manejo de errores en SQL SERVER que es lo que se conoce como Excepciones, y TRY-CATCH.

Los lenguajes de programación tienen un especial control de los errores posibles que puedan generarse a nivel de programación, SQL Server incorpora diferentes alternativas para controlar dichos errores, no encargando esta tarea, al lenguaje de programación sino al TRANSACT SQL desligándose un poco de las validaciones propias del SQL, a partir de la versión 2005 del SQL Server se incorpora el bloque TRY-CATCH que permite gestionar los errores de manera adecuada.



Sintaxis:
BEGIN TRY
                Expresion_sql
END TRY
BEGIN CATCH
                Expression_sql
END CATCH



Donde:
  • BEGIN TRY: Precisa el inicio del control de error en TRANSACT, en este bloque se coloca el script de trascendencia normal
  • END TRY: Indica la finalización del BEGIN TRY
  • BEGIN CATCH: Precisa el inicio del lado excepcional del script, es decir el bloque BEGIN TRY le encomendará el trabajo a BEGIN CATCH al generarse cualquier tipo de error, aquí entra a relucir las funciones especiales de errores que veremos a continuación
  • END CATCH: Indica la finalización del bloque BEGIN CATCH
Consideraciones:
  • El bloque TRY-CATCH detecta todos los errores de ejecución que tienen una gravedad mayor de 10 y que no cierran la conexión con la base de datos en SQL SERVER
  • La implementación de un bloque TRY-CATCH no puede abarcar varios bloques del mismo es decir no se podrá incluir dentro de un TRY-CATCH otro TRY-CATCH ni mucho menos dentro de un bloque IF...ELSE, ya que generará un error dentro del motor de base de datos SQL Server. Pero dentro del bloque BEGIN CATCH si se puede implementar BEGIN TRY.
  • La forma de trabajo del bloque TRY-CATCH es que si no hay errores en el script incluido dentro del bloque BEGIN TRY, se ejecutan las sentencias inmediatamente posterior a la instrucción. En caso de que hubiera un error en el script incluido en un bloque BEGIN TRY, el control se transfiere a la primera instrucción del bloque BEGIN CATCH.
  • Toda vez que la instrucción END CATCH sea la última de un store proceure o trigger, el control se devuelve a la instrucción que llamo al store procedure o activó el trigger.
  • Los errores capturados por un bloque BEGIN CATCH no devuelven valor alguno a la aplicación que realiza la llamada. En caso necesite devolver alguna información tendrá que implementarse funciones como RAISEERROR o PRINT
  • La implementación de un bloque TRY-CATCH no se puede utilizar en una función definida por el usuario

Funciones especiales de error

  • ERROR_NUMBER(): Devuelve el número de error detectado
  • ERROR_MESSAGE(): Devuelve el texto completo del mensaje de error. El texto incluye los valores suministrados para los parámetros sustituibles, como longitudes, nombres de objetos u horas
  • ERROR_SEVERITY(): Devuelve la gravedad del error
  • ERROR_STATE(): Devuelve el número de estado del error.
  • ERROR_LINE(): Devuelve el número de línea dentro de la rutina en que se produjo el error.
  • ERROR_PROCEDURE():Devuelve el nombre del procedimiento almacenado o desencadenador en que se produjo el error

Ejemplo 1
Script que permita mostrar el valor devuelto por las funciones de control de errores a partir de una división sin resultado como 1/0

BEGIN TRY
       SELECT 1/0
END TRY
BEGIN CATCH
       SELECT
             ERROR_NUMBER() AS ErrorNumber,
             ERROR_SEVERITY() AS ErrorSeverity,
             ERROR_STATE() AS ErrorState,
             ERROR_PROCEDURE() AS ErrorProcedure,
             ERROR_LINE() AS ErrorLine,
             ERROR_MESSAGE() AS ErrorMessage
END CATCH
GO

En el script el BEGIN TRY se muestra una consulta 1/0 normalmente debería emitir un resultado si la división no fuera indeterminada, esto quiere decir que en este caso no emitirá nada u mostrará un vacío.

En el BEGIN CATCH se implementa una consulta donde se mostrarán todos los valores emitidos por las funciones de error.

La imagen siguiente muestra el resultado del script:

Ejemplo 2:
Script que permita insertar un registro a la tabla PASAJERO controlando los posibles errores usando el bloque TRY CATCH


BEGIN TRY
       DECLARE @IDPAS CHAR(5)='P0010',
       @NOM VARCHAR(40)='MANUEL TORRES REMON',
       @IDPAI CHAR(4) = '0004', @TEL CHAR(15)='999999999',
       @EMA VARCHAR(40) = 'PMTORRES@CIBERTEC.EDU.MX'
      
       INSERT INTO PASAJERO
             VALUES (@IDPAS, @NOM, @IDPAI, @TEL, @EMA)
END TRY
BEGIN CATCH
             PRINT 'ERROR EN LA TABLA PASAJERO'
END CATCH
GO

En el script se declaran variables que representan a la misma cantidad de columnas de la tabla PASAJERO y también se inicializan con los valores mostrados, hemos dejado un error en el script puesto que el IDPASAJERO P0010 ya fue ingresado antes, eso quiere decir que el error es la duplicidad del código. Un mensaje sin control de errores se mostraría como la siguiente imagen:


Si se tiene el control como lo muestra el script solo debería mostrar el siguiente mensaje:


Si empleamos las funciones de error podríamos implementar el script de la siguiente manera:

BEGIN TRY
       DECLARE @IDPAS CHAR(5)='P0010',
       @NOM VARCHAR(40)='MANUEL TORRES REMON',
       @IDPAI CHAR(4) = '0004', @TEL CHAR(15)='999999999',
       @EMA VARCHAR(40) = 'PMTORRES@CIBERTEC.EDU.MX'
      
       INSERT INTO PASAJERO
             VALUES (@IDPAS, @NOM, @IDPAI, @TEL, @EMA)
END TRY
BEGIN CATCH
             PRINT 'ERROR EN LA TABLA PASAJERO'
             PRINT 'NUMERO DE ERROR            :'+CAST(ERROR_NUMBER() AS VARCHAR(10))
             PRINT 'NUMERO DE SEVERIDAD :'+CAST(ERROR_SEVERITY() AS VARCHAR(10))
             PRINT 'NUMERO DE ESTADO           :'+CAST(ERROR_STATE() AS VARCHAR(10))
             PRINT 'LINEA DE ERROR             :'+CAST(ERROR_LINE() AS VARCHAR(10))
             PRINT 'MENSAJE DE ERROR           :'+ERROR_MESSAGE()
END CATCH
GO

La siguiente imagen muestra el resultado de la consulta:

Ejemplo 3
Script que permita mostrar la tabla PASAJERO donde comprueba si la tabla existe, en caso hubiera un error en el nombre de la tabla, mostrar un mensaje de TABLA NO REGISTRADA EN LA BASE DE DATOS. Utilizando el bloque TRY CATCH

BEGIN TRY
       SELECT * FROM PASAJEROS
END TRY
BEGIN CATCH
             PRINT 'ERROR EN LA TABLA PASAJERO'
             PRINT 'NUMERO DE ERROR            :'+CAST(ERROR_NUMBER() AS VARCHAR(10))
             PRINT 'NUMERO DE SEVERIDAD :'+CAST(ERROR_SEVERITY() AS VARCHAR(10))
             PRINT 'NUMERO DE ESTADO           :'+CAST(ERROR_STATE() AS VARCHAR(10))
             PRINT 'LINEA DE ERROR             :'+CAST(ERROR_LINE() AS VARCHAR(10))
             PRINT 'MENSAJE DE ERROR           :'+ERROR_MESSAGE()
END CATCH
GO

En primera instancia estaríamos pensando que el script anterior soluciona al error que se podría generar al no proporcionar un nombre correcto a la tabla. Pero lo único que logramos es mostrar un error desde el Motor de Base de Datos. La imagen siguiente muestra el error conseguido:


Para dar solución al caso debemos implementar un procedimiento almacenado que realiza la consulta errada y éste al devolver el control al bloque si podrá detectar el error, aqui se muestra el script:

Primero: Se implementa el procedimiento almacenado (que se ablará de él en futuros post) SP_TABLA que permite mostrar los registros de la tabla PASAJEROS, la tabla en mención no existe en la base de datos.

CREATE PROC SP_TABLA
AS
       SELECT * FROM PASAJEROS
GO

Segundo: Ahora modificamos el bloque BEGIN TRY para hacer la invocación de la ejecución del procedimiento almacenado con EXEC SP_TABLA

BEGIN TRY
       EXEC SP_TABLA
END TRY
BEGIN CATCH
             PRINT 'TABLA NO REGISTRADA'
             PRINT 'NUMERO DE ERROR            :'+CAST(ERROR_NUMBER() AS VARCHAR(10))
             PRINT 'NUMERO DE SEVERIDAD :'+CAST(ERROR_SEVERITY() AS VARCHAR(10))
             PRINT 'NUMERO DE ESTADO           :'+CAST(ERROR_STATE() AS VARCHAR(10))
             PRINT 'LINEA DE ERROR             :'+CAST(ERROR_LINE() AS VARCHAR(10))
             PRINT 'MENSAJE DE ERROR           :'+ERROR_MESSAGE()
END CATCH
GO

La imagen siguiente muestra el resultado de la consulta con los cambios establecidos:


Funcion @@ERROR

La función del sistema @@ERROR devuelve 0 si la última sentencia Transact-SQL se ejecutó con éxito; si la sentencia causo un error, @@ERROR devuelve el número del error. Hay que tener en cuenta que el valor de @@ERROR cambia al finalizar cada sentencia Transact-SQL.

En vista de que @@ERROR obtiene un nuevo valor cuando se completa cada sentencia TRANSACT-SQL entonces deberá considerar las siguientes opciones:

  • Use la función @@ERROR después de una instrucción Transact-SQL
  • Guarde @@ERROR en una variable local tipo entero después que se complete la instrucción Transact-SQL. El valor de la variable se puede usar inmediatamente después de la asignación.
Consideraciones:

  • @@ERROR debe comprobarse o guardarse después de cada instrucción Transact-SQL porque un programador no puede predecir que instrucción puede generar un error. Esto dobla el número de instrucciones Transact-SQL que deben codificarse para implementar un fragmento de lógica dado
  • Las construcciones TRY CATCH son mucho mas simples. Un bloque de instrucciones Transact-SQL esta delimitado por isntrucciones BEGIN TRY y END TRY y después se escribe un bloque CATCH para gestionar errores que pueden generarse mediante el bloque de instrucciones.
  • Fuera del bloque CATCH, @@ERROR es la única parte de un error de Database Engine (Motor de Base de Datos) disponible en el lote, procedimiento almacenado o desencadenador que haya generado el error. El resto de las partes del error, como su gravedad, estado y texto del mensaje solo se devuelven a la aplicación, donde pueden procesarse mediante mecanismo de control de errores de las API. Si el error invoca un bloque CATCH, se pueden usar las funciones del sistema ERROR_LINE, ERROR MESSAGE, ERROR_PROCEDURE, ERROR_NUMBER ERROR_SEVERITY y ERROR_STATE
Ejemplo 4:
Script qué permita actualizar los datos de un determinado pasajero. Use la función @@ERROR

--1.
UPDATE PASAJERO
       SET IDPAIS='9999'
       WHERE PASAJERO.IDPASAJERO='P0010'
--2.
IF @@ERROR<>0
       PRINT 'OCURRIO UN ERROR AL ACTUALIZAR LA TABLA PASAJERO'
GO

En el punto uno se implementa la sentencia UPDATE para la actualización del IDPAIS a la tabla PASAJERO, pero como notará en la clausula IDPAIS se asigno con 9999 identificador de país que no existe en la tabla PAIS y por lo tanto generará un error.

En el punto dos  se verifica que la función @@ERROR sea diferente de cero, ya que según teoría solo emitirá cero cuando no haya errores en la sentencia SQL. Entonces el resultado se muestra en la siguiente imagen:


Para mejorar el script anterior emplearemos el bloque TRY CATCH de la siguiente forma:

--1.
BEGIN TRY
       UPDATE PASAJERO
             SET IDPAIS='9999'
             WHERE PASAJERO.IDPASAJERO='P0010'
END TRY
--2
BEGIN CATCH
       IF @@ERROR<>0
             PRINT 'OCURRIO UN ERROR AL ACTUALIZAR LA TABLA PASAJERO'
END CATCH
GO

En el punto uno se implementa el bloque BEGIN TRY donde se coloca la sentencia UPDATE, ya sabemos que si ocurriera algún error dentro de este bloque se enviará al bloque BEGIN CATCH.

En el punto dos se implementa el bloque BEGIN CATCH que permite verificar que error se cometió, en este caso solo se condiciona a que @@ERROR sea diferente de cero y se imprimirá un mensaje genérico de error, la diferencia con el script anterior es que no mostrará las líneas de error propias del motor de base de datos.

La imagen muestra el resultado del script modificado:


Podríamos capturar el error en una variable de tipo entera como se muestra en el siguiente script:

--1.
BEGIN TRY
       UPDATE PASAJERO
             SET IDPAIS='9999'
             WHERE PASAJERO.IDPASAJERO='P0010'
END TRY
--2
BEGIN CATCH
       DECLARE @NUMEROERROR INT=@@ERROR
       IF @NUMEROERROR<>0
             PRINT 'OCURRIO UN ERROR AL ACTUALIZAR LA TABLA PASAJERO'
END CATCH
GO

Como verá en el script anterior el resultado es el mismo, ya que esta vez solo se captura el error en una variable y esta variable es condicionada.

Para tener una lista de los errores controlados por @@ERROR podemos ejecutar el siguiente script que permitirá mostrar los 97526 errores que se podría ocasionar en una instrucción SQL

SELECT *
       FROM SYS.sysmessages
GO

La imagen a continuación muestra el catálogo sys.messages:


Funcion RAISERROR

Se usa para devolver mensajes definidos por el usuario a las aplicaciones con el mismo formato de un error del sistema o de un mensaje de advertencia generado por el motor de base de datos SQL Server

Sintaxis
RAISERROR(msg_id | msg_str | @local_variable, severity, state
[,argument[,…n]])
[WITH option [,…n]]

Donde:

  • msg_id: Es el número de mensaje de error definido por el usuario almacenado en el catálogo SYS.MESSAGES mediante el procedimiento almacenado SP_ADDMESSAGE. Los números de los mensajes de error definidos por el usuario deben ser mayores de 50000 ya que este número es el tope de los números asignados de forma nativa en el Motor de Base de Datos de SQL. Si no se especifica msg_id, RAISERROR genera un mensaje de error con el número 50000
  • msg_str: Si se especifica msg_str, RAISERROR genera un mensaje de error con el número 50000, msg_str es una cadena de caracteres que incluye especificaciones de conversión opcionales. Cada especificación de conversión define como se aplica formato a un valor de la lista de argumentos y como se coloca en un campo en la posición indicada en la especificación de conversión de msg_str.
  • Severity: Es el nivel de gravedad definido por el usuario asociado a este mensaje. Cuando se utiliza msg_id para generar un mensaje definido por el usuario creado mediante sp_addmessage, la gravedad especificada en RAISERROR remplaza la gravedad especificada en sp_addmessage. 
  • State: Es el numero entero entre 0 y 255. Los niveles negativos o mayores que 255 generaran un error
Consideraciones

  • RAISERROR permite asignar un número de error, gravedad y un estado específico
  • Puede solicitar que el error se guarde en el registro de errores del Motor de Base de Datos y el registro de aplicación de Microsoft Windows
  • Puede sustituir valores de argumento en el texto del mensaje, de forma parecida a la función PRINT. Tanto RAISERROR como PRINT se pueden usar para devolver mensajes de información o advertencias a una aplicación
  • Cuando RAISERROR se usa con el msg_id de un mensaje definido por el usuario en sys.messages, msg_id se devuelve como número de error de SQL Server o el código nativo del error. Cuando RAISERROR se usa con una msg_id, el numero del error y el numero nativo del error SQL Server devuelto es 50000
  • Cuando use RAISERROR para devolver un mensaje definido por el usuario, use un numero de estado distinto en cada RAISERROR que haga referencia al error. Esto puede ayudar a diagnosticar los errores cuando se generan

Ejemplo 5
Script que permita mostrar el valor devuelto a partir de una división sin resultados como 1/0 usando la instrucción RAISERROR

BEGIN TRY
       PRINT 1/0
END TRY
BEGIN CATCH
       DECLARE @MENSAJEERROR VARCHAR(4000)
       DECLARE @SEVERIDAD INT
       DECLARE @ESTADO INT
      
       SELECT
       @MENSAJEERROR = ERROR_MESSAGE(),
       @SEVERIDAD = ERROR_SEVERITY(),
       @ESTADO = ERROR_STATE()
       RAISERROR(@MENSAJEERROR,@SEVERIDAD, @ESTADO)
       END CATCH
GO

Hasta aquí el post queridos lectores, el próximo post estará dedicado a los cursores, espero este post sea de utilidad, espero compartan, comenten, den +1 o algún clic en algún anuncio que les interese.

S

Banderas de Visitantes

Flag Counter