Kiedy kod T-SQL zawiera instrukcje modyfikujące dane lub komendy DDL oraz kiedy zawiera zadeklarowane transakcje lub procedury składowane to powinno się zawrzeć obsługę błędów. SQL Serwer dostarcza pełnego zestawu poleceń służących do obsługi błędów.

Wykrywanie i zgłaszanie błędów

Kiedy SQL Serwer napotyka na błąd w czasie wykonywania kody T-SQL to generuje stan błędu. Pisząc kod T-SQL trzeba przygotować się na wystąpienie błędów w czasie wykonywania kodu i radzić sobie z nimi aby nie stracić kontroli nad wykonywaniem. Ponadto we własnych procedurach czasem trzeba sprawdzić poprawność działania logiki jakiej SQL Serwer nie jest w stanie sprawdzić i wygenerować błędu. Przykładowo gdy tabela nie ma wierszy to może oznaczać dla nas, że nie chcemy dalej wykonywać naszego kodu. W takiej sytuacji trzeba wygenerować własny błąd i odpowiednio go obsłużyć. T-SQL zapewnia sposoby detekcji i zgłaszania błędów. Kiedy SQL Serwer generuje błąd to funkcja systemowa @@ERROR posiada dodatnią wartość liczbową oznaczającą numer błędu. Jeżeli kod zgłaszający błąd nie będzie zawarty w klauzuli TRY/CATCH to wiadomość błędu będzie wysłana bezpośrednio do klienta i nie będzie mogła być zinterpretowana i obsłużona przez kod T-SQL. Oprócz komunikatów o błędach zgłaszanych przez SQL Serwer można również zgłaszać własne błędy za pomocą komend:

  • RAISERROR - starsza wersja używana do wersji SQL Serwer 2008.
  • THROW - nowa komenda w SQL Serwerze 2012.

Analiza komunikatów błędów

Przykładowy komunikat błędu SQL Serwera wygląda tak:

Msg 547, Level 16, State 0, Line 11 The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Products_ Categories". The conflict occurred in database "TSQL2012", table "Production.Categories", column 'categoryid'.

Komunikat błędu SQL Serwera ma cztery części:

  • Numer błędu (Error number) - wartość liczbowa dodatnia
    • Błędy SQL Serwera mają numer z zakresu 1 do 49999.
    • Własne komunikaty błędu są numerowane od numeru 50001 wzwyż.
    • Numer 50000 jest zarezerwowany dla komunikatów które nie mają numeru błędu.
  • Stopień zagrożenia (Severity level) - istnieje 26 stopni od 0 do 25.
    • Błędy o poziomie 16 i wyższym są automatycznie logowane do dziennika SQL Serwera i aplikacji Windows.
    • Poziomy 19 do 25 mogą być określone tylko przez członków grupy sysadmin.
    • Poziomy 19 do 25 oznaczają błędy fatalne i sprawiają, że połączenie ma być zakończone i wszystkie transakcje wycofane.
    • Poziomy 1 do 10 mają charakter informacyjny.
  • Stan (State) - liczba całkowita do 127 używana przez Microsoft do celów wewnętrznych.
  • Komunikat błędu (Error message) - komunikat o długości do 255 znaków (unicode).
    • Komunikaty SQL Serwera są wymienione w sys.messages.
    • Własne komunikaty można dodać używając procedury sp_addmessage.

RAISERROR

Składnia komendy RAISERROR wygląda tak:

RAISERROR ( { msg_id | msg_str | @local_variable }
{ ,severity ,state }
[ ,argument [ ,...n ] ] )
[ WITH option [ ,...n ] ]

Komunikat (msg_id, msg_str lub @local_variable), stopień i stan są wymagane. komunikat może być literałem tekstowym jak na przykładzie:

RAISERROR ('Error in usp_InsertCategories stored procedure', 16, 0);

Można również użyć formatowania literału jak poniżej:

RAISERROR ('Error in % stored procedure', 16, 0, N'usp_InsertCategories');

Użycie zmiennej jest przedstawione na przykładzie poniżej:

DECLARE @message AS NVARCHAR(1000) = 'Error in % stored procedure';
RAISERROR (@message, 16, 0, N'usp_InsertCategories');

We wcześniejszych wersjach prosta forma RAISERROR int, 'string' była możliwa lecz od wersji 2012 jest zabroniona. Niektóre bardziej zaawansowane funkcje RAISERROR są następujące:

  • Używając poziomu zagrożenia od 0 do 9 można wywołać komunikaty informacyjne.
  • Można użyć poziomu zagrożenia powyżej 20 jeżeli używamy opcji WITH LOG i mamy uprawnienia sysadmin. Gdy taki błąd zostanie wywołany to połączenie zostanie zerwane w momencie wystąpienia błędu.
  • Można użyć opcji NOWAIT aby wysłać natychmiast komunikat do klienta.

THROW

Komenda THROW jest podobna do RAISERROR z pewnymi wyjątkami. Podstawowa składnia wygląda tak:

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

THROW posiada wiele wspólnych komponentów z RAISERROR lecz z następującymi różnicami:

  • THROW nie używa nawiasów do ograniczenia listy parametrów.
  • THROW może być użyty bez parametrów ale tylko w bloku CATCH.
  • Kiedy parametry są wyszczególnione to wymagane zawsze są error_number, message i state.
  • Error_number nie musi być zdefiniowany w sys.messages.
  • Parametr message nie może być formatowany ale można użyć sformatowanej za pomocą FORMAT-MESSAGE() zmiennej.
  • Parametr state musi być liczbą z zakresu 0 do 255.
  • Każdy parametr może być zmienną.
  • Nie ma parametru stopnia zagrożenia severity. Jego wartość to zawsze 16.
  • THROW zawsze kończy partię kodu wyjątkiem kiedy jest użyty w bloku TRY.
  • Przed THROW trzeba używać ;.

Przykładowe użycie THROW:

THROW 50000, 'Error in usp_InsertCategories stored procedure', 0;

Przykład formatowania komunikatu w THROW:

DECLARE @message AS NVARCHAR(1000) = 'Error in % stored procedure';
SELECT @message = FORMATMESSAGE (@message, N'usp_InsertCategories');
THROW 50000, @message, 0;

Istnieją pewne różnice pomiędzy użyciem THROW i RAISERROR. Przykładowo RAISERROR normalnie nie kończy partii kodu.

RAISERROR ('Hi there', 16, 0);
PRINT 'RAISERROR error'; -- Prints
GO

Natomiast instrukcja THROW zawsze kończy partię kodu:

THROW 50000, 'Hi there', 0;
PRINT 'THROW error'; -- Does not print
GO

Oto jeszcze klika ważnych różnic:

  • Nie można użyć THROW wraz z opcją NOWAIT w celu wysłania natychmiastowego komunikatu do klienta.
  • Nie można użyć THROW z poziomem zagrożenia wyższym niż 16 i klauzulą WITH LOG.

TRY_CONVERT i TRY_PARSE

Można użyć tych funkcji do wykrycia potencjalnych błędów przy konwersji typów danych. TRY_CONVERT próbuje rzutować wartość jako typ danych docelowych,  jeśli się powiedzie zwraca wartość rzutowaną na dany typ, jeżeli nie to zwraca NULL. Poniższy kod demonstruje testowanie dwóch wartości na typ datetime który nie zezwala na wartości mniejsze niż 1753-01-01:

SELECT TRY_CONVERT(DATETIME, '1752-12-31');
SELECT TRY_CONVERT(DATETIME, '1753-01-01');

Pierwsze zapytanie zwróci NULL ponieważ konwersja nie powiedzie się. Drugie zapytanie zwróci datę o typie datetime. TRY_PARSE pozwala na konwersję parametru wejściowego na żądany typ jeżeli to możliwe. Jeżeli konwersja jest niemożliwa to zostanie zwrócony NULL. Poniższy kod prezentuje działanie obu przypadków:

SELECT TRY_PARSE('1' AS INTEGER);
SELECT TRY_PARSE('B' AS INTEGER);

Pierwsze zapytanie zwróci wartość liczbową 1 a drugie NULL.

Obsługa błędów po wykryciu

Istnieją dwie metody obsługi wykrytych błędów: nieuporządkowana i uporządkowana. Nieuporządkowana polega na obsłudze każdego błędu w momencie jego wystąpienia za pomocą funkcji @@ERROR. Metoda uporządkowana używa jednego miejsca obsługi błędów jakim jest blok CATCH.

Użycie @@ERROR

Nieuporządkowana metoda obsługi błędów polega na sprawdzeniu pojedynczych instrukcji czy nie występuje w nich błąd zaraz po ich wykonaniu. Wykonuje się to za pomocą funkcji @@ERROR. Jeżeli instrukcja powiedzie się to @@ERROR ma wartość zero, w przeciwnym wypadku ma wartość kodu błędu. Niestety zapytanie o wartość @@ERROR nawet w klauzuli IF resetuje wartość ponieważ @@ERROR zawsze raportuje status ostatnio wykonanej komendy. Dlatego nie można testować wartości @@ERROR w kodzie obsługi błędu. Zamiast tego należy dodać kod który przechowa wartość kodu błędu w zmiennej. Ponieważ wartość @@ERROR trzeba sprawdzić przed każdą modyfikacją lub instrukcją DML to nieuporządkowana metoda obsługi ma fundamentalny problem. W każdym miejscu w którym może wystąpić błąd trzeba dodać dodatkowy kod obsługi błędu który zwiększa złożoność kodu.

Użycie XACT_ABORT z transakcjami

Jest inna opcja obsługi błędów która jest krok bliżej zorganizowanej obsługi błędów: SET XACT_ABORT (gdzie XACT oznacza transakcję). XACT_ABORT działa ze wszystkimi typami kodu i wpływa na całą partię wsadową kodu (batch). Jeżeli jakikolwiek błąd pojawi się w partii wsadowej to można sprawić, że całą partia zawiedzie jeżeli włączymy SET XACT_ABORT ON. Właściwość tę ustawia się dla sesji. SET XACT_ABORT posiada wiele zalet. Wycofuje wszystkie transakcje jeżeli wystąpi błąd z poziomem zagrożenia większym niż 10. Niestety ma również wiele ograniczeń:

  • Nie można przechwycić błędu ani jego kodu.
  • Wszystkie błędy z poziomem zagrożenia większym niż 10 powodują wycofanie transakcji.
  • Po wycofaniu transakcji można jedynie odnieść się do błędu zwróconego do klienta.

TRY/CATCH

Od wersji 2005 SQL Serwera istnieje konstrukcja TRY/CATCH która pozwala na zorganizowaną obsługę błędów. Dzięki TRY/CATCH można:

  • Otoczyć sprawdzany kod blokiem TRY.
    • Każdy blok TRY musi być zamknięty przez CATCH.
    • Oba bloki muszą być parą i występować w tej samej partii wsadowej (batch).
  • Jeżeli wykryty zostaje błąd w kodzie T-SQL w środku bloku TRY to obsługa zostaje przekierowana do bloku CATCH który uzupełnia ten blok.
    • Pozostały kod w bloku TRY nie zostaje wykonany.
    • Po wykonaniu bloku CATCH kontrola zostaje przekierowana do instrukcji T-SQL która jest następna po wyrażeniu END CATCH.
    • Jeżeli w TRY nie wykryto błędu to kontrola zostaje przekierowana do instrukcji T-SQL która jest następna po wyrażeniu END CATCH (przeskakuje blok CATCH).
  • Po wykryciu błędu w bloku TRY żaden komunikat nie jest wysyłany do klienta.
    • Jest to zachowanie inne niż w niezorganizowanej obsłudze błędów gdzie komunikat jest zawsze wysyłany do klienta.
    • Nawet RAISERROR w bloku TRY o poziomie zagrożenia od 11 do 19 nie powoduje wysłania komunikatu do klienta.

Używając TRY/CATCH nie trzeba sprawdzać pojedynczych instrukcji w celu wykrycia błędów. Prawie wszystkie błędy spowodowane przez kod zostaną obsłużone w CATCH. Kilka zasad korzystania z TRY/CATCH:

  • Błędy o poziomie zagrożenia pomiędzy 10 a 20 występujące w bloku TRY są zawsze obsługiwane w bloku CATCH.
  • Błędy o poziomie zagrożenia większym niż 20 które nie zamykają połączenia są również obsługiwane w bloku CATCH.
  • Błędy kompilacji i niektóre błędy czasu wykonania przerywają natychmiast partię wsadową i nie są obsługiwane w CATCH.
  • Jeżeli błąd występuje w bloku CATCH to transakcja jest przerywana i błąd jest zwracany do klienta chyba, że blok CATCH jest zawarty w innym bloku TRY.
  • W bloku CATCH można zatwierdzić lub wycofać transakcję. Chyba, że transakcja jest w stanie w którym nie może być już zatwierdzona to można ją tylko wycofać. Stan transakcji sprawdzamy za pomocą funkcji XACT_STATE.
  • Blok TRY/CATCH nie wyłapuje błędów powodujących rozłączenie jak np błędy krytyczne lub wykonanie komendy KILL przez użytkownika z uprawnieniami sysadmin.
  • Blok TRY/CATCH nie wyłapuje błędów na poziomie kompilacji, błędów składni lub błędów spowodowanych przez nieistniejące obiekty.
  • Bloki TRY/CATCH można zagnieżdżać .

Wraz z blokiem TRY/CATCH można używać szeregu funkcji do raportowania błędów:

  • ERROR_NUMBER - zwraca numer błędu.
  • ERROR_MESSAGE - zwraca komunikat błędu.
  • ERROR_SEVERITY - zwraca stopień zagrożenia błędu.
  • ERROR_LINE - zwraca linię partii wsadowej w której wystąpił błąd.
  • ERROR_PROCEDURE - zwraca nazwę funkcji, wyzwalacza lub procedury składowanej w której wystąpił błąd.
  • ERROR_STATE - zwraca stan błędu.

Przykładowe użycie funkcji w bloku CATCH:

BEGIN CATCH
-- Error handling
SELECT ERROR_NUMBER() AS errornumber
, ERROR_MESSAGE() AS errormessage
, ERROR_LINE() AS errorline
, ERROR_SEVERITY() AS errorseverity
, ERROR_STATE() AS errorstate;
END CATCH;

THROV vs. RAISERROR w bloku TRY/CATCH

W bloku TRY można użyć zarówno RAISERROR jak i THROW (z parametrami) do wygenerowania błędu przenoszącego obsługę do bloku CATCH. RAISERROR w bloku TRY musi mieć poziom zagrożenia z zakresu od 11 do 19 aby błąd został obsłużony w bloku CATCH. Gdy użyjemy jednej z tych instrukcji wewnątrz bloku TRY to SQL Serwer nie wyśle komunikatu do klienta. W bloku CATCH można użyć RAISERROR, THROW z parametrami oraz THROW bez parametrów. RAISERROR w bloku CATCH może być użyty do raportowania oryginalnego błędu lub innego własnego błędu który chcemy przesłać do klienta. Oryginalny numer błędu nie może być nadpisany. Musi to być niestandardowy numer błędu lub domyślny numer 50000. Aby zwrócić numer błędu można dodać go do ciągu @error_massage. Wykonanie bloku CATCH jest dalej wykonywane po wyrażeniu RAISERROR. THROW z parametrami tak jak RAISERROR można użyć  do raportowania oryginalnego błędu lub innego własnego błędu który chcemy przesłać do klienta. Jednakże THROW z parametrami zawsze generuje błąd z niestandardowym numerem błędu i poziomem zagrożenia 16, więc nie można uzyskać dokładnych informacji. THROW z parametrami przerywa działanie partii wsadowej  więc komendy po THROW nie będą wykonane. THROW bez parametrów może być użyty do przekazania oryginalnego błędu do klienta. Jest to najlepsza metoda przekazywania błędu do wywołującego klienta. Oryginalny komunikat jest przesłany do klienta i partia wsadowa jest natychmiast przerwana.

Użycie XACT_ABORT z blokiem TRY/CATCH

XACT_ABORT działa inaczej kiedy używany jest blok TRY. Zamiast przerwania transakcji przenosi kontrolę do bloku CATCH. Transakcja jest pozostawiona w stanie uniemożliwiającym zatwierdzenie (XACT_STATE() zwraca -1 ). Nie można zatwierdzić transakcji w bloku CATCH jeżeli włączona jest opcja XACT_ABORT. Transakcja musi być wycofana.

Ćwiczenia

I. Praca z nieuporządkowaną obsługą błędów

  1. Użyj kodu poniżej demonstrującego użycie funkcji @@ERROR do obsługi błędów.
    USE TSQL2012;
    GO
    DECLARE @errnum AS int;
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid, categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR;
    IF @errnum <> 0 -- Handle the error
    BEGIN
    PRINT 'Insert into Production.Products failed with error ' + CAST(@errnum AS VARCHAR);
    END;
    GO
    
  2. Uruchom poniższy kod demonstrujący działanie nieuporządkowanej obsługi błędów. Kod zawiera dwie instrukcje INSERT, pierwsza z nich zawodzi i transakcja jest wycofana natomiast druga powiedzie się i nie jest wycofana przez pierwszy ROLLBACK. Dzieje się tak ponieważ nieuporządkowana obsługa błędów nie przenosi kontroli do bloku obsługi błędów.
    USE TSQL2012;
    GO
    DECLARE @errnum AS int;
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    -- Insert #1 will fail because of duplicate primary key
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR;
    IF @errnum <> 0
    BEGIN
    IF @@TRANCOUNT > 0 ROLLBACK TRAN;
    PRINT 'Insert #1 into Production.Products failed with error ' +
    CAST(@errnum AS VARCHAR);
    END;
    -- Insert #2 will succeed
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid,unitprice, discontinued)
    VALUES(101, N'Test2: Bad categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR;
    IF @errnum <> 0
    BEGIN
    IF @@TRANCOUNT > 0 ROLLBACK TRAN;
    PRINT 'Insert #2 into Production.Products failed with error ' +
    CAST(@errnum AS VARCHAR);
    END;
    SET IDENTITY_INSERT Production.Products OFF;
    IF @@TRANCOUNT > 0 COMMIT TRAN;
    -- Remove the inserted row
    DELETE FROM Production.Products WHERE productid = 101;
    PRINT 'Deleted ' + CAST(@@ROWCOUNT AS VARCHAR) + ' rows';
    

II. Użycie XACT_ABORT do obsługi błędów

  1. Użyj kodu poniżej demonstrującego użycie XACT_ABORT do obsługi błędów.
    USE TSQL2012;
    GO
    SET XACT_ABORT ON;
    PRINT 'Before error';
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid, categoryid,
    unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET IDENTITY_INSERT Production.Products OFF;
    PRINT 'After error';
    GO
    PRINT 'New batch';
    SET XACT_ABORT OFF;
    
  2. Użyj kodu poniżej demonstrującego użycie XACT_ABORT z instrukcją THROW do obsługi błędów.
    USE TSQL2012;
    GO
    SET XACT_ABORT ON;
    PRINT 'Before error';
    THROW 50000, 'Error in usp_InsertCategories stored procedure', 0;
    PRINT 'After error';
    GO
    PRINT 'New batch';
    SET XACT_ABORT OFF;
    
  3. Użyj kodu poniżej demonstrującego użycie XACT_ABORT w transakcji. Użycie XACT_ABORT w transakcji nie pozwoli na wykonanie drugiej instrukcji INSERT jeżeli pierwsza zawiedzie. Niestety użycie XACT_ABORT nie pozwala również na odczyt numeru błędu.
    USE TSQL2012;
    GO
    DECLARE @errnum AS int;
    SET XACT_ABORT ON;
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    -- Insert #1 will fail because of duplicate primary key
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR;
    IF @errnum <> 0
    BEGIN
    IF @@TRANCOUNT > 0 ROLLBACK TRAN;
    PRINT 'Error in first INSERT';
    END;
    -- Insert #2 no longer succeeds
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(101, N'Test2: Bad categoryid', 1, 1, 18.00, 0);
    SET @errnum = @@ERROR;
    IF @errnum <> 0
    BEGIN
    -- Take actions based on the error
    IF @@TRANCOUNT > 0 ROLLBACK TRAN;
    PRINT 'Error in second INSERT';
    END;
    SET IDENTITY_INSERT Production.Products OFF;
    IF @@TRANCOUNT > 0 COMMIT TRAN;
    GO
    DELETE FROM Production.Products WHERE productid = 101;
    PRINT 'Deleted ' + CAST(@@ROWCOUNT AS VARCHAR) + ' rows';
    SET XACT_ABORT OFF;
    GO
    SELECT XACT_STATE(), @@TRANCOUNT;
    

Użycie TRY/CATCH

  1. Użyj kodu poniżej demonstrującego użycie TRY/CATCH do obsługi błędów. Błąd pierwszej instrukcji INSERT powoduje przeniesienie obsługi do bloku CATCH.
    USE TSQL2012;
    GO
    BEGIN TRY
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
    SET IDENTITY_INSERT Production.Products OFF;
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
    IF ERROR_NUMBER() = 2627 -- Duplicate key violation
    BEGIN
    PRINT 'Primary Key violation';
    END
    ELSE IF ERROR_NUMBER() = 547 -- Constraint violations
    BEGIN
    PRINT 'Constraint violation';
    END
    ELSE
    BEGIN
    PRINT 'Unhandled error';
    END;
    IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
    
  2. Użyj poniższego kodu demonstrującego użycie zmiennej do przechwycenia informacji o błędzie i ponownego wyrzucenia błędu poprzez RAISERROR.
    USE TSQL2012;
    GO
    SET NOCOUNT ON;
    DECLARE @error_number AS INT, @error_message AS NVARCHAR(1000), @error_severity AS
    INT;
    BEGIN TRY
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
    SET IDENTITY_INSERT Production.Products OFF;
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
    SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';
    SELECT @error_number = ERROR_NUMBER(), @error_message = ERROR_MESSAGE(),
    @error_severity = ERROR_SEVERITY();
    RAISERROR (@error_message, @error_severity, 1);
    IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
  3. Użyj poniższego kodu demonstrującego użycie THROW bez parametrów do wyrzucenia błędu do klienta.
    USE TSQL2012;
    GO
    BEGIN TRY
    BEGIN TRAN;
    SET IDENTITY_INSERT Production.Products ON;
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(1, N'Test1: Ok categoryid', 1, 1, 18.00, 0);
    INSERT INTO Production.Products(productid, productname, supplierid,
    categoryid, unitprice, discontinued)
    VALUES(101, N'Test2: Bad categoryid', 1, 10, 18.00, 0);
    SET IDENTITY_INSERT Production.Products OFF;
    COMMIT TRAN;
    END TRY
    BEGIN CATCH
    SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';
    IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    THROW;
    END CATCH;
    GO
    SELECT XACT_STATE() as 'XACT_STATE', @@TRANCOUNT as '@@TRANCOUNT';
    

Podsumowanie

  1. SQL Serwer pozwala na użycie instrukcji RAISERROR i THROW do wyrzucania błędów.
  2. Można odpytać @@ERROR aby dowiedzieć się czy wystąpił błąd.
  3. Można ustawić SET XACT_ABORT ON dla sesji aby wymusić zakończenie transakcji jeżeli pojawi się błąd.
  4. Nieuporządkowana obsługa błędów nie przekierowuje kodu do jednego miejsca obsługi w przypadku wystąpienia błędu.
  5. TRY/CATCH pozwala na obsługę błędów w jednym miejscu.
  6. Komenda THROW  może być użyta do ponownego wyrzucenia błędu.