Usar TRY...CATCH en Transact-SQL

Los errores en el código de Transact-SQL se pueden procesar mediante una construcción TRY…CATCH similar a las características de control de excepciones de los lenguajes Microsoft Visual C++ y Microsoft Visual C#. Las construcciones TRY…CATCH constan de dos partes: un bloque TRY y uno CATCH. Cuando se detecta una condición de error en una instrucción de Transact-SQL que se encuentra dentro de un bloque TRY, se pasa el control a un bloque CATCH donde se puede procesar el error.

Cuando el bloque CATCH controla la excepción, el control se transfiere a la primera instrucción de Transact-SQL siguiente a la instrucción END CATCH. Si la instrucción CATCH es la última instrucción de un procedimiento almacenado o un desencadenador, el control vuelve al código que los invocó. No se ejecutarán las instrucciones de Transact-SQL del bloque TRY que siguen a la instrucción que genera un error.

Si no existen errores en el bloque TRY, el control pasa a la instrucción inmediatamente después de la instrucción END CATCH asociada. Si la instrucción END CATCH es la última instrucción de un procedimiento almacenado o un desencadenador, el control pasa a la instrucción que invocó al procedimiento almacenado o el desencadenador.

Un bloque TRY se inicia con la instrucción BEGIN TRY y finaliza con la instrucción END TRY. Se pueden especificar una o varias instrucciones de Transact-SQL entre las instrucciones BEGIN TRY y END TRY.

Un bloque TRY debe ir seguido inmediatamente por un bloque CATCH. Un bloque CATCH se inicia con la instrucción BEGIN CATCH y finaliza con la instrucción END CATCH. En Transact-SQL, cada bloque TRY se asocia a un sólo bloque CATCH.

Trabajar con TRY…CATCH

Cuando utilice la construcción TRY…CATCH, tenga en cuenta las siguientes directrices y sugerencias:

  • Cada construcción TRY…CATCH debe encontrarse en un solo lote, procedimiento almacenado o desencadenador. Por ejemplo, no se puede colocar un bloque TRY en un lote y el bloque asociado CATCH en otro. El siguiente script generaría un error:

    BEGIN TRY
        SELECT *
            FROM sys.messages
            WHERE message_id = 21;
    END TRY
    GO
    -- The previous GO breaks the script into two batches,
    -- generating syntax errors. The script runs if this GO
    -- is removed.
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber;
    END CATCH;
    GO
    
  • Un bloque CATCH debe seguir inmediatamente a un bloque TRY.

  • Las construcciones TRY…CATCH pueden estar anidadas. Esto significa que las construcciones TRY…CATCH se pueden colocar dentro de otros bloques TRY y CATCH. Cuando se produce un error dentro de un bloque TRY anidado, el control del programa se transfiere al bloque CATCH que está asociado al bloque TRY anidado.

  • Para controlar un error que se produce en un bloque CATCH determinado, escriba un bloque TRY…...CATCH en el bloque CATCH especificado.

  • El bloque TRY…CATCH no controlará los errores con una gravedad de 20 o superior que hacen que Database Engine (Motor de base de datos) cierre la conexión. No obstante, TRY…CATCH controlará los errores con una gravedad de 20 o superior siempre que la conexión no se cierre.

  • Los errores con un gravedad de 10 o inferior se consideran advertencias o mensajes informativos, y los bloques TRY…CATCH no los controlan.

  • Las atenciones terminarán el lote aunque éste se encuentre en el ámbito de una construcción TRY…CATCH. Incluyen las atenciones enviadas por el Coordinador de transacciones distribuidas de Microsoft (MS DTC) cuando se produce un error en una transacción distribuida. MS DTC administra transacciones distribuidas.

    [!NOTA]

    Si una transacción distribuida se ejecuta en el ámbito de un bloque TRY y se produce un error, la ejecución se transfiere al bloque CATCH asociado. La transacción distribuida adopta un estado no confirmable. Puede que la ejecución en el bloque CATCH se vea interrumpida por el Coordinador de transacciones distribuidas de Microsoft, que controla las transacciones distribuidas. Cuando se produce el error, MS DTC notifica de manera asincrónica a todos los servidores que participan de la transacción distribuida y termina todas las tareas implicadas en dicha transacción. Esta notificación se envía en forma de atención, que no se controla mediante una construcción TRY…CATCH, y el lote se termina. Cuando finaliza la ejecución de un lote, Database Engine (Motor de base de datos) revierte todas las transacciones activas no confirmables. Si no se ha enviado ningún mensaje de error cuando la transacción pasó al estado no confirmable, cuando el lote termina, se enviará un mensaje de error a la aplicación cliente indicando que se ha detectado y revertido una transacción no confirmable. Para obtener más información acerca de las transacciones distribuidas, vea Transacciones distribuidas (motor de base de datos).

Funciones de error

TRY…CATCH utiliza las siguientes funciones de error para capturar información de errores:

  • ERROR_NUMBER() devuelve el número de error.

  • 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 objeto 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.

La información de errores se recupera mediante estas funciones desde cualquier sitio en el ámbito del bloque CATCH de una construcción TRY…CATCH. Las funciones de error devolverán un valor NULL si se llaman desde fuera del ámbito de un bloque CATCH. Se puede hacer referencia a funciones de error contenidas en un procedimiento almacenado y pueden utilizarse para recuperar información de errores cuando el procedimiento almacenado se ejecuta en el bloque CATCH. Al hacerlo, no será necesario repetir el código de control de errores en cada bloque CATCH. En el siguiente ejemplo de código, la instrucción SELECT del bloque TRY generará un error de división por cero. El error se controlará mediante el bloque CATCH, que utiliza un procedimiento almacenado para devolver información de errores.

USE AdventureWorks;
GO

-- Verify that the stored procedure does not exist.
IF OBJECT_ID ('usp_GetErrorInfo', 'P') IS NOT NULL
    DROP PROCEDURE usp_GetErrorInfo;
GO

-- Create a procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
    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;
GO

BEGIN TRY
    -- Generate divide-by-zero error.
    SELECT 1/0;
END TRY
BEGIN CATCH
    -- Execute the error retrieval routine.
    EXECUTE usp_GetErrorInfo;
END CATCH;
GO

Errores de compilación y recompilación de instrucciones

Existen dos tipos de errores que no se controlan mediante TRY…CATCH cuando el error se produce en el mismo nivel de ejecución que la construcción TRY…CATCH:

  • Errores de compilación, como errores de sintaxis que impiden que un lote se ejecute.

  • Errores que se producen durante la recompilación de instrucciones, como errores de resolución de nombres de objeto que se producen después de la compilación debido a una resolución diferida de nombres.

Cuando el lote, procedimiento almacenado o desencadenador que contiene la construcción TRY…CATCH genera uno de estos errores, la construcción TRY…CATCH no los controla. Estos errores se devuelven a la aplicación o lote que invocó la rutina de generación de errores. En el siguiente ejemplo de código se muestra una instrucción SELECT que provoca un error de sintaxis. Si este código se ejecuta en el Editor de consultas de SQL Server Management Studio, la ejecución no se iniciará ya que el lote no se compila. El error se devuelve al Editor de consultas y TRY…CATCH no lo capturará.

USE AdventureWorks;
GO

BEGIN TRY
    -- This PRINT statement will not run because the batch
    -- does not begin execution.
    PRINT N'Starting execution';

    -- This SELECT statement contains a syntax error that
    -- stops the batch from compiling successfully.
    SELECT ** FROM HumanResources.Employee;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

A diferencia del error de sintaxis del ejemplo anterior, un error que se produzca durante la recompilación de instrucciones no impedirá que el lote se compile, pero terminará el lote en cuanto la recompilación de la instrucción genere un error. Por ejemplo, si un lote tiene dos instrucciones y la segunda de ellas hace referencia a una tabla que no existe, la resolución diferida de nombres provoca que el lote se compile correctamente e inicie la ejecución sin enlazar la tabla que falta al plan de consultas hasta que la instrucción se vuelva a compilar. El lote detiene la ejecución al llegar a la instrucción que hace referencia a la tabla que falta y devuelve un error. Este tipo de error no se controla mediante una construcción TRY…CATCH al mismo nivel de ejecución en que se produjo el error. El siguiente ejemplo demuestra este comportamiento.

USE AdventureWorks;
GO

BEGIN TRY
    -- This PRINT statement will run because the error
    -- occurs at the SELECT statement.
    PRINT N'Starting execution';

    -- This SELECT statement will generate an object name
    -- resolution error because the table does not exist.
    SELECT * FROM NonExistentTable;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Se puede utilizar TRY…CATCH para controlar errores que se producen durante la compilación o la recompilación de instrucciones mediante la ejecución del código de generación de errores en un lote independiente en el bloque TRY. Por ejemplo, para hacerlo, coloque el código en un procedimiento almacenado o ejecute una instrucción Transact-SQL dinámica mediante sp_executesql. Esto permite que TRY…CATCH capture el error en un nivel de ejecución más alto que el de ejecución del error. En el siguiente ejemplo de código se muestra un procedimiento almacenado que genera un error de resolución de nombres de objeto. El lote que contiene la construcción TRY…CATCH se ejecuta en un nivel más alto que el procedimiento almacenado y el error, que se produce en un nivel inferior, se captura.

USE AdventureWorks;
GO

-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ('usp_MyError', 'P') IS NOT NULL
    DROP PROCEDURE usp_MyError;
GO

CREATE PROCEDURE usp_MyError
AS
    -- This SELECT statement will generate
    -- an object name resolution error.
    SELECT * FROM NonExistentTable;
GO

BEGIN TRY
    -- Run the stored procedure.
    EXECUTE usp_MyError;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Éste es el conjunto de resultados.

ErrorNumber ErrorMessage
----------- ---------------------------------------
208         Invalid object name 'NonExistentTable'.

Para obtener más información, vea Resolución diferida de nombres y compilación y la sección sobre volver a compilar planes de ejecución de Almacenar en caché y volver a utilizar un plan de ejecución.

Transacciones no confirmables

En una construcción TRY…CATCH, las transacciones pueden pasar a un estado en el que la transacción sigue abierta pero no se puede confirmar. La transacción no puede realizar ninguna acción que genere una escritura en el registro de transacciones, como modificar datos o intentar revertir a un punto de retorno. En este estado, sin embargo, los bloqueos adquiridos por la transacción se mantienen y la conexión también sigue abierta. Los efectos de la transacción no se revierten hasta que se emite la instrucción ROLLBACK, o bien hasta que el lote termina y Database Engine (Motor de base de datos) revierte automáticamente la transacción. Si no se ha enviado ningún mensaje de error cuando la transacción pasó al estado no confirmable, cuando el lote termina, se enviará un mensaje de error a la aplicación cliente indicando que se ha detectado y revertido una transacción no confirmable.

Una transacción pasa a un estado no confirmable dentro de un bloque TRY cuando se produce un error que, de lo contrario, habría terminado la transacción. Por ejemplo, la mayoría de los errores procedentes de una instrucción de lenguaje de definición de datos (DDL) como CREATE TABLE o la mayoría de los errores que se producen cuando SET XACT_ABORT está en ON, terminan la transacción fuera de un bloque TRY, pero crean una transacción no confirmable dentro de un bloque TRY.

El código de un bloque CATCH debe probar el estado de una transacción mediante la función XACT_STATE. XACT_STATE devuelve -1 si la sesión tiene una transacción no confirmable. El bloque CATCH no debe realizar acciones que generen escrituras en el registro cuando XACT_STATE devuelve -1. En el siguiente ejemplo de código se genera un error desde una instrucción DDL y se utiliza XACT_STATE para probar el estado de una transacción a fin de realizar la acción más adecuada.

USE AdventureWorks;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_books', N'U') IS NOT NULL
    DROP TABLE my_books;
GO

-- Create table my_books.
CREATE TABLE my_books
    (
    Isbn        int PRIMARY KEY,
    Title       NVARCHAR(100)
    );
GO

BEGIN TRY
    BEGIN TRANSACTION;
        -- This statement will generate an error because the 
        -- column author does not exist in the table.
        ALTER TABLE my_books
            DROP COLUMN author;
    -- If the DDL statement succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;

    -- Test XACT_STATE for 1 or -1.
    -- XACT_STATE = 0 means there is no transaction and
    -- a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT
            N'The transaction is in an uncommittable state. ' +
            'Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT
            N'The transaction is committable. ' +
            'Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Controlar interbloqueos

Se puede utilizar TRY…CATCH para controlar interbloqueos. Se puede capturar el error de sujeto del interbloqueo 1205 mediante el bloque CATCH y revertir la transacción hasta que los subprocesos se desbloqueen. Para obtener más información acerca de los interbloqueos, vea Interbloqueos.

En el siguiente ejemplo se muestra cómo se puede utilizar TRY…CATCH para controlar interbloqueos. En esta primera sección se crea una tabla que se utilizará para demostrar un estado de interbloqueo y un procedimiento almacenado que se utilizará para imprimir información de errores.

USE AdventureWorks;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_sales',N'U') IS NOT NULL
    DROP TABLE my_sales;
GO

-- Create and populate the table for deadlock simulation.
CREATE TABLE my_sales 
    (
    Itemid       INT PRIMARY KEY,
    Sales        INT not null
    );
GO

INSERT my_sales (itemid, sales) VALUES (1, 1);
INSERT my_sales (itemid, sales) VALUES (2, 1);
GO
  
-- Verify that the stored procedure for error printing
-- does not exist.
IF OBJECT_ID (N'usp_MyErrorLog',N'P') IS NOT NULL
    DROP PROCEDURE usp_MyErrorLog;
GO

-- Create a stored procedure for printing error information.
CREATE PROCEDURE usp_MyErrorLog
AS
    PRINT 
        'Error ' + CONVERT(VARCHAR(50), ERROR_NUMBER()) +
        ', Severity ' + CONVERT(VARCHAR(5), ERROR_SEVERITY()) +
        ', State ' + CONVERT(VARCHAR(5), ERROR_STATE()) + 
        ', Line ' + CONVERT(VARCHAR(5), ERROR_LINE());
    PRINT 
        ERROR_MESSAGE();
GO

Los siguientes scripts de código para la sesión 1 y la sesión 2 se ejecutan simultáneamente en dos conexiones independientes de SQL Server Management Studio. Ambas sesiones intentan actualizar las mismas filas de la tabla. Una de las sesiones realizará la operación de actualización en el primer intento y la otra se seleccionará como sujeto del interbloqueo. El error de sujeto del interbloqueo provocará que la ejecución salte al bloque CATCH y la transacción pase a un estado no confirmable. En el bloque CATCH, el sujeto del interbloqueo puede revertir la transacción e intentar actualizar de nuevo la tabla hasta que se actualice o se alcance el límite de reintentos, lo que suceda antes.

Sesión 1

Sesión 2

USE AdventureWorks;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
-- Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        WAITFOR DELAY '00:00:13';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO
USE AdventureWorks;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
--Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
       BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        WAITFOR DELAY '00:00:07';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO

TRY…CATCH con RAISERROR

Se puede utilizar RAISERROR en un bloque TRY o CATCH de una construcción TRY…CATCH para modificar el comportamiento de control de errores.

La ejecución de RAISERROR con una gravedad de 11 a 19 en un bloque TRY provoca la transferencia del control al bloque CATCH asociado. La ejecución de RAISERROR con una gravedad de 11 a 19 en un bloque CATCH devuelve un error a la aplicación o el lote que realiza la llamada. De este modo, se puede utilizar RAISERROR para devolver información al solicitante sobre el error que provocó la ejecución del bloque CATCH. La información de errores proporcionada por las funciones de error TRY…CATCH se puede capturar en el mensaje de RAISERROR, incluido el número de error original; sin embargo, el número de error de RAISERROR debe ser >= 50000.

RAISERROR con una gravedad de 10 o inferior devuelve un mensaje informativo al lote o la aplicación que realiza la llamada sin invocar a un bloque CATCH.

RAISERROR con una gravedad de 20 o superior termina la conexión de la base de datos sin invocar al bloque CATCH.

En el siguiente ejemplo de código se muestra cómo se puede utilizar RAISERROR en un bloque CATCH para devolver la información original de errores a la aplicación o el lote que realiza la llamada. El procedimiento almacenado usp_GenerateError ejecuta una instrucción DELETE en un bloque TRY que genera un error de infracción de restricciones. El error provoca la transferencia de la ejecución al bloque CATCH asociado dentro de usp_GenerateError, donde se ejecuta el procedimiento almacenado usp_RethrowError para generar la información de errores de infracción de restricciones mediante RAISERROR. Este error generado con RAISERROR se devuelve al lote que realiza la llamada en que se ejecutó usp_GenerateError y provoca la transferencia de la ejecución al bloque CATCH asociado del lote que realiza la llamada.

[!NOTA]

RAISERROR puede generar errores con un estado comprendido sólo entre 1 y 127. Como Database Engine (Motor de base de datos) puede generar errores con un estado 0, se recomienda comprobar el estado del error que devuelve ERROR_STATE antes de pasarlo como un valor al parámetro de estado de RAISERROR.

USE AdventureWorks;
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
    DROP PROCEDURE usp_RethrowError;
GO

-- Create the stored procedure to generate an error using 
-- RAISERROR. The original error information is used to
-- construct the msg_str for RAISERROR.
CREATE PROCEDURE usp_RethrowError AS
    -- Return if there is no error information to retrieve.
    IF ERROR_NUMBER() IS NULL
        RETURN;

    DECLARE 
        @ErrorMessage    NVARCHAR(4000),
        @ErrorNumber     INT,
        @ErrorSeverity   INT,
        @ErrorState      INT,
        @ErrorLine       INT,
        @ErrorProcedure  NVARCHAR(200);

    -- Assign variables to error-handling functions that 
    -- capture information for RAISERROR.
    SELECT 
        @ErrorNumber = ERROR_NUMBER(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE(),
        @ErrorLine = ERROR_LINE(),
        @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    -- Build the message string that will contain original
    -- error information.
    SELECT @ErrorMessage = 
        N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 
            'Message: '+ ERROR_MESSAGE();

    -- Raise an error: msg_str parameter of RAISERROR will contain
    -- the original error information.
    RAISERROR 
        (
        @ErrorMessage, 
        @ErrorSeverity, 
        1,               
        @ErrorNumber,    -- parameter: original error number.
        @ErrorSeverity,  -- parameter: original error severity.
        @ErrorState,     -- parameter: original error state.
        @ErrorProcedure, -- parameter: original error procedure name.
        @ErrorLine       -- parameter: original error line number.
        );
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_GenerateError',N'P') IS NOT NULL
    DROP PROCEDURE usp_GenerateError;
GO

-- Create a stored procedure that generates a constraint violation
-- error. The error is caught by the CATCH block where it is 
-- raised again by executing usp_RethrowError.
CREATE PROCEDURE usp_GenerateError 
AS 
    BEGIN TRY
        -- A FOREIGN KEY constraint exists on the table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;
    END TRY
    BEGIN CATCH
        -- Call the procedure to raise the original error.
        EXEC usp_RethrowError;
    END CATCH;
GO

-- In the following batch, an error occurs inside 
-- usp_GenerateError that invokes the CATCH block in
-- usp_GenerateError. RAISERROR inside this CATCH block
-- generates an error that invokes the outer CATCH
-- block in the calling batch.
BEGIN TRY  -- outer TRY
    -- Call the procedure to generate an error.
    EXECUTE usp_GenerateError;
END TRY
BEGIN CATCH  -- Outer CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;
END CATCH;
GO

Cambiar el flujo de ejecución

Pata cambiar el flujo de ejecución, se puede utilizar GOTO en un bloque TRY o un bloque CATCH. También se puede utilizar GOTO para salir de un bloque TRY o CATCH; sin embargo, no se puede utilizar para entrar en un bloque TRY o CATCH.

Solución de control de errores en la base de datos de ejemplo Adventure Works

La base de datos de ejemplo AdventureWorks incluye una solución de control de errores diseñada para registrar información sobre errores capturados por el bloque CATCH de una construcción TRY…CATCH que posteriormente se puede consultar o analizar.

Tabla dbo.ErrorLog

La tabla ErrorLog registra información relativa al número de error, la gravedad del error, el estado del error, el nombre del procedimiento almacenado o desencadenador en que se produjo el error, el número de línea en que se produjo el error y el texto completo del mensaje de error. Registra también la fecha y hora en que se produjo el error y el nombre del usuario que ejecutó la rutina de generación de errores. Esta tabla se llena cuando el procedimiento almacenado uspLogError se ejecuta en el ámbito del bloque CATCH de una construcción TRY…CATCH. Para obtener más información, vea Tabla ErrorLog (AdventureWorks).

dbo.uspLogError

El procedimiento almacenado uspLogError registra información de errores en la tabla ErrorLog acerca del error que provocó la transferencia de la ejecución al bloque CATCH de una construcción TRY…CATCH. Para que uspLogError inserte información de errores en la tabla ErrorLog, deben cumplirse las siguientes condiciones:

  • uspLogError se ejecuta en el ámbito de un bloque CATCH.

  • Si la transacción actual se encuentra en un estado no confirmable, se revierte antes de ejecutar uspLogError.

El parámetro de salida @ErrorLogID de uspLogError devuelve el valor de ErrorLogID de la fila que uspLogError insertó en la tabla ErrorLog. El valor predeterminado de @ErrorLogID es 0. En el siguiente ejemplo se muestra el código de uspLogError. Para obtener más información, vea Procedimientos almacenados de AdventureWorks.

CREATE PROCEDURE [dbo].[uspLogError] 
    @ErrorLogID [int] = 0 OUTPUT  -- Contains the ErrorLogID of the row inserted
                                  -- by uspLogError in the ErrorLog table.

AS
BEGIN
    SET NOCOUNT ON;

    -- Output parameter value of 0 indicates that error 
    -- information was not logged.
    SET @ErrorLogID = 0;

    BEGIN TRY
        -- Return if there is no error information to log.
        IF ERROR_NUMBER() IS NULL
            RETURN;

        -- Return if inside an uncommittable transaction.
        -- Data insertion/modification is not allowed when 
        -- a transaction is in an uncommittable state.
        IF XACT_STATE() = -1
        BEGIN
            PRINT 'Cannot log error since the current transaction is in an uncommittable state. ' 
                + 'Rollback the transaction before executing uspLogError in order to successfully log error information.';
            RETURN;
        END;

        INSERT [dbo].[ErrorLog] 
            (
            [UserName], 
            [ErrorNumber], 
            [ErrorSeverity], 
            [ErrorState], 
            [ErrorProcedure], 
            [ErrorLine], 
            [ErrorMessage]
            ) 
        VALUES 
            (
            CONVERT(sysname, CURRENT_USER), 
            ERROR_NUMBER(),
            ERROR_SEVERITY(),
            ERROR_STATE(),
            ERROR_PROCEDURE(),
            ERROR_LINE(),
            ERROR_MESSAGE()
            );

        -- Pass back the ErrorLogID of the row inserted
        SELECT @ErrorLogID = @@IDENTITY;
    END TRY
    BEGIN CATCH
        PRINT 'An error occurred in stored procedure uspLogError: ';
        EXECUTE [dbo].[uspPrintError];
        RETURN -1;
    END CATCH
END; 

dbo.uspPrintError

El procedimiento almacenado uspPrintError imprime información acerca del error que provocó la transferencia de la ejecución al bloque CATCH de una construcción TRY…CATCH. uspPrintError debe ejecutarse en el ámbito de un bloque CATCH; de lo contrario, el procedimiento se devuelve sin imprimir ninguna información de errores. En el siguiente ejemplo se muestra el código de uspPrintError. Para obtener más información, vea Procedimientos almacenados de AdventureWorks.

CREATE PROCEDURE [dbo].[uspPrintError] 
AS
BEGIN
    SET NOCOUNT ON;

    -- Print error information. 
    PRINT 'Error ' + CONVERT(varchar(50), ERROR_NUMBER()) +
          ', Severity ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
          ', State ' + CONVERT(varchar(5), ERROR_STATE()) + 
          ', Procedure ' + ISNULL(ERROR_PROCEDURE(), '-') + 
          ', Line ' + CONVERT(varchar(5), ERROR_LINE());
    PRINT ERROR_MESSAGE();
END;

Ejemplo de control de errores

En el siguiente ejemplo se muestra la solución de control de errores de AdventureWorks. El código dentro del bloque TRY intenta eliminar el registro con ProductID 980 de la tabla Production.Product. Una restricción FOREIGN KEY en la tabla impide que se ejecute correctamente la instrucción DELETE y se genera una infracción de restricciones. Este error provoca la transferencia de la ejecución al bloque CATCH. En el bloque CATCH, se producen las siguientes acciones:

  • uspPrintError imprime la información de errores.

  • Una vez revertida la transacción, uspLogError escribe la información de errores en la tabla ErrorLog y devuelve el valor de ErrorLogID de la fila insertada en el parámetro @ErrorLogID OUTPUT.

USE AdventureWorks;
GO

-- Variable to store ErrorLogID value of the row
-- inserted in the ErrorLog table by uspLogError 
DECLARE @ErrorLogID INT;

BEGIN TRY
    BEGIN TRANSACTION;

    -- A FOREIGN KEY constraint exists on this table. This 
    -- statement will generate a constraint violation error.
    DELETE FROM Production.Product
        WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Call procedure to print error information.
    EXECUTE dbo.uspPrintError;

    -- Roll back any active or uncommittable transactions before
    -- inserting information in the ErrorLog.
    IF XACT_STATE() <> 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

    EXECUTE dbo.uspLogError @ErrorLogID = @ErrorLogID OUTPUT;
END CATCH; 

-- Retrieve logged error information.
SELECT * FROM dbo.ErrorLog WHERE ErrorLogID = @ErrorLogID;
GO

Ejemplo de control de errores anidados

El siguiente ejemplo muestra cómo se utilizan las construcciones TRY...CATCH.

BEGIN TRY
    BEGIN TRY
        SELECT CAST('invalid_date' AS datetime)
    END TRY
    BEGIN CATCH 
        PRINT 'Inner TRY error number: ' +    
            CONVERT(varchar,ERROR_NUMBER()) + ' on line: ' +
            CONVERT(varchar, ERROR_LINE())
    END CATCH
    SELECT CAST('invalid_int' AS int)
END TRY
BEGIN CATCH
    PRINT 'Outer TRY error mumber: ' + CONVERT(varchar,ERROR_NUMBER())+ 
            ' on line: ' + CONVERT(varchar, ERROR_LINE())
END CATCH

Éste es el conjunto de resultados.

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9