Usando TRY...CATCH na Transact-SQL

Os erros no código Transact-SQL podem ser processados com o uso da construção TRY…CATCH similar aos recursos de controle de exceções das linguagens Microsoft Visual C++ e Microsoft Visual C#. Uma construção TRY...CATCH consiste em duas partes: um bloco TRY e um bloco CATCH. Quando uma condição de erro for detectada em uma instrução Transact-SQL que está dentro de um bloco TRY, o controle é passado para um bloco CATCH, onde o erro pode ser processado.

Após o bloco CATCH controlar a exceção, o controle é transferido para a primeira instrução Transact-SQL que segue a instrução END CATCH. Se a instrução END CATCH for a última instrução de um procedimento ou gatilho, o controle retorna para o código que invocou o procedimento armazenado ou gatilho. As instruções Transact-SQL do bloco TRY que seguem a instrução que gera um erro não serão executadas.

Se não houver erros dentro do bloco TRY, o controle passa para a instrução imediatamente seguinte à instrução END CATCH associada. Se a instrução END CATCH for a última instrução de um procedimento armazenado ou gatilho, o controle é passado para a instrução que invocou o procedimento armazenado ou gatilho.

Um bloco TRY inicia com a instrução BEGIN TRY e termina com a instrução END TRY. Podem ser especificadas uma ou mais instruções Transact-SQL entre as instruções BEGIN TRY e END TRY.

Um bloco TRY deve ser seguido imediatamente por um bloco CATCH. Um bloco CATCH inicia com a instrução BEGIN CATCH e termina com a instrução END CATCH. Em Transact-SQL, cada bloco TRY é associado com só um bloco CATCH.

Trabalhando com TRY...CATCH

Ao usar a construção TRY…CATCH, considere as diretrizes e sugestões a seguir:

  • Cada construção TRY...CATCH deve estar dentro de um único lote, procedimento armazenado ou gatilho. Por exemplo, você não pode colocar um bloco TRY em um lote e o bloco CATCH associado, em outro lote. O script seguinte geraria um erro:

    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
    
  • Um bloco TRY deve ser seguido imediatamente por um bloco CATCH.

  • As construções TRY...CATCH podem ser aninhadas. Isso significa que as construções TRY...CATCH podem ser colocadas dentro de outro bloco TRY e CATCH. Ao ocorrer um erro dentro de um bloco TRY aninhado, o controle de programas será transferido para o bloco CATCH associado ao bloco TRY aninhado.

  • Para controlar um erro dento de um determinado bloco CATCH, grave um bloco TRY…...CATCH no interior do bloco CATCH especificado.

  • Os erros com severidade 20 ou superior que levam o Mecanismo de Banco de Dados a encerrar a conexão não serão controlados pelo bloco TRY…CATCH. Porém, o TRY…CATCH controlará os erros com severidade igual ou superior a 20 contanto que a conexão seja mantida.

  • Os erros com severidade 10 ou inferior são considerados avisos ou mensagens informativas, e não serão controlados pelos blocos TRY…CATCH.

  • As atenções finalizarão um lote, mesmo ele estando dentro do escopo de uma construção TRY...CATCH. Isto inclui uma atenção enviada pelo MS DTC (Coordenador de Transações Distribuídas da Microsoft) quando uma transação distribuída falhar. O MS DTC gerencia as transações distribuídas.

    ObservaçãoObservação

    Se uma transação distribuída for executada dentro do escopo de um bloco TRY e ocorrer um erro , a execução será transferida para o bloco CATCH associado. A transação distribuída entra em um estado não confirmável. A execução dentro do bloco CATCH pode ser interrompida pelo Coordenador de Transações Distribuídas da Microsoft, que administra as transações distribuídas. Quando o erro ocorrer, o MS DTC notifica, de forma assíncrona, todos os servidores que participam da transação distribuída, e finaliza todas as tarefas envolvidas na transação distribuída. Essa notificação é enviada na forma de atenção que não será controlada por uma construção TRY…CATCH, e o lote é finalizado. Quando um lote terminar de executar, o Mecanismo de Banco de Dados reverte qualquer transação ativa não confirmável. Se nenhuma mensagem de erro foi enviada quando a transação entrou em um estado não confirmável, quando o lote terminar, uma mensagem de erro será enviada ao aplicativo cliente que indica que uma transação não confirmável foi detectada e revertida. Para obter mais informações sobre transações distribuídas, consulte Transações distribuídas (Mecanismo de Banco de Dados).

Funções de erro

TRY…CATCH usa as seguintes funções de erro para capturar informações de erros:

  • ERROR_NUMBER () retorna o número do erro.

  • ERROR_MESSAGE () retorna o texto completo da mensagem de erro. O texto inclui os valores fornecidos para qualquer parâmetro substituível, como extensões, nomes de objetos ou horas.

  • ERROR_SEVERITY () retorna a severidade de erro.

  • ERROR_STATE() retorna o número do estado do erro.

  • ERROR_LINE () retorna o número de linha dentro da rotina que causou o erro.

  • ERROR_PROCEDURE() retorna o nome do procedimento armazenado ou do gatilho no qual ocorreu o erro.

As informações sobre o erro podem ser recuperadas usando-se essas funções de qualquer lugar dentro do escopo do bloco CATCH de uma construção TRY...CATCH. As funções de erro retornarão o NULL, se chamadas fora do escopo de um bloco CATCH. As funções de erro podem ser referenciadas dentro de um procedimento armazenado e usadas para recuperar as informações sobre erros, quando o procedimento armazenado for executado no bloco CATCH. Fazendo isto, você não precisa repetir o código de tratamento de erros em todo bloco CATCH. No exemplo de código a seguir, a instrução SELECT no bloco TRY gerará um erro de divisão por zero. O erro será controlado pelo bloco CATCH, que usa um procedimento armazenado para retornar as informações de erro.

USE AdventureWorks2008R2;
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

Erros ao compilar e recompilar em nível de instrução

Existem dois tipos de erros que não serão controlados pelo TRY…CATCH, se o erro ocorrer no mesmo nível de execução da construção TRY…CATCH:

  • Erros de compilação, como erros de sintaxe que impedem um lote de executar.

  • Erros ocorridos ao se recompilar em nível de instrução, como os erros de resolução do nome do objeto que acontecem após a compilação devido à resolução do nome adiada.

Quando o lote, procedimento armazenado, ou gatilho que contém a construção TRY…CATCH gerar um desses erros, a construção TRY…CATCH não os controlará. Esses erros voltarão ao aplicativo ou lote que chamou a rotina de geração de erro. Para exemplificar, o exemplo de código a seguir mostra uma instrução SELECT que causa um erro de sintaxe. Se esse código for executado no Editor de Consultas do SQL Server Management Studio, a execução não será iniciada, pois a compilação do lote falhará. O erro retornará ao Editor de Consultas e não será pego por TRY…CATCH.

USE AdventureWorks2008R2;
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

Diferente do erro de sintaxe do exemplo anterior, um erro que ocorre durante uma recompilação em nível de instrução não impedirá que o lote compile; mas, ela terminará o lote assim que a recompilação da instrução falhar. Por exemplo, se um lote possuir duas instruções e a segunda instrução fizer referência a uma tabela que não existe, a resolução adiada do nome faz o lote compilar com êxito, e iniciar a execução sem associar a tabela que falta ao plano de consulta até que essa instrução seja recompilada. O lote deixa de executar quando se deparar com a instrução, que fará referência à tabela perdida e retornará um erro. Esse tipo de erro não será controlado pela construção TRY…CATCH no mesmo nível de execução no qual o erro ocorreu. O exemplo a seguir demonstra esse comportamento.

USE AdventureWorks2008R2;
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

Você pode usar TRY…CATCH para controlar erros que ocorrem durante a compilação ou recompilação em nível de instrução, executando o código de geração de erro em um lote separado dentro do bloco TRY. Por exemplo, você pode fazer isso colocando o código em um procedimento armazenado ou executando uma instrução dinâmica Transact-SQL usando sp_executesql. Isso permitirá que TRY…CATCH capture o erro em um nível superior de execução em relação à ocorrência de erro. Por exemplo, o código a seguir mostra um procedimento armazenado que gera um erro de resolução de nome de objeto. O lote que contém a construção TRY…CATCH está executando em um nível superior ao do procedimento armazenado; e o erro, que ocorre em um nível inferior, é capturado.

USE AdventureWorks2008R2;
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

Aqui está o conjunto de resultados.

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

Para obter mais informações, consulte Resolução e compilação de nome diferida e a seção "Recompilando Planos de Execução" em Reutilização e armazenamento em cache do plano de execução.

Transações não confirmáveis

Dentro de uma construção TRY...CATCH, as transações podem entrar em um estado em que a transação permanece aberta, mas não pode ser confirmada. A transação não poderá efetuar qualquer ação que gere uma gravação no log de transação, como a alteração de dados ou a tentativa de reversão a um ponto de salvamento. Contudo, nesse estado, são mantidos os bloqueios adquiridos pela transação e a conexão também permanece aberta. Os efeitos da transação não são revertidos até que uma instrução ROLLBACK seja emitida, ou até que o lote termine e a transação seja revertida automaticamente pelo Mecanismo de Banco de Dados. Se nenhuma mensagem de erro foi enviada quando a transação entrou em um estado não confirmável, quando o lote terminar, uma mensagem de erro será enviada ao aplicativo cliente, que indicará que uma transação não confirmável foi detectada e revertida.

Uma transação entra em um estado não confirmável dentro de um bloco TRY, quando ocorre um erro que, de outra forma, teria encerrado a transação. Por exemplo, a maioria dos erros de uma instrução DDL, linguagem de definição de dados (como CREATE TABLE), ou a maioria dos erros que ocorrem quando SET XACT_ABORT estiver definido em ON, encerram a transação fora de um bloco TRY; mas, tornam uma transação não confirmável dentro de um bloco TRY.

O código de um bloco CATCH deve testar o estado de uma transação usando a função XACT_STATE. O XACT_STATE retornará um -1 se a sessão tiver uma transação não confirmável. O bloco CATCH não deverá efetuar qualquer ação que gere gravações no log se a função XACT_STATE retornar um -1. O exemplo de código a seguir gera um erro de uma instrução DDL e usa XACT_STATE para testar o estado de uma transação, com objetivo de tomar a ação mais apropriada.

USE AdventureWorks2008R2;
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

Manipulando Deadlocks

É possível usar TRY…CATCH para controlar deadlocks. O erro 1205, vítima de deadlock, pode ser pego pelo bloco CATCH, e a transação poderá ser revertida até que os threads sejam desbloqueados. Para obter mais informações sobre deadlock, consulte Deadlock.

O exemplo a seguir mostra como TRY...CATCH pode ser usada para controlar deadlocks. A primeira seção cria uma tabela que será usada para demonstrar um estado deadlock e um procedimento armazenado que será usado para imprimir as informações de erros.

USE AdventureWorks2008R2;
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

Os scripts de código, a seguir, para sessão 1 e sessão 2 executam simultaneamente em duas conexões separadas do SQL Server Management Studio. Ambas as sessões tentam atualizar as mesmas linhas na tabela. Uma das seções terá êxito na operação de atualização durante a primeira tentativa, e a outra sessão será selecionada como vítima de deadlock. O erro vítima de deadlock fará a execução pular para o bloco CATCH, e a transação entrará em um estado não confirmável. Dentro do bloco CATCH, a vítima de deadlock pode reverter a transação e tentar, novamente, atualizar a tabela até que a atualização tenha êxito, ou até que o limite de tentativas seja atingido, ou o que ocorrer primeiro.

Sessão 1

Sessão 2

USE AdventureWorks2008R2;
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 AdventureWorks2008R2;
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 com RAISERROR

RAISERROR pode ser usado no bloco TRY ou CATCH de uma construção TRY...CATCH para afetar o comportamento do tratamento de erros.

RAISERROR com severidade de 11 a 19, executado dentro de um bloco TRY, transfere o controle para o bloco CATCH associado. O RAISERROR com severidade de 11 a 19, executado dentro de um bloco CATCH, retorna um erro para o aplicativo ou lote que chama. Desse modo, o RAISERROR pode ser usado para retornar informações para o chamador sobre o erro que fez o bloco CATCH executar. As informações de erro fornecidas pelas funções de erro TRY...CATCH podem ser capturadas na mensagem do RAISERROR, incluindo o número do erro original; no entanto, o número de erro do RAISERROR deve ser.>= 50000.

O RAISERROR com severidade igual ou inferior a 10 retorna uma mensagem informativa para o lote ou aplicativo de chamada, sem invocar o bloco CATCH.

O RAISERROR com severidade igual ou superior a 20 fecha a conexão de banco de dados sem invocar o bloco CAPTURA.

O exemplo de código a seguir mostra como o RAISERROR pode ser usado dentro de um bloco CATCH, para retornar as informações do erro original para o aplicativo ou lote de chamada. O procedimento armazenado usp_GenerateError executa uma instrução DELETE dentro de um bloco TRY, que gera um erro de violação de restrição. O erro faz a execução transferir para o bloco CATCH associado dentro do procedimento usp_GenerateError onde o procedimento armazenado usp_RethrowError é executado, para gerar as informações de erro de violação de restrição com o uso do RAISERROR. Esse erro gerado pelo RAISERROR retorna ao lote de chamada onde usp_GenerateError foi executado, e faz a execução transferir para o bloco CATCH associado no lote de chamada.

ObservaçãoObservação

RAISERROR pode gerar erros somente com estado de 1 a 127. Como o Mecanismo de Banco de Dados pode gerar erros com o estado 0, recomendamos que você verifique o estado de erro retornado por ERROR_STATE antes de passá-lo como um valor ao parâmetro de estado de RAISERROR.

USE AdventureWorks2008R2;
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

Alterando o fluxo de execução

Para alterar o fluxo de execução, o GOTO pode ser usado dentro de um bloco TRY ou um CATCH. O GOTO também pode ser usado para sair de um bloco TRY ou CATCH; no entanto, o GOTO não pode ser usado para inserir um bloco TRY ou CATCH.

Solução de tratamento de erros no banco de dados de exemplo AdventureWorks2008R2

O banco de dados de exemplo AdventureWorks2008R2 inclui uma solução de tratamento de erros projetada para registrar informações sobre erros identificados pelo bloco CATCH de uma construção TRY…CATCH, que poderá ser consultada ou analisada posteriormente.

Tabela dbo.ErrorLog

A tabela ErrorLog registra as informações sobre o número do erro, severidade do erro, estado do erro, nome do procedimento armazenado ou gatilho onde o erro ocorreu, número de linha na qual o erro ocorreu, e o texto completo da mensagem de erro. Ela também registra a data e hora na qual o erro ocorreu, e o nome de usuário que executou a rotina que gerou o erro. Essa tabela é populada quando o procedimento armazenado uspLogError é executado no escopo do bloco CATCH de uma construção TRY...CATCH.

dbo.uspLogError

O procedimento armazenado uspLogError registra informações na tabela ErrorLog sobre o erro que fez a execução transferir para o bloco CATCH de uma construção TRY... CATCH. Para o uspLogError inserir informações de erro na tabela ErrorLog, são exigidas as seguintes condições:

  • O uspLogError deve ser executado dentro do escopo de um bloco CATCH.

  • Se a transação atual estiver em um estado não confirmável, a transação será revertida antes de executar o uspLogError.

O parâmetro de saída @ ErrorLogID de uspLogError retorna o ErrorLogID da linha inserida por meio do uspLogError na tabela ErrorLog. O valor padrão de @ErrorLogID é 0. O exemplo a seguir mostra o código para uspLogError.

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

O procedimento armazenado uspPrintError imprime as informações sobre o erro que fez a execução se transferir para o bloco CATCH de uma construção TRY... CATCH. uspPrintError deve ser executado no escopo de um bloco CATCH; caso contrário, o procedimento retornará sem imprimir qualquer informação de erro. O exemplo a seguir mostra o código para uspPrintError.

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;

Exemplo de tratamento de erros

O exemplo a seguir demonstra a solução de tratamento de erros do AdventureWorks2008R2. O código dentro do bloco TRY tenta excluir o registro com ProductID 980, na tabela Production.Product. Uma restrição FOREIGN KEY da tabela não permite o êxito da instrução DELETE e um erro de violação de é gerado. Esse erro faz a execução transferir ao bloco CATCH. Dentro do bloco CATCH, ocorrem as seguintes ações:

  • O uspPrintError imprime as informações de erro.

  • Após a transação ser revertida, o uspLogError insere as informações de erro na tabela ErrorLog e retorna o ErrorLogID da linha inserida no parâmetro @ErrorLogID OUTPUT.

USE AdventureWorks2008R2;
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

Exemplo de manipulação de erro aninhado

O exemplo a seguir mostra o uso da construção aninhada 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

Aqui está o conjunto de resultados.

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9