Az SQL Server 2005-ben bevezetett TRY/CATCH hibakezelés óriási előrelépést jelentett az átlátható TSQL hibakezelés terén. Más nyelvekhez viszonyítva egy valami azonban hiányzott a képből, a THROW, amivel ki lehet váltani egy kivételt (exceptiont). Volt persze egy hasonló, a RAISERROR, ám ez kicsit más, mint a többi programnyelv throw-ja. A throw ugyanis általában kétféle dolgot tud. Az egyik, hogy feldob egy kivételt, a másik, hogy egy elkapott kivételt továbbdob. Ez utóbbi re-thrownak hívják, és azért nagyon hasznos, mert try-catch blokkban le tudjuk kezelni az eredeti hibát, pl. visszagörgetjük a tranzakciót, naplózunk vagy bármi egyéb akciót hajtunk végre, majd a catch ág végén újra feldobhatjuk az eredeti hibát. Így a kliens alkalmazás vagy SQL hívó pontosan megkapja az eredeti hiba részleteit, például a kivétel sora nem a catch ágba rakott (re)throw-ra mutat, hanem az eredeti hibát kiváltó SQL sorra.
A THROW tehát hasonló helyeken használatos, mint a RAISERROR, de van közöttük pár fontos különbség. A legfontosabbak: a THROW nem tud rendszer kivételeket generálni (SQL Server belső hibaüzeneteket), nem használja a sys.messages táblát (ebben vannak az előre definiált hibaüzenetek) és mindig 16-os severity levelű kivételeket dob.
Az alábbi példában egy update az, ami hibát dobhat, ha beleütközik egy idegen kulcsba. A kivételt elkapjuk, lelogoljuk, majd újradobjuk a kliens részére. A re-throw miatt a hibanaplózó logika nem szól bele a kliens által kapott hibarészletekbe.
BEGIN TRY
--Ez hibázhat
update HumanResources.Employee
set BusinessEntityID = -1
where BusinessEntityID = 2;
END TRY
BEGIN CATCH
--Naplózzuk a hibát
INSERT INTO
[dbo].[ErrorLog]
([ErrorTime]
,[UserName]
,[ErrorNumber]
,[ErrorSeverity]
,[ErrorState]
,[ErrorProcedure]
,[ErrorLine]
,[ErrorMessage])
VALUES(SYSDATETIME(),
SUSER_NAME(),
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE());
--Újradobjuk a kivételt a kliens részére
THROW;
END CATCH
A kliens ezt kapja:
Msg 547, Level 16, State 0, Line 3 The UPDATE statement conflicted with the REFERENCE constraint "FK_EmployeeDepartmentHistory_Employee_BusinessEntityID". The conflict occurred in database "AdventureWorks2012", table "HumanResources.EmployeeDepartmentHistory", column 'BusinessEntityID'.
Látható, hogy ez az eredeti hiba, a 3. sorra hivatkozik, nem a 29.-re, ahol valójában a THROW van. Az errorlog táblánkba ugyanez az információ kerül be:
Nyilván nem csak újradobásra jó a THROW, hanem olyan helyeken is, ahol eddig RAISERROR-ral dobtunk fel saját hibát.
Ez:
RAISERROR(N'Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok', 16, 1);
így néz ki THROW-val:
THROW 50000, N'Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok', 1;
A hívó mindkét esetben ezt kapja:
Msg 50000, Level 16, State 1, Line 1 Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok
Összegezve, ha saját hibákat szeretnénk a kliensek felé közvetíteni, illetve, ha meg kell tartani az eredeti hibáról szóló információkat, akkor a THROW-t érdemes használni. Speciális esetekben, pl. nem 16-os szintű hibákhoz, vagy ha a sys.messagesben előre definiált hibaüzenetet kell feldobni, akkor továbbra is elérhető a RAISERROR.
Egyébként az a tény, hogy a THROW nem használja a sys.messages táblát igen fontos lépés, mert így könnyebb költöztetni az adatbázist másik szerverre, nem kell átvinni egyedi hibaüzeneteket a master táblák között. Ez kapcsolódik az itt nem tárgyalt, SQL 2012 újdonság Contained Databases újításhoz is.