Utilizzo di TRY...CATCH in Transact-SQL

Gli errori nel codice Transact-SQL possono essere elaborati utilizzando un costrutto TRY…CATCH simile alle funzionalità di gestione delle eccezioni dei linguaggi Microsoft Visual C++ e Microsoft Visual C#. Un costrutto TRY…CATCH è costituito da due parti: un blocco TRY e un blocco CATCH. Quando viene rilevata una condizione di errore in un'istruzione Transact-SQL contenuta in un blocco TRY, il controllo viene passato a un blocco CATCH, dove può essere eseguita l'elaborazione dell'errore.

Dopo la gestione dell'eccezione da parte del blocco CATCH, il controllo viene trasferito alla prima istruzione Transact-SQL successiva all'istruzione END CATCH. Se l'istruzione END CATCH è l'ultima istruzione in una stored procedure o in un trigger, il controllo viene restituito al codice che ha richiamato la stored procedure o il trigger. Le istruzioni Transact-SQL nel blocco TRY successive all'istruzione che ha generato un errore non verranno eseguite.

Se nel blocco TRY non sono presenti errori, il controllo passa all'istruzione immediatamente successiva all'istruzione END CATCH associata. Se l'istruzione END CATCH è l'ultima istruzione in una stored procedure o in un trigger, il controllo viene passato all'istruzione che ha richiamato la stored procedure o il trigger.

Un blocco TRY inizia con l'istruzione BEGIN TRY e termina con l'istruzione END TRY. Tra le istruzioni BEGIN TRY ed END TRY è possibile specificare una o più istruzioni Transact-SQL.

Un blocco TRY deve essere immediatamente seguito da un blocco CATCH. Un blocco CATCH inizia con l'istruzione BEGIN CATCH e termina con l'istruzione END CATCH. In Transact-SQL ogni blocco TRY è associato a un solo blocco CATCH.

Utilizzo di TRY…CATCH

Quando si utilizza il costrutto TRY…CATCH, considerare le linee guida e i suggerimenti seguenti:

  • Ogni costrutto TRY…CATCH deve essere incluso in un unico batch, stored procedure o trigger. Non è possibile, ad esempio, inserire un blocco TRY in un batch e il blocco CATCH associato in un altro batch. Lo script seguente genererebbe un errore:

    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 blocco TRY deve essere immediatamente seguito da un blocco CATCH.

  • I costrutti TRY…CATCH possono essere nidificati. Ciò significa che i costrutti TRY…CATCH possono essere inseriti in altri blocchi TRY e CATCH. Quando si verifica un errore all'interno di un blocco TRY nidificato, il controllo del programma viene trasferito al blocco CATCH associato al blocco TRY nidificato.

  • Per gestire un errore verificatosi in un blocco CATCH specifico, scrivere un blocco TRY…...CATCH all'interno del blocco CATCH.

  • Gli errori con una gravità maggiore o uguale a 20 che provocano l'interruzione della connessione da parte di Motore di database non verranno gestiti dal blocco TRY…CATCH. In TRY…CATCH, tuttavia, gli errori con gravità maggiore o uguale a 20 verranno gestiti nel caso in cui la connessione non venga interrotta.

  • Gli errori con una gravità minore o uguale a 10 sono considerati avvisi o messaggi informativi e non vengono gestiti dai blocchi TRY...CATCH.

  • Le situazioni di attenzione comportano l'interruzione di un batch anche se il batch si trova nell'ambito di un costrutto TRY…CATCH. Lo stesso vale per i messaggi di attenzione inviati da Microsoft Distributed Transaction Coordinator (MS DTC) nel caso di errore di una transazione distribuita. In MS DTC vengono gestite le transazioni distribuite.

    [!NOTA]

    Se una transazione distribuita viene eseguita nell'ambito di un blocco TRY e si verifica un errore, l'esecuzione viene trasferita al blocco CATCH associato. La transazione distribuita passa in uno stato bloccato. L'esecuzione nel blocco CATCH può essere interrotta da Microsoft Distributed Transaction Coordinator, che gestisce le transazioni distribuite. Quando si verifica l'errore, MS DTC invia una notifica in modo asincrono a tutti i server che partecipano alla transazione distribuita e interrompe tutte le attività coinvolte nella transazione distribuita. Questa notifica viene inviata in forma di messaggio di attenzione, non gestito da un costrutto TRY…CATCH, e il batch viene interrotto. Al termine dell'esecuzione di un batch, Motore di database esegue il rollback di tutte le transazioni bloccate attive. Se non sono stati inviati messaggi di errore quando la transazione è passata in uno stato bloccato, al termine del batch verrà inviato un messaggio di errore all'applicazione client indicante che è stata rilevata una transazione bloccata e ne è stato eseguito il rollback. Per ulteriori informazioni sulle transazioni distribuite, vedere Transazioni distribuite (Motore di database).

Funzioni di errore

TRY…CATCH utilizza le funzioni di errore seguenti per acquisire informazioni sull'errore:

  • ERROR_NUMBER() restituisce il numero di errore.

  • ERROR_MESSAGE() restituisce il testo completo del messaggio di errore. Il testo include i valori specificati per eventuali parametri sostituibili, ad esempio lunghezze, nomi di oggetti o orari.

  • ERROR_SEVERITY() restituisce la gravità dell'errore.

  • ERROR_STATE() restituisce il numero di contesto dell'errore.

  • ERROR_LINE() restituisce il numero di riga nella routine che ha causato l'errore.

  • ERROR_PROCEDURE() restituisce il nome della stored procedure o del trigger in cui si è verificato l'errore.

Le informazioni sull'errore vengono recuperate utilizzando queste funzioni da un punto qualsiasi nell'ambito del blocco CATCH di un costrutto TRY…CATCH. Le funzioni di errore restituiscono NULL se chiamate all'esterno dell'ambito di un blocco CATCH. È possibile fare riferimento alle funzioni di errore in una stored procedure e utilizzare tali funzioni per recuperare le informazioni sull'errore quando la stored procedure viene eseguita nel blocco CATCH. In questo modo, non sarà necessario ripetere il codice di gestione dell'errore in ogni blocco CATCH. Nell'esempio di codice seguente, l'istruzione SELECT nel blocco TRY genererà un errore di divisione per zero. L'errore verrà gestito dal blocco CATCH, in cui viene utilizzata una stored procedure per restituire le informazioni sull'errore.

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

Errori di compilazione e di ricompilazione a livello di istruzione

Vi sono due tipi di errore che non vengono gestiti da TRY…CATCH se l'errore si verifica allo stesso livello di esecuzione del costrutto TRY…CATCH:

  • Errori di compilazione, ad esempio errori di sintassi che impediscono l'esecuzione di un batch.

  • Errori che si verificano durante la ricompilazione a livello di istruzione, ad esempio errori di risoluzione dei nomi degli oggetti che si verificano dopo la compilazione a causa di una risoluzione dei nomi posticipata.

Quando il batch, la stored procedure o il trigger che contiene il costrutto TRY…CATCH genera uno di questi errori, l'errore non viene gestito dal costrutto TRY…CATCH. Questi errori vengono restituiti all'applicazione o al batch che ha chiamato la routine che ha provocato l'errore. Nell'esempio di codice seguente viene illustrata un'istruzione SELECT che causa un errore di sintassi. Se questo codice viene eseguito nell'editor di query di SQL Server Management Studio, l'esecuzione non verrà avviata in quanto non è possibile compilare il batch. L'errore verrà restituito all'editor di query e non verrà rilevato da TRY…CATCH.

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 differenza dell'errore di sintassi dell'esempio precedente, un errore che si verifica durante la ricompilazione a livello di istruzione non impedisce la compilazione del batch, ma comporta l'interruzione del batch non appena la ricompilazione dell'istruzione genera un errore. Se il batch, ad esempio, include due istruzioni e la seconda istruzione fa riferimento a una tabella che non esiste, la risoluzione dei nomi posticipata consente la compilazione corretta del batch e l'avvio dell'esecuzione senza eseguire l'associazione della tabella mancante al piano di query fino a quando l'istruzione non è stata ricompilata. L'esecuzione del batch viene interrotta in corrispondenza dell'istruzione che fa riferimento alla tabella mancante e viene restituito un errore. Questo tipo di errore non verrà gestito da un costrutto TRY…CATCH allo stesso livello di esecuzione in cui si è verificato l'errore. Nell'esempio seguente viene illustrato questo comportamento.

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

È possibile utilizzare TRY…CATCH per gestire gli errori che si verificano durante la compilazione o la ricompilazione a livello di istruzione eseguendo il codice che ha generato l'errore in un batch separato all'interno del blocco TRY. A tale scopo è possibile, ad esempio, inserire il codice in una stored procedure oppure eseguire un'istruzione Transact-SQL dinamica utilizzando sp_executesql. In questo modo, l'errore viene intercettato da TRY…CATCH a un livello di esecuzione superiore rispetto a quello in cui si è verificato. Nel codice seguente, ad esempio, viene illustrata una stored procedure che genera un errore di risoluzione dei nomi degli oggetti. Il batch che contiene il costrutto TRY…CATCH è in esecuzione a un livello superiore rispetto alla stored procedure, di conseguenza l'errore, che si verifica a un livello inferiore, viene intercettato.

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

Set di risultati:

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

Per ulteriori informazioni, vedere Risoluzione dei nomi posticipata e compilazione e la sezione relativa alla ricompilazione dei piani di esecuzione in Caching e riutilizzo del piano di esecuzione.

Transazioni bloccate

In un costrutto TRY…CATCH le transazioni possono passare in uno stato in cui la transazione rimane aperta non è possibile eseguirne il commit. La transazione non può eseguire nessuna azione che comporti una scrittura nel log delle transazioni, ad esempio la modifica di dati o il tentativo di eseguire il rollback a un punto di salvataggio. In questo stato, tuttavia, i blocchi acquisiti dalla transazione vengono mantenuti e la connessione rimane aperta. Gli effetti della transazione non vengono annullati finché non viene eseguita un'istruzione ROLLBACK oppure finché il batch non termina e non viene eseguito il rollback automatico della transazione da parte di Motore di database. Se non sono stati inviati messaggi di errore quando la transazione è passata in uno stato bloccato, al termine del batch verrà inviato un messaggio di errore all'applicazione client che indica che è stata rilevata una transazione bloccata e ne è stato eseguito il rollback.

Una transazione passa in uno stato bloccato all'interno di un blocco TRY quando si verifica un errore che, in caso contrario, avrebbe provocato l'interruzione della transazione. La maggior parte degli errori di un'istruzione DDL (Data Definition Language), ad esempio CREATE TABLE, o degli errori che si verificano quando l'opzione SET XACT_ABORT è impostata su ON comportano, ad esempio, l'interruzione della transazione all'esterno di un blocco TRY, mentre all'interno di un blocco TRY la transazione diventa bloccata.

Il codice in un blocco CATCH consente di verificare lo stato di una transazione utilizzando la funzione XACT_STATE. XACT_STATE restituisce -1 se nella sessione è presente una transazione bloccata. Se XACT_STATE restituisce -1, il blocco CATCH non deve eseguire azioni che comporterebbero scritture nel log. Nell'esempio di codice seguente viene illustrata la generazione di un errore da un'istruzione DDL e viene utilizzata la funzione XACT_STATE per verificare lo stato di una transazione al fine di intraprendere l'azione più appropriata.

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

Gestione di deadlock

Il costrutto TRY…CATCH può essere utilizzato per la gestione di deadlock. L'errore 1205 relativo alla vittima del deadlock può essere intercettato dal blocco CATCH ed è possibile eseguire il rollback della transazione fino a sbloccare i thread. Per ulteriori informazioni sui deadlock, vedere Utilizzo di deadlock.

Nell'esempio seguente viene illustrato come utilizzare TRY…CATCH per la gestione di deadlock. La prima sezione consente di creare una tabella che verrà utilizzata per dimostrare uno stato di deadlock e una stored procedure che verrà utilizzata per la stampa delle informazioni sull'errore.

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

Gli script di codice seguenti per la sessione 1 e la sessione 2 vengono eseguiti simultaneamente in due connessioni separate di SQL Server Management Studio. Entrambe le sessioni tentano di aggiornare le stesse righe nella tabella. Una delle sessioni riuscirà ad eseguire l'operazione di aggiornamento nel corso del primo tentativo, mentre l'altra verrà scelta come vittima del deadlock. A causa dell'errore relativo alla vittima del deadlock, l'esecuzione passerà al blocco CATCH e la transazione passerà in uno stato bloccato. Nel blocco CATCH la vittima del deadlock può eseguire il rollback della transazione e riprovare ad aggiornare la tabella fino a quando l'operazione viene eseguita correttamente oppure viene raggiunto il limite di tentativi, a seconda della situazione che si verifica per prima.

Sessione 1

Sessione 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

L'istruzione RAISERROR può essere utilizzata nel blocco TRY o nel blocco CATCH di un costrutto TRY…CATCH per modificare il comportamento di gestione dell'errore.

L'istruzione RAISERROR con un livello di gravità compreso tra 11 e 19 eseguita in un blocco TRY provoca il trasferimento del controllo al blocco CATCH associato. L'istruzione RAISERROR con un livello di gravità compreso tra 11 e 19 eseguita in un blocco CATCH restituisce un errore all'applicazione o al batch che ha eseguito la chiamata. In questo modo, l'istruzione RAISERROR può essere utilizzata per restituire al chiamante le informazioni sull'errore che ha causato l'esecuzione del blocco CATCH. È possibile acquisire nel messaggio RAISERROR le informazioni sull'errore indicate dalle funzioni di errore TRY…CATCH, incluso il numero di errore originale. Il numero di errore per RAISERROR, tuttavia, deve essere >= 50000.

L'istruzione RAISERROR con un livello di gravità minore o uguale a 10 restituisce al batch o all'applicazione che ha eseguito la chiamata un messaggio informativo, senza richiamare un blocco CATCH.

L'istruzione RAISERROR con un livello di gravità maggiore o uguale a 20 provoca l'interruzione della connessione di database senza richiamare il blocco CATCH.

Nell'esempio di codice seguente viene illustrato l'utilizzo di RAISERROR in un blocco CATCH per restituire le informazioni sull'errore originale all'applicazione o al batch che ha eseguito la chiamata. La stored procedure usp_GenerateError esegue un'istruzione DELETE in un blocco TRY che genera un errore di violazione di un vincolo. Questo errore provoca il trasferimento dell'esecuzione al blocco CATCH associato in usp_GenerateError, dove viene eseguita la stored procedure usp_RethrowError per generare le informazioni sull'errore di violazione di un vincolo tramite RAISERROR. Questo errore generato da RAISERROR viene restituito al batch che ha eseguito la chiamata, in cui era stata eseguita usp_GenerateError, e l'esecuzione viene trasferita al blocco CATCH associato nel batch che ha eseguito la chiamata.

[!NOTA]

RAISERROR può generare errori con contesto compreso esclusivamente tra 1 e 127. Poiché Motore di database può generare errori con contesto 0, è consigliabile verificare lo stato dell'errore restituito da ERROR_STATE prima di passarlo come valore al parametro di stato di 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

Modifica del flusso di esecuzione

Per modificare il flusso di esecuzione, è possibile utilizzare GOTO in un blocco TRY o in un blocco CATCH. È inoltre possibile utilizzare GOTO per uscire da un blocco TRY o CATCH ma non per entrare in un blocco TRY o CATCH.

Soluzione di gestione degli errori nel database di esempio AdventureWorks

Il database di esempio AdventureWorks include una soluzione di gestione degli errori progettata per registrare le informazioni sugli errori intercettati dal blocco CATCH di un costrutto TRY…CATCH, utilizzabili in un secondo momento per eseguire query o a scopo di analisi.

Tabella dbo.ErrorLog

La tabella ErrorLog consente di registrare le informazioni relative al numero, alla gravità e al contesto dell'errore, al nome della stored procedure o del trigger e al numero di riga in cui si è verificato l'errore e infine al testo completo del messaggio di errore. Consente inoltre di registrare la data e l'ora in cui si è verificato l'errore e il nome dell'utente che ha eseguito la routine che ha generato l'errore. Questa tabella viene popolata quando la stored procedure uspLogError viene eseguita nell'ambito del blocco CATCH di un costrutto TRY…CATCH. Per ulteriori informazioni, vedere Tabella ErrorLog (AdventureWorks).

dbo.uspLogError

La stored procedure uspLogError registra nella tabella ErrorLog le informazioni sull'errore che ha causato il trasferimento dell'esecuzione al blocco CATCH di un costrutto TRY…CATCH. Affinché uspLogError possa inserire le informazioni sull'errore nella tabella ErrorLog, devono verificarsi le condizioni seguenti:

  • uspLogError viene eseguita nell'ambito di un blocco CATCH.

  • Se la transazione corrente si trova in uno stato bloccato, ne viene eseguito il rollback prima di eseguire uspLogError.

Il parametro di output @ErrorLogID di uspLogError restituisce il valore di ErrorLogID della riga inserita da uspLogError nella tabella ErrorLog. Il valore predefinito di @ErrorLogID è 0. Nell'esempio seguente viene illustrato il codice per uspLogError. Per ulteriori informazioni, vedere Stored procedure in 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

La stored procedure uspPrintError consente di stampare le informazioni sull'errore che ha causato il trasferimento dell'esecuzione al blocco CATCH di un costrutto TRY…CATCH. uspPrintError deve essere eseguita nell'ambito di un blocco CATCH, in caso contrario non verranno stampate le informazioni sull'errore. Nell'esempio seguente viene illustrato il codice per uspPrintError. Per ulteriori informazioni, vedere Stored procedure in 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;

Esempio di gestione degli errori

Nell'esempio seguente viene illustrata la soluzione di gestione degli errori di AdventureWorks. Il codice all'interno di un blocco TRY tenta di eliminare il record con ProductID 980 nella tabella Production.Product. Un vincolo FOREIGN KEY nella tabella impedisce l'esecuzione dell'istruzione DELETE e viene generato un errore di violazione di un vincolo. Questo errore causa il trasferimento dell'esecuzione al blocco CATCH. All'interno del blocco CATCH si verificano le azioni seguenti:

  • uspPrintError stampa le informazioni sull'errore.

  • Dopo l'esecuzione del rollback della transazione, uspLogError immette le informazioni sull'errore nella tabella ErrorLog e restituisce il valore di ErrorLogID della riga inserita nel parametro @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

Esempio di gestione degli errori nidificata

Nell'esempio seguente viene illustrato l'utilizzo dei costrutti nidificati 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

Set di risultati:

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9