I have a stored procedure spMyProc. The procedure is passed a table name as a parameter #TableName. It is important to verify that the table exists before proceeding to the body of the procedure.
How can I create a custom exception to raise an error if the table name is invalid?
I am aware of TRY and CATCH but I'm unsure how to put this together with custom exceptions.
Regards.
Look into RAISERROR() funiction.
TRY.. CATCH works pretty same as it would work in any other programming language
BEGIN TRY
-- do your checks
IF NOT EXISTS(SELECT 1 FROM sys.tables WHERE NAME = #TableName)
BEGIN
RAISERROR('Table does not exist', 16,1)
END
-- rest of the code if checks are passed
-- if above checks are not passed and you riase an error
-- control will skip any code in TRY Block after the error has been
-- Raised and staright jump to Catch block.
END TRY
BEGIN CATCH
-- Do your error logging
-- Other stuff
-- you have access to ERROR_ functions here to get detailed info about errors
END CATCH
I think one of examples supplied here RAISERROR should match your problem. New versions of SQL Server will use THROW instead of RAISERROR.
You can use try catch and raiseerror for handle the custom exception, for example:
begin try
--sql statements
end try
begin catch
RAISERROR
(
ERROR_MESSAGE(), -- or add your custom message
ERROR_SEVERITY(),
1,
ERROR_NUMBER(), -- parameter: original error number.
ERROR_SEVERITY(), -- parameter: original error severity.
ERROR_STATE(), -- parameter: original error state.
ISNULL(ERROR_PROCEDURE(), '-'), -- parameter: original error procedure name.
ERROR_LINE() -- parameter: original error line number.
end catch
Related
I would like to know the best position of a TRY/CATCH for a T-SQL procedure and why
In the statement:
CREATE PROCEDURE procedure_name
AS
BEGIN
-- Code
BEGIN TRY
sql_statement
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
--Code
END
or in the call:
BEGIN TRY
EXEC procedure_name
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
I would go for the first option.
BEGIN TRY
sql_statement
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
The reason is you would want to catch the errors at the source and then take some appropriate actions.
In second option you are letting the error bubble up and there you would not have access to all the Exact error information returned by the error functions inside the catch block.
For example the ERROR_LINE() function will return the line number of the calling procedure where the it is calling the procedure containing the actual sql code, you would want to know the error line number where the actual exception was thrown, this information is only available in the catch block of the procedure being called.
Moral of the story is try to catch exceptions as close to the source as possible.
I have a stored procedure that seems not to be logging its errors correctly.
The code is erroring, but the catch block doesn't seem to be coming into effect.
The try block is fairly long - but the erroring section is simple and comes rightat the end, so I've precis'd that.
BEGIN TRY
insert into tbl_X
select * from #temp_tbl_Y
RETURN 1
END TRY
BEGIN CATCH
Insert Into ExtractsErrorLog
SELECT
getdate() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage
;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR (#errormessage,
#errorseverity,
#errorstate
);
END CATCH;
The error the proc is failing on is our old friend
"Column name or number of supplied values does not match table definition."
I've fixed that error - It was a dumb lazy mistake - but I'm baffled why my error logging process didn't seem to be working - no row is being inserted into my ExtractsErrorLog table.
TSQL's TRY...CATCH does not catch that error. This error falls into the "compilation/recompilation" type errors that are not handled by the CATCH block "within the same level of execution".
From MSDN:
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such
as object name resolution errors that occur after compilation because
of deferred name resolution
...
You can use TRY…CATCH to handle errors that occur during compilation
or statement-level recompilation by executing the error-generating
code in a separate batch within the TRY block. For example, you do
this by placing the code in a stored procedure or by executing a
dynamic Transact-SQL statement using sp_executesql. This allows
TRY…CATCH to catch the error at a higher level of execution than the
error occurrence. For example, the following code shows a stored
procedure that generates an object name resolution error. The batch
that contains the TRY…CATCH construct is executing at a higher level
than the stored procedure; and the error, which occurs at a lower
level, is caught.
I ran into similar issues with a script creating a transaction inside a TRY...CATCH that would ROLLBACK the transaction if it failed. A statement inside the transaction was throwing that same error and caused the transaction to never be closed, as the CATCH was never entered.
As mentioned in the MSDN article, one alternative is to create a stored procedure out of your INSERT statement and then call that inside your try/catch. If the sproc is wrong, you'll catch the compilation error while trying to create it. If the table definition later changes to invalidate the sproc, then the TRY...CATCH will catch the exception for you.
If you want it to all live in one script, you can make it a temporary stored procedure, but you will then need to handle the compilation errors while you are creating the sprocs. It's not pretty, but it will work:
-- Creating error sproc to re-use code
CREATE PROCEDURE #HandleError AS
Insert Into ExtractsErrorLog
SELECT GETDATE() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR ( #errormessage,
#errorseverity,
#errorstate);
GO
-- Create a stored procedure of our INSERT and catch any compilation errors
CREATE PROCEDURE #TEST AS
insert into tbl_X
select * from #temp_tbl_Y
GO
IF (##ERROR <> 0) BEGIN
exec #HandleError
-- If there was an error creating the sprocs, don't continue to the next batch
RETURN
END
-- If compilation succeeded, then run the sproc
BEGIN TRY
exec #TEST
RETURN
END TRY
BEGIN CATCH
exec #HandleError
END CATCH;
I used THROW in my CATCH block before the INSERT statement for my logging - and got the same issue as you. Once I moved THROW after the logging INSERT statement it worked. Looks like THROW might terminate the session.
You don't use THROW in your code example, but thought this might help someone else.
It's your RETURN: "Exits unconditionally from a query or procedure. RETURN is immediate and complete and can be used at any point to exit from a procedure, batch, or statement block."
I would like to log warnings thrown from my Transact SQL scripts that aren't going to get caught in a TRY...CATCH block. Is there any way to do this? ERROR_NUMBER(), etc. won't work outside of a catch block and I'm unsure of how to even know to know a warning was thrown. Googling hasn't yielded much.
The documentation seems to intend that the error message be passed pack to the caller. It does also however state that if you wrap the statements in a stored procedure, and then call that one within a try-catch block, you will catch low-severity errors.
-- Verify that the stored procedure does not exist.
IF OBJECT_ID ( N'usp_ExampleProc', N'P' ) IS NOT NULL
DROP PROCEDURE usp_ExampleProc;
GO
-- Create a stored procedure that will cause an
-- object resolution error.
CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO
BEGIN TRY
EXECUTE usp_ExampleProc;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
You cannot catch these errors with a try catch even if you wrap it in a proc and try. Here is an example.
CREATE PROC P
AS
BEGIN
RAISERROR('TEST',9,-1,-1)
END;
BEGIN TRY
EXEC P
END TRY
BEGIN CATCH
PRINT 'CAUGHT'
END CATCH;
So in Sql Server it appears that I cannot have specific steps within my catch branch like I can in PL/SQL (I could add IF / ELSE checks).
So in PL/SQL I would have something like this
DECLARE
MY_EXCEPTION EXCEPTION;
BEGIN
//My Error is Raised
EXCEPTION
WHEN MY_EXCEPTION THEN
//Perform actions
END
How are others handling this? Is there a more elegant solution with the TRY / CATCH than using IF Statements to look at the errors and perform operations?
Thanks,
S
What kind of operations? Do you mean you want to declare elsewhere that for exception A it gets logged and exception B just gets ignored, and have your catch block inherit those actions? SQL Server 2012 adds THROW so that you can do other things (log, send e-mail, whatever) and then essentially re-throw the error that triggered the catch in the first place - but there is no way to define error handling as such centrally unless you pass the error number, severity etc. to a stored procedure (then the logic could be done in the procedure instead of in the CATCH block). Quick example:
CREATE PROCEDURE dbo.CustomErrorHandler
#ErrorNumber INT,
#ProcID INT
AS
BEGIN
IF #ErrorNumber = 8134
BEGIN
PRINT 'Oh, it was just divide by 0 in '
+ COALESCE(OBJECT_NAME(#ProcID), 'ad hoc');
RETURN;
END
IF #ErrorNumber = 208
BEGIN
PRINT 'Invalid object access!';
-- send e-mail about invalid object access
RETURN;
END
/* other custom handling for other exceptions */
RAISERROR('Unhandled exception I guess?', 11, 1);
END
GO
Then you could play with various exceptions (well, ones that pass the parsing phase, at least):
BEGIN TRY
SELECT 1/0; --8134
--EXEC('SELECT * FROM splunge;'); --208
END TRY
BEGIN CATCH
DECLARE
#e INT = ERROR_NUMBER(),
#p INT = ##PROCID;
EXEC dbo.CustomErrorHandler #e, #p;
END CATCH
For the bible on error handling see Erland's articles http://www.sommarskog.se/error-handling-I.html, http://www.sommarskog.se/error-handling-II.html and http://www.sommarskog.se/error_handling_2005.html
In the most recent SQL Servers you can use try catch blocks like:
BEGIN try
the code
END try
BEGIN catch
do whatever you need to do in case of exception
END catch
In older versions you are stuck with
IF ##ERROR > 0 THEN BEGIN
do you stuff
END IF
I am using SQL Server 2008 and when I run this Statement in Management studio the Select statement in the Catch Block is executed as expected
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
However when I run this statement the statement in the Catch Block is never executed and instead the error is just displayed in the results tab
BEGIN TRY
Select * from IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
They both return the same error number '208' 'Invalid Object Name: IDontExist' so why would one get handled and the other not?
I don't get the CATCH block hit at all.
That's because the code won't compile, because the object doesn't exist, no plan is generated, so nothing runs to hit the CATCH block.
You can never hit this catch block so somethign is wrong with your testing/example. You can hit an outer catch block in a different scope (eg nested stored procs)
Edit: I'm using SQL Server 2005 SP3
It depends when deferred name resolution applies, related to statement level recompilation.
In my case, the whole batch fails both times and no statement level recompilation happens so no deferred name resolution
In OP's case, the batch compiles and runs but then has a statement level recompilation/deferred name resolution error in running code
I'm off to find some references about why it's different, given BOL doesn't say much, neither does Erland Sommarskog
This has bitten me in the past as well.
Not all errors generated inside the TRY block statements are passed into the CATCH block. Any errors with a severity of 10 or less are considered to be warnings and do not cause control to flow to the CATCH block. Also, any errors that break the database connection will not cause the CATCH block to be reached. There may be other situations as well.
Directly from http://msdn.microsoft.com/en-us/library/ms175976.aspx.
USE AdventureWorks2008R2;
GO
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH construct to the next higher level.
Running the SELECT statement inside a stored procedure will cause the error to occur at a level lower than the TRY block. The error will be handled by the TRY…CATCH construct.
This behaviour happens if you previously had a table IDontExist and compiled a plan for it that is still in the cache then drop the table.
It also happens if you run the individual statement twice even without the table ever existing. The first run raises an error that is not caught. The second run (after the first plan is cached) succeeds.
/*Clear Cache*/
DBCC FREEPROCCACHE
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
/*Plan now Cached*/
SELECT query_plan
FROM sys.dm_exec_cached_plans cp
OUTER APPLY sys.dm_exec_sql_text(plan_handle) t
OUTER APPLY sys.dm_exec_query_plan(plan_handle) qp
WHERE t.text LIKE '%IDontExist%'
OPTION (RECOMPILE)
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
The INSERT statement gets auto parameterised.
If you change your Select * from IDontExist statement to Select * from IDontExist WHERE ProductID = 1 this also becomes auto parameterised and they behave the same.
I'm not absolutely certain why the auto parameterisation makes a difference here. I think that it is explained by the below extract from BOL however.
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct ... [those] that occur during statement-level recompilation ... If an error occurs during compilation or statement-level recompilation at a lower execution level (for example, when executing sp_executesql or a user-defined stored procedure) inside the TRY block, the error
occurs at a lower level than the TRY…CATCH construct and will be handled by the associated CATCH block.
I presume the auto parametrization of that statement means that it gets recompiled at a lower execution level and is catchable.
Now that we have all the explanations as to why this is happening. Let's see an actual solution to the problem.
First let's take the statements that #d-k-mulligan proposed above and turn them into stored procs.
IF OBJECT_ID('dbo.prcIDontExistINSERT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistINSERT
GO
CREATE PROCEDURE dbo.prcIDontExistINSERT
AS
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT
AS
BEGIN TRY
SELECT * FROM IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
If we run either of them we see the same error.
EXEC dbo.prcIDontExistINSERT
EXEC dbo.prcIDontExistSELECT
Msg 208, Level 16, State 1, Procedure prcIDontExistSELECT, Line 4
Invalid object name 'IDontExist'.
The solution now is to create error handling wrapper procs with the sole purpose of catching any error from the original procs above that are getting the object not found errors.
IF OBJECT_ID('dbo.prcIDontExistInsert_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistINSERT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistSELECT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
Finally, let's run either of our error handling procs and see the message we expect.
EXEC dbo.prcIDontExistInsert_ERROR_HANDLER
EXEC dbo.prcIDontExistSELECT_ERROR_HANDLER
There was an error! Invalid object name 'IDontExist'.
NOTE: Kalman Toth did all the hard research work here:
http://www.sqlusa.com/articles2008/trycatch/
Workaround with dynamic sql. Maybe it will be helpful for someone.
begin try
exec('
insert into IDontExist(ProductID)
values(1)
')
end try
begin catch
select 'There was an error! ' + error_message()
end catch