Tra le nuove funzionalità di programmazione T-SQL in SQL Server 2005 spicca la cattura degli errori tramite il blocco Try Catch. Questo costrutto è ben noto agli sviluppatori java, c#, c++, in quanto ricalca il pattern tipo utilizzato in questi linguaggi per intercettare le eccezioni sollevate nelle routines.
Microsoft ha comunque potenziato il classico Try/Catch in SQL Server 2005 aggiungendo un nutirito gruppo di funzioni di sistema in grado restituire codici e livelli severità degli errori T-SQL intercettati. In questo articolo andremo ad esporre modalità e limiti di utilizzo di Try/Catch.
Sintassi di Try/Catch
Usare il costrutto Try/Catch all'interno di uno script T-SQL è semplicissimo. Ci sono due blocchi di codice, il "blocco Try" con le istruzioni T-SQL che devono essere eseguite dalla procedura e il "blocco Catch" con le istruzioni T-SQL che da eseguire nel caso si verifichino errori nel blocco precedente.
Un blocco Try inizia sempre con l'istruzione BEGIN TRY
e termina con l'istruzione END TRY
, idem per il blocco Catch (BEGIN CATCH
, END CATCH
). Ad ogni blocco Try corrisponde un blocco Catch ed ogni blocco Try può contenere una o più istruzioni T-SQL.
Listato 1. Sintassi di Try Catch
BEGIN TRY
{ sql_statement | statement_block }
END TRY
BEGIN CATCH
{ sql_statement | statement_block }
END CATCH
Quando si verifica un errore nel blocco Try il controllo del codice viene passato al blocco Catch all'interno del quale è codificata la logica di gestione dell'errore (loggin degli errori, rollback di transazioni, invio di messaggi agli utenti, ecc...). Al contrario, quando non si verificano errori, il controllo passa direttamente all'istruzione End Catch associata.
Le istruzioni all'interno di un blocco Catch vengono sempre eseguite a meno che si faccia esplicitamente ricorso a un'istruzione che cambi il flusso di esecuzione come RETURN
o GOTO
.
Listato 2. Esempio di blocco Try/Catch in azione
CREATE TABLE test (id int IDENTITY(1,1) PRIMARY KEY, data NVARCHAR(64) not null);
BEGIN TRY
INSERT INTO test (id,data) VALUES (1,'test');
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() as ErrorNumber, ERROR_MESSAGE() as ErrorMessage;
END CATCH
L'istruzione INSERT
genera un errore perchè forza l'inserimento di un valore all'interno di una colonna identity
. L'errore sollevato viene catturato dal blocco Try e passato al blocco Catch. Quest'ultimo formatta un messaggio di errore utilizzando un'istruzione SELECT
e le funzioni ERROR_NUMBER()
e ERROR_MESSAGE()
.
L'output restituito da ERROR_MESSAGE
Quando IDENTITY_INSERT è OFF non è possibile inserire un valore esplicito per la colonna Identity nella tabella 'test'
Il codice ERROR_NUMBER()
restituito è 544
.
Recuperare informazioni sugli errori
Nell'ambito di un blocco CACTH SQL Server permette di utilizzare delle funzioni di sistema in grado di recuperare informazioni sullo stato e sulla natura degli errori, ecco un elenco:
Funzione | Descrizione |
---|---|
ERROR_NUMBER() |
restituisce il numero dell'errore |
ERROR_SEVERITY() |
restituisce la gravità dell'errore |
ERROR_STATE() |
restituisce il numero di contesto dell'errore |
ERROR_PROCEDURE() |
restituisce il nome della stored procedure o del trigger in cui si è verificato l'errore |
ERROR_LINE() |
restituisce il numero di riga all'interno della routine che ha causato l'errore |
ERROR_MESSAGE() |
restituisce il contenuto del messaggio di errore |
Queste funzioni restituiscono un valore NULL se vengono chiamate all'esterno di un blocco Try/Cacth e sono utilizzabili all'interno del blocco Catch, o in una stored procedure richiamata dal suo interno.
Utilizzando queste funzioni all'interno di una stored procedure e richiamando la stessa da un blocco Catch è possibile centralizzare la raccolta degli errori nelle applicazioni.
Listato 3. Centralizzare la raccolta degli errori di una applicazione
-- Creazione della procedura
CREATE PROCEDURE prcCatchErrors
AS
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() as ErrorState,
ERROR_LINE () as ErrorLine,
ERROR_PROCEDURE() as ErrorProcedure,
ERROR_MESSAGE() as ErrorMessage;
GO
-- Uso la procedura in un blocco CACTH
BEGIN TRY
...
END TRY
BEGIN CATCH
exec dbo.prcCatchErrors
END CATCH
Try/Catch e le transazioni
Per controllare lo stato delle transazioni esplicite all'interno del blocco Try/Catch è possibile usare due funzioni: @@TRANCOUNT
e XACT_STATE()
.
La prima funzione (già presente in SQL Server 2000) restituisce un numero intero che rappresenta il numero di transazioni aperte nella sessione, mentre la seconda (introdotta in SQL Server 2005) restituisce un range di tre valori (1,-1,0) in grado di identificare lo stato delle transazioni attualmente in corso.
Interrogando il valore restituito da XACT_STATE()
all'interno di un blocco Try/Catch possiamo sapere se fare o no il COMMIT
(o il ROLLBACK
) di una transazione. I valori di ritorno della funzione XACT_STATE()
hanno il seguente significato:
Funzione | Descrizione |
---|---|
1 | se è attiva una transazione la sessione può compiere qualsiasi azione (COMMIT o ROLLBACK ) |
0 | non ci sono transazioni attive |
-1 | se esiste una transazione aperta, non è possibile eseguire il COMMIT |
Vediamo un possibile pattern di utilizzo di XACT_STATE()
in un blocco Try/Cacth.
Listato 4. Usare XACT_STATE() per valutare lo stato di una transazione in un blocco Catch
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
INSERT INTO test (data) VALUES ('test')
SELECT 1/0; -- divisione per zero
END TRY
BEGIN CATCH
--Catturo errori
exec dbo.prcCatchErrors
IF (XACT_STATE()) = -1
ROLLBACK TRAN
IF (XACT_STATE()) = 1
COMMIT TRAN
END CATCH
Nella prima riga del listato 4 l'opzione XACT_ABORT
è settata su "ON", così facendo la transazione viene bloccata (congelata) quando si verifica l'errore della divisione per 0. Quando l'errore passa al blocco Catch è sufficiente usare la funzione XACT_STATE()
per valutare le azioni da intraprendere (ROLLBACK
o COMMIT
).
Dove Try Catch fallisce
Il costrutto Try/Catch non è sempre efficace nell'intercettazione degli errori. Esistono situazioni particolari in cui non ha alcun effetto, ad esempio quando si verificano:
- Avvisi o messaggi con un livello di gravità minore o uguale a 10.
- Errori con un livello di gravità superiore o uguale a 20 che implicano un blocco del SQL Server Database Engine, cioè l'interruzione della connessione verso il database, per la sessione in corso.
- Errori di compilazione o ricompilazione a livello di singola istruzione del batch di comandi.