SQL Server 2012 - Użycie instrukcji THROW w obsłudze błędów zapytań T-SQL  Udostępnij na: Facebook

Autor: Damian Widera

Opublikowano: 2012-11-07

SQL Server 2005 pozwalał programistom na użycie mechanizmu strukturalnej obsługi wyjątków, powszechnego w takich językach programowania, jak C#, Visual Basic czy Java. Wcześniejsza wersja serwera umożliwiała jedynie proste sprawdzanie – czy błąd wystąpił. Jeśli systemowa zmienna @@ERROR przechowywała wartość różną od 0, to zapytanie, które poprzedzało jej sprawdzenie, kończyło się błędem. Sprawdzanie to należało przeprowadzić jednak po każdej operacji DML.

Poniższy przykład ilustruje sprawdzanie wystąpienia błędu w zapytaniu wstawiającym wiersze do tabeli, które narusza warunek unikalności:

USE SQL2012
GO

--utworzenie tabeli testowej oraz indeksu zapewniającego unikalność wierszy
CREATE TABLE ThrowTest
(
    ID INT NOT NULL
    ,FILLER CHAR(4000)  
)ON [PRIMARY]
GO
CREATE UNIQUE INDEX idx_uq_ID ON ThrowTest
(
    ID ASC
) ON [PRIMARY]
GO

--SQL Server 2000 i starsze
INSERT INTO ThrowTest 
VALUES(1,'a'),(2,'b'),(1,'c');
IF @@ERROR<> 0 
    PRINT 'Błąd';
ELSE PRINT 'OK'

Począwszy od wersji SQL Server 2005 programiści mogą używać strukturalnej obsługi wyjątków. Jeśli błąd wystąpi wewnątrz bloku kodu, pomiędzy frazami BEGIN TRY oraz END TRY, to wykonanie kodu jest zatrzymywane i przenoszone do bloku pomiędzy frazami BEGIN CATCH oraz END CATCH.

SQL Server nie wspiera obecnie wszystkich aspektów związanych ze strukturalną obsługą wyjątków, np. brakuje obsługi klauzuli FINALLY.

Należy pamiętać, że obsługa wyjątków odbywa się w trakcie wykonywania kodu TSQL. Błędy kompilacji nie są obsługiwane w ramach strukturalnej obsługi wyjątków.

SQL Server umożliwia sprawdzanie metadanych związanych z wystąpieniem błędu, które dostępne są w bloku CATCH:

  • numer błędu, który można sprawdzić za pomocą funkcji ERROR_NUMBER(),
  • opis błędu, który dostępny jest w funkcji ERROR_MESSAGE(),
  • ważność błędu, tzw. severity, jest liczbą z zakresu od 1 do 25 i dostępna jest w funkcji ERROR_SEVERITY(),
  • nazwę procedury składowanej, w której wystąpił błąd. Funkcja ERROR_PROCEDURE() zwróci NULL, jeśli błąd wystąpił poza procedurą składowaną,
  • numer linii, w której wystąpił błąd. Informacja ta dostępna jest w funkcji ERROR_LINE.

Programiści mają również możliwość wywołania funkcji RAISERROR, która może być wywołana w dwóch sytuacjach:

  • w kodzie TSQL i wtedy, kiedy generuje błąd o określonych parametrach, które mogą być następnie odczytane we frazie CATCH,
  • we frazie CATCH, wówczas inicjuje przekazanie błędu do procedury lub nadrzędnego bloku kodu.

Poniższy przykład pokazuje sposób użycia fraz TRY-CATCH, systemowych funkcji do związanych z obsługą wyjątków oraz funkcji RAISERROR:

BEGIN TRY
    INSERT INTO ThrowTest 
    VALUES(1,'a'),(2,'b'),(1,'c');
END TRY
BEGIN CATCH
    SELECT ERROR_LINE() AS [numer linii], 
           ERROR_MESSAGE() AS [opis],
           ERROR_NUMBER() AS [numer],
           ERROR_PROCEDURE() AS [nazwa procedury],
           ERROR_SEVERITY() AS [severity],
           ERROR_STATE() AS [state]

    RAISERROR (N'Błąd', -- Tekst błedu,
                16, -- Severity,
                1 -- State
              );

END CATCH

Kolejny przykład pokazuje inną metodę przekazywania informacji o błędzie do procedury nadrzędnej lub aplikacji klienckiej. W bloku CATCH zadeklarowano zmienne, którym przypisano wartości funkcji systemowych, związanych z obsługą błędów, a następnie przekazano jako parametry funkcji RAISERROR.

BEGIN TRY
    INSERT INTO ThrowTest 
    VALUES(10,'a'),(12,'b'),(13,'c');
END TRY
BEGIN CATCH

    DECLARE @msg NVARCHAR(4000) =   ERROR_MESSAGE();
    DECLARE @severity tinyint =   ERROR_SEVERITY();
    DECLARE @state tinyint =   ERROR_STATE();

    RAISERROR (@msg,@severity,@state);

END CATCH

GO

Ostatni z serii przykładów, związanych z funkcją RAISERROR, przedstawia możliwość wywołania tej funkcji w bloku TRY, a następnie na ponownym jej użyciu w bloku CATCH. Metoda ta pozwala na wywołanie konkretnego błędu, zdefiniowanego przez użytkownika. Należy jednak pamiętać, że jeśli trzeba wyświetlić konkretny numer błędu z określoną wiadomością, to taki komunikat powinien zostać zarejestrowany procedurą składowaną sp_add_message. To jednak może powodować komplikacje przy przenoszeniu kodu pomiędzy serwerami, ponieważ trzeba pamiętać o dodaniu:

BEGIN TRY
    RAISERROR('To jest błąd',16,1);
END TRY
BEGIN CATCH

    DECLARE @msg NVARCHAR(4000) =   ERROR_MESSAGE();
    DECLARE @severity tinyint =   ERROR_SEVERITY();
    DECLARE @state tinyint =   ERROR_STATE();

    RAISERROR (@msg,@severity,@state);

END CATCH
GO

SQL Server 2012 wprowadza nową frazę THROW, która pozwala na wywołanie wyjątku i przekazanie go do procedury nadrzędnej lub do aplikacji klienckiej.

Definicja frazy THROW jest następująca:     

THROW [ { error_number | @local_variable },
        { message | @local_variable },
        { state | @local_variable }
] [ ; ]

Fraza THROW nie wymaga, aby numer błędu, który zostanie wywołany był zdefiniowany w sys.messages. Oczywiście, można w ten sposób przekazać tylko błędy o numerach z zakresu powyżej 50 000, które są zarezerwowane dla użytkowników. 

Paramter message jest typu nvarchar(2048), a state jest liczbą całkowitą z zakresu od 0 do 255.

Fraza THROW nie umożliwia sterowania parametrem severity, który zawsze będzie miał wartość 16.

THROW można użyć na jeden z dwóch sposobów:

  • z jakiegokolwiek miejsca w kodzie TSQL. Jeśli błąd nie wystąpi w ramach frazy TRY-CATCH, to połączenie ze SQL Server zostanie zakończone:
THROW 55000, 'Błąd', 1;
GO
  • z bloku CATCH. Nie trzeba wtedy przekazywać funkcji systemowych, aby przekazać informacje o błędzie:
BEGIN TRY
    INSERT INTO ThrowTest 
    VALUES(10,'a'),(12,'b'),(13,'c');
END TRY
BEGIN CATCH
    THROW
END CATCH