Read our blogs, tips and tutorials
Try our exercises or test your skills
Watch our tutorial videos or shorts
Take a self-paced course
Read our recent newsletters
License our courseware
Book expert consultancy
Buy our publications
Get help in using our site
551 attributed reviews in the last 3 years
Refreshingly small course sizes
Outstandingly good courseware
Whizzy online classrooms
Wise Owl trainers only (no freelancers)
Almost no cancellations
We have genuine integrity
We invoice after training
Review 30+ years of Wise Owl
View our top 100 clients
Search our website
We also send out useful tips in a monthly email newsletter ...
Written by Andy Brown
In this tutorial
This tutorial will explain what to do when your SQL query goes pear-shaped!
Let's create an error! Consider the SQL excerpt below, which tries to squeeze a quart into a pint pot (that English expression may not travel well ...).
-- create an integer variable
DECLARE @i int
-- try to store text in it
SET @i='owl'
When you run this, you'll see this error:
You can't store text in an integer variable!
When you double-click on the error message, SQL will highlight the offending line:
Double-click on any error message to highlight the statement which caused the error.
For any error, SQL Server stores the following bits of information:
Information | Our example | Notes |
---|---|---|
Error number | 245 | The unique error message number. |
Level | 16 | The severity of the error. |
State | 1 | The state of the error (usually 1). |
Message | Conversion failed when ... | The text of the error. |
Line number | 6 | The number of the offending line. |
Procedure | Not applicable here. | The name of any stored procedure. |
All of these are described in much more detail in the next part of this blog.
Other languages allow you to trap errors - here are a couple of examples:
Language | Syntax |
---|---|
VBA | ON ERROR GOTO ... |
C# | try { ... } catch { ... } |
Here's how to do this in SQL:
BEGIN TRY
-- try doing something dodgy
END TRY
BEGIN CATCH
-- what to do if it goes wrong
END CATCH
There are two important things to notice about this syntax:
There is no FINALLY clause in SQL (although if you don't program in other languages, you won't miss it!).
You can't omit the CATCH clause, even if you don't need or want one!
Here's a simple example of the use of error-handling:
BEGIN TRY
-- create an integer variable
DECLARE @i int
-- try to store text in it
SET @i='owl'
END TRY
BEGIN CATCH
-- set variable to 1 instead
SET @i = 1
END CATCH
SELECT @i
This would show the following output when run:
Having failed to store owl in the variable, we successfully store 1 instead.
It's time now to look at the various parts of an error in more detail, then I'll give some practical examples of error-handling in SQL.
There are 6 standard error functions in SQL - each is described in more detail below.
The table below uses the following error message as an example:
This page uses this error message as its example.
Here's the 6 bits of information displayed for any error such as this:
Function | What it returns | Our example value |
---|---|---|
ERROR_NUMBER() | The internal error number. | 245 |
ERROR_SEVERITY() | How serious the error is. | 16 |
ERROR_STATE() | The system state. | 1 |
ERROR_PROCEDURE() | The name of the procedure. | spTest |
ERROR_LINE() | The offending line number. | 10 |
ERROR_MESSAGE() | A description of the error. | Conversion failed ... |
You could use these to trap the above error and show it in a glossier form. For example, suppose you ran the following query:
-- a stored procedure which will fail
ALTER PROC spTest
AS
-- create an integer variable
DECLARE @i int
BEGIN TRY
-- try to store text in it
SET @i='owl'
END TRY
BEGIN CATCH
-- display details of error
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
END CATCH
This would produce the following output:
You can see the various bits of information about the error more clearly here.
If you want to see what error messages are installed with SQL Server, run the following script:
-- display all built-in errors
SELECT * FROM sys.messages
On my version of SQL Server 2022 this gives 345,004 possible error messages (this number seems to go up with each new version of SQL Server released). I'm not sure if this means that the later version can generate more errors!
Here are the first few error messages for SQL Server 2022:
Where a % sign appears it means that a message's text can vary (as explained in the final part of this tutorial).
Error message enthusiasts might like to use the system stored procedure sp_AddMessage to add their own messages to those stored in the Sys.Messages table.
On my instance of SQL Server the highest-numbered error is 49,902. When you create your own errors, they are automatically flagged as number 50,000 by default:
Here the web has gone down, so we raise an error to reflect this. Because we haven't given an error number, 50000 is assumed.
There's more detail on raising your own error messages at the end of this blog series, including using the new THROW command.
The severity number goes from 0 to 25. Here's what the numbers mean:
Band | What this means |
---|---|
0-10 | These are regarded as warnings by SQL Server, and don't trigger error trapping (so, for example, they won't transfer control to the CATCH clause of a TRY / CATCH block). |
11-18 | Error message severity levels that you can use. |
19-25 | These are severity levels reserved for error messages generated by members of the sysadmin fixed server role! Any severity level of 20 or above is regarded as fatal by SQL Server. |
There's not a great deal to say about this. It's provided as a way of distinguishing between two messages with identical number. To all intents and purposes it always takes the value 1, and is of no interest. If you want to prove this to yourself, have a look at Microsoft's take on the subject.
This gives the name of the procedure or trigger in which the error occurred, where there is one (otherwise, it returns Null). What follows is an example to illustrate where this might be useful.
First create a stored procedure which takes an integer parameter, then tries to divide it by 0:
CREATE PROC spSub(@num int) AS
-- divide 1 by 0
SELECT @num / 0
Now write a main procedure which calls this subprocedure, passing the number 1 to it:
CREATE PROC spMain AS
BEGIN TRY
-- try to execute the subprocedure
EXEC spSub 1
END TRY
BEGIN CATCH
-- display details of error
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
END CATCH
When you run this main procedure, it will crash. Here's what the error-trapping will show:
The error occurred in the spSub procedure, but is reported from spMain.
What this shows is that if an error isn't captured in a called procedure, it will bubble up to the one which called it.
You can display line numbers for SQL in SQL Server by selecting Tools --> Options, then ticking the box shown here:
Tick the Line numbers box in the Transact-SQL General tab.
You can then use SQL line numbers to see where an error occurred:
Here the error occurred on line number 7.
As mentioned earlier, however, by far the easiest way to find an error is just to double-click on the offending line!
The text of error messages is the most useful part of an error, but there's not much more to say about it here!
Now that we've covered the parts of an error message, let's have a look at a practical example showing the use of error-handling in SQL.
This page gives a practical example of using TRY / CATCH, which uses the following two tables:
There are 3 authors ... | ... and 5 books. |
You can run the script to create this database by downloading and opening this file in SQL Server Management Studio.
Suppose that we want to create a stored procedure to allow us to add a book easily. We could use this to add books for existing authors:
-- author number 3 does exist, so this should work
spAddBook 'The L-Shaped Room', 3
Alternatively, we might try to add books whose authors don't yet exist:
-- there is no author number 4
spAddBook 'The Pelican Brief', 4
Here's what we should see for these two cases:
| |
If the author exists ... | ... and if the author doesn't. |
The start of the stored procedure should specify which parameters it takes:
-- procedure to insert a book
ALTER PROC spAddBook(
@BookName varchar(100),
@AuthorId int
)
AS
The procedure should then try inserting the book in question, and displaying the record just added if this all goes OK:
BEGIN TRY
-- try inserting book
INSERT INTO tblBook(
BookName,
AuthorId
) VALUES (
@BookName,
@AuthorId
)
-- show last inserted book if worked
SELECT TOP 1 * FROM tblBook
ORDER BY BookId DESC
END TRY
If this fails (perhaps because the author number given doesn't exist in the authors table, so that the referential integrity constraint prevents insertion of the book), control will jump to the CATCH clause:
BEGIN CATCH
-- if couldn't insert book, show why
SELECT
'Could not do' AS ErrorMessage,
ERROR_NUMBER() AS 'Error no',
'Author ' + CAST(@AuthorId AS varchar(10)) + ' not found' AS Problem
END CATCH
Here are two calls to this stored procedure (the first will insert a row, the second will display the error details):
-- add a book by Lynne Reid Banks
spAddBook 'The L-Shaped Room', 3
-- there is no author 5 yet!
spAddBook 'The Lacuna', 5
As the final part of this blog, I'll now take a look at raising your own errors!
If the built-in SQL errors aren't annoying enough, you could always add your own!
One reason to do this might be to signal to a client application that a process hadn't completed successfully.
There are two ways to generate errors in T-SQL - using RAISERROR or THROW. Here's a basic example of each:
-- raise error of severity level 15
RAISERROR('This went pear-shaped',15,1);
-- alternatively, throw the same error message
THROW 50000, 'This went pear-shaped', 1
As this shows, both commands do more or less the same thing. Here are some reasons to use one or the other:
Reason | Notes |
---|---|
Compatibility | THROW only works from SQL Server 2012 (so if you're using 2008 R2 or earlier, ignore this table!). |
Future-proofing | It seems likely from reading websites that Microsoft will eventually deprecate RAISERROR. |
Ease of syntax | You must end the line before a THROW statement with a semi-colon (as for the above example). |
Custom error numbers | RAISERROR can only raise error messages already in the sys.messages table (although you can add your own messages, as explained below). With THROW you can use any error number between 50000 and 2147483647! |
Levels of severity | THROW automatically generates errors of severity level 16 (with RAISERROR you can choose your severity level). |
Formatting messages | You can format messages more with RAISERROR (although why you'd want to do this within SQL I can't imagine). |
When you call RAISERROR, the main arguments are:
Either a message id number, or the text of the message to be displayed.
The severity level of the message.
The state (nearly always just 1).
Here's our simple example above:
-- raise error of severity level 15
RAISERROR('This went pear-shaped',15,1);
So this would raise an error of severity level 15 (still enough to end the query in question). Alternatively, we could show a system error message:
-- Show message: "the article resolver supplied
-- is either invalid or nonexistent"
RAISERROR(20020,12,1)
Finally, you can create your own parametrised error message:
-- show a message using parameters
RAISERROR('Star sign %s limited to %i queries per day',
16, 1, 'Scorpio', 10)
This would display the following message:
SQL Server fills in the values for the parameters given.
The main symbols that you can use are as follows:
Symbol | What it means |
---|---|
%d or %i | Any integer |
%s | Any string of text |
The parameters have to come in the right order, and be of the right type (so in the example above, Scorpio is assigned to the first parameter, which is a string of text, and 10 to the second, which is an integer).
There's quite a bit more you can do with formatting parameters within error messages - if you know the printf statement in C you'll be able to guess what's possible (otherwise, you're not missing anything, believe me!).
If you have SQL Server 2012, you're probably better off using THROW instead of RAISERROR. The only arguments you can pass to this are:
A message id number (or a variable containing a number).
The message text to be displayed (often built up in a variable using FORMATMESSAGE - see below for an example).
The state (nearly always just 1).
Remember that THROW always creates errors with severity level 16.
Here's an example of using THROW to display a customised error message:
-- add a message to the system messages table
EXEC sys.sp_addmessage
@msgnum=60001,
@severity = 16,
@msgtext = N'No %s or %s can run queries after %i hours.'
GO
-- create a variable to hold message text
DECLARE @WolErrorMessage nvarchar(2048) = FORMATMESSAGE(60001,
'Scorpio','Aquarius', 17);
-- previous line must end in semi-colon
THROW 60000, @WolErrorMessage, 1;
Here's what this would display when run:
The message the above SQL would display.
You can also use sys.sp_addmessage to add messages for display using RAISERROR, in a similar way.
And with that, this error-handling blog is complete! Unless you know better ... ?
You can learn more about this topic on the following Wise Owl courses:
Kingsmoor House
Railway Street
GLOSSOP
SK13 2AA
Landmark Offices
99 Bishopsgate
LONDON
EC2M 3XD
Holiday Inn
25 Aytoun Street
MANCHESTER
M1 3AE
© Wise Owl Business Solutions Ltd 2024. All Rights Reserved.