There are two distinctly different ways to code error handling with SQL Server: ■ Legacy error handling is how it’s been done since the beginning of SQL Server, using@@errorto see the er
Trang 2T-SQL Error Handling
IN THIS CHAPTER Legacy error handling Try/catch blocks Rethrowing errors
So an atom goes into a bar and says to the barkeeper, ‘‘Hey, I think I’ve lost
an electron.’’
‘‘Are you sure?’’ asks the barkeep
‘‘Of course, in fact, I’m positive.’’
Lame, I know, but it’s my favorite geek joke; I couldn’t help it Back to SQL,
despite our best efforts, any application can lose an electron every once in a
while — the trick is to handle it in a positive way
Of course, all robust programming languages provide some method for trapping,
logging, and handling errors In this area, T-SQL has a sad history (almost as sad
as that joke), but it’s made significant progress with SQL Server 2005
There are two distinctly different ways to code error handling with SQL Server:
■ Legacy error handling is how it’s been done since the beginning of
SQL Server, using@@errorto see the error status of the previous SQL
statement
■ Try/catch was introduced in SQL Server 2008, bringing SQL Server into
the 21st century
Legacy Error Handling
Historically, T-SQL error handling has been tedious at best I’d prefer to not even
include this legacy method of handling errors, but I’m sure you’ll see it in old
code, so it must be covered
Trang 3What’s New with Error Handling?
Unfortunately, there is nothing new in 2008 when it comes to error handling However, when performing
code reviews for third-party software vendors, I rarely see production code with any error handling,
much less the improved try catch, which was introduced in SQL Server 2005 So, from an adoption
standpoint, it may as well be new
The basic error information system functions, such as@@errorand@@rowcount, contain the status
for the previous T-SQL command in the code This means that the legacy method of error handling
must examine T-SQL’s system functions and handle the error after each SQL statement that might
potentially encounter an error
@@error system function
The@@errorsystem function will contain the integer error code for the previous T-SQL statement
A0indicates success
The difficulty is that@@error, unlike other languages that hold the last error in a variable until another
error occurs, is updated for every command, so even testing its value updates it
The following code sample attempts to update the primary key to a value already in use This violates
the foreign key constraint and generates an error The twoprintcommands demonstrate how
@@erroris reset by every T-SQL command The firstprintcommand displays the success or failure
of the update
The secondprintcommand (results in bold) displays the success or failure of the previousprint
command:
USE Family;
UPDATE Person SET PersonID = 1 Where PersonID = 2;
Print @@error;
Print @@error;
Result:
Msg 2627, Level 14, State 1, Line 2 Violation of PRIMARY KEY constraint ‘PK Person AA2FFB847F60ED59’
Cannot insert duplicate key in object ‘dbo.Person’
The statement has been terminated
2627
0
Trang 4The solution to the ‘‘last error status’’ problem is to save the error status to a local variable This solution
retains the error status so it may be properly tested and then handled The following batch uses@erras
a temporary error variable:
USE Family;
DECLARE @err INT;
UPDATE Person
SET PersonID = 1
Where PersonID = 2
SET @err = @@error;
IF @err <> 0
BEGIN
error handling code
PRINT @err;
END;
Result:
Msg 2627, Level 14, State 1, Line 2
Violation of PRIMARY KEY constraint ‘PK Person AA2FFB847F60ED59’
Cannot insert duplicate key in object ‘dbo.Person’
The statement has been terminated
2627
@@rowcount system function
Another way to determine whether the query was a success is to check the number of rows affected
Even if no error was generated, it’s possible that the data didn’t match and the operation failed, which
might indicate a data, logic, or business rule problem The@@rowCountsystem function is useful for
checking the effectiveness of the query
The reset issue that affects@@erroralso affects@@rowcount
The following batch uses@@rowcountto check for rows updated The failure results from the
incor-rectWHEREclause condition No row withPersonID = 100exists.@@rowcountis used to detect the
query failure:
USE FAMILY;
UPDATE Person
SET LastName = ‘Johnson’
WHERE PersonID = 100;
IF @@rowCount = 0
BEGIN
error handling code
PRINT ‘no rows affected’;
END;
Trang 5no rows affected
To capture both the@@errorand the@@rowcountfunctions, use aSELECTstatement with two
variables:
SELECT @err = @@error, @rcount = @@rowcount
Raiserror
To return custom error messages to the calling procedure or front-end application, use theRAISERROR
command Two forms forRAISERRORexist: a legacy simple form and the recommended complete form
The simple raiserror form
The simple form, which dates from the Sybase days, passes only a hard-coded number and message The
severity level is always passed back as16— user error severe:
RAISERROR ErrorNumber ErrorMessage;
For example, this code passes back a simple error message:
RAISERROR 5551212 ‘Unable to update customer.’;
Result:
Msg 5551212, Level 16, State 1, Line 1
’Unable to update customer.’
The simple form is deprecated and will be removed in a future version of SQL Server I don’t recommend writing new code using this form — it’s included here only in case you see this form in legacy code.
The improved raiserror form
The improved form (introduced back in SQL Server 7) incorporates the following four useful features
into theRAISERRORcommand:
■ Specifies the severity level
■ Dynamically modifies the error message
■ Uses serverwide stored messages
■ May optionally log the error to the event log The syntax for the improvedRAISERRORadds parameters for the severity level, state (seldom used),
and message-string arguments:
RAISERROR ( message or number, severity, state, optional arguments ) WITH LOG;
Trang 6Error severity
Windows has established standard error-severity codes, listed in Table 23-1 The other severity codes are
reserved for Microsoft’s use In any case, the severity code you’ll use for yourRAISERRORwill almost
always be 16
TABLE 23-1
Available Severity Codes
Severity Code Description
10 Status message: Does not raise an error, but returns a message, such
as a PRINT statement 11–13 No special meaning
14 Informational message
15 Warning message: Something may be wrong
16 Critical error: The procedure failed
Adding variable parameters to messages
The error message can be a fixed-string message or the error number of a stored message Either type
can work with optional arguments
The arguments are substituted for placeholders within the error message While several types and
options are possible, the placeholders I find useful are%sfor a string and%ifor a signed integer The
following example uses one string argument:
RAISERROR (’Unable to update %s.’, 14, 1, ‘Customer’);
Result:
Msg 50000, Level 14, State 1, Line 1
Unable to update Customer
Stored messages
TheRAISERRORcommand can also pull a message from thesys.messagessystem view Message
numbers 1–50,000 are reserved for Microsoft Higher message numbers are available for user-defined
messages The benefit of using stored messages is that all messages are forced to become consistent and
numbered
Note that withsys.messagesstored messages, the message-number scheme is serverwide If two
ven-dors, or two databases, use overlapping messages, then no division exists between databases, and there’s
no solution beyond recoding all the error handling on one of the projects The second issue is that when
migrating a database to a new server, the messages must also be moved
Trang 7Thesys.messagestable includes columns for themessage_id,text,severity, and whether
the error should be logged However, the severity of theRAISERRORcommand is used instead of the
severityfrom thesys.messagestable, sosys.messages.severityis moot
To manage messages in code, use thesp_addmessagesystem stored procedure:
EXEC sp_addmessage 50001, 16, ‘Unable to update %s’;
For database projects that may be deployed in multiple languages, the optional@langparameter can be
used to specify the language for the error message
If the message already exists, then areplaceparameter must be added to the system stored procedure
call, as follows:
EXEC sp_addmessage 50001, 16,
‘Update error on %s’, @replace = ‘replace’;
To view the existing custom messages, select from thesys.messagessystem view:
SELECT * FROM sys.messages WHERE message_id > 50000;
Result:
message_id language_id severity is_event_logged text - - - -
-50001 1033 16 0 Unable to update %s
To move messages between servers, do one of the following:
■ Save the script that was originally used to load the messages
■ Use the Transfer Error Messages Task in Integration Services
■ Use the following query to generate a script that adds the messages:
SELECT ‘EXEC sp_addmessage, ’ + CAST(message_id AS VARCHAR(7)) + ‘, ’ + CAST(severity AS VARCHAR(2)) + ‘, ’‘’ + [text] + ‘’‘;’
FROM sys.messages WHERE message_id > 50000;
Result:
-EXEC sp_addmessage, 50001, 16, ‘Unable to update %s’;
To drop a message, use thesp_dropmessagesystem stored procedure with the error number:
EXEC sp_dropmessage 50001;
Trang 8Logging the error
Another advantage of using the improved form of theRAISERRORcommand is that it can log the error
to the Windows Application event log and the SQL Server event log
The downside to logging to the Application event log is that it’s stored on individual workstations
While the Application event log is a great place to log front-end ‘‘unable to connect’’ errors, it’s an
inconvenient place to store database errors
To specify that an event should be logged from theRAISERRORcommand, add theWITH LOGoption:
RAISERROR (’Unable to update %s.’, 14, 1, ‘Customer’)
WITH LOG
Result:
Server: Msg 50000, Level 14, State 1, Line 1
Unable to update Customer
To view errors in the Application event log (see Figure 23-1), select Control Panel➪ Administrative
Tools➪ Event Viewer An Event Viewer is also located in Control Panel ➪ Administrative Tools
FIGURE 23-1
A SQL Server raiserror error in the Windows Application event log Notice that the server and database
name are embedded in the error data
Trang 9SQL Server log
SQL Server also maintains a series of log files Each time SQL Server starts, it creates a new log file
Six archived copies of the last log files are retained, for a total of seven log files Management Studio’s
Object Explorer in the Management➪ SQL Server Logs node lists the logs Double-clicking a log opens
SQL Server’s very cool Log File Viewer, shown in Figure 23-2 It’s worth exploring, as it has a filter and
search capabilities
FIGURE 23-2
Viewing an error in the SQL Server log using Management Studio
Try Catch
TRY CATCHis a standard method of trapping and handling errors that NET programmers have
enjoyed for years The basic idea is that if SQL Server encounters any errors when it tries to execute a
block of code, it will stop execution of theTRYblock and immediately jump to theCATCHblock to
handle the error:
BEGIN TRY
Trang 10<SQL code>;
END TRY
BEGIN CATCH
<error handling code>;
END CATCH;
If theTRYblock of code executes without any error, then theCATCHcode is never executed, and
execution resumes after theCATCHblock:
BEGIN TRY
SELECT ‘Try One’;
RAISERROR(’Simulated Error’, 16, 1);
Select ‘Try Two’;
END TRY
BEGIN CATCH
SELECT ‘Catch Block’;
END CATCH;
SELECT ‘Post Try’;
Result:
-Try One
-Catch Block
-Post Try
(1 row(s) affected)
Walking through this example, SQL Server executes theTRYblock until theRAISERROR’s simulated
error, which sends the execution down to theCATCHblock The entireCATCHblock is executed
Following execution of the CATCHblock, execution continues with the next statement,SELECT
‘Post Try’
The T-SQL compiler treats the END TRY BEGIN CATCH combination as a single
contigu-ous command Any other statements, a batch terminator ( go ), or a statement terminator ( ; )
between these two commands will cause an untrapped error END TRY must be followed immediately by
a BEGIN CATCH
Catch block
When an error does occur, the best way to trap and handle it is in theCATCHblocks Within the
CATCHblock, you want to do the following:
1 If the batch is using logical transactions (BEGIN TRAN/COMMIT TRAN), then, depending on
the error and situation, the error handler might need to roll back the transaction If this is