Error handling in Oracle stored procedure - sql-server

Is it possible to know the line no. at which an error occurred inside an oracle or SQL server stored procedure?

In Oracle you can use
DBMS_UTILITY.FORMAT_ERROR_STACK to get the error stack and
DBMS_UTILITY.FORMAT_CALL_STACK to get the call stack.
Both return a varchar2(2000).
A nice example of the usage is here http://psoug.org/reference/exception_handling.html in Dan Morgans library.
There is a lot of info available and line numbers are amongst them.

In SQL Server, you can catch all of the attributes of the error.
BEGIN TRY
-- Generate a divide-by-zero error.
SELECT 1/0;
END TRY
BEGIN CATCH
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;
GO
http://msdn.microsoft.com/en-us/library/ms175976.aspx
Better yet, create an error table and a stored procedure to insert these values into the table. Then execute the stored procedure in the catch block.

For SQL Server it will give you the line in the message tab when you run the proc from SQL Server Management Studio
For example, if you have this proc
CREATE PROCEDURE prTest
AS
SELECT 1
SELECT 2
SELECT bla FROM SOMETABLE
SELECT 3
GO
and you run it like this
EXEC prTest
you get this error message
Msg 208, Level 16, State 1, Procedure
prTest, Line 7 Invalid object name
'SOMETABLE'.

In your procedure you'll need to catch the exception. You can even send the exception to an error log table. How fun is that! Or, you could just DBMS_OUTPUT the message, but it might be pretty long. DBMS_OUTPUT has a limit on message sizes. Default is 20000 characters.
You can even create custom exceptions.
You'll first need a variable
EM VARCHAR(2000);
Then this at the end of your procedure.
EXCEPTION WHEN OTHERS THEN
EM := substr(SQLERRM, 1, 2000) ;
ROLLBACK;
INSERT INTO ERROR_LOG(ERROR_TIME, PROC_NAME , ERROR_MSG)
VALUES(SYSTIMESTAMP , 'PKG.get_stuff', EM);
COMMIT;
RETURN NULL;

Related

Writing SQL runtime/syntax errors to a text file

I want to generate a text file if there are syntax or runtime errors in a SQL stored procedure.
For example, I want to create a text file with this information when the procedure is called and if the following error is generated:
Msg 8114, Level 16, State 5, Procedure sp_LoadKAD_UAT, Line 94 [Batch Start Line 94]
Error converting data type varchar to float.
I am not using any other front-end tools. I need the solution in SQL server.
I am using Microsoft SQL Server 2017
I have done research on TRY and catch blocks and ##Error but I have not got the solution yet.
Using Try Catch block you can handle the error message and insert into any Temporary table.
Declare #Catcherror as Table (Errornumber int, Errormessage varchar(100))
BEGIN TRY
Declare #num1 as int=1, #num2 as int = 0, #result as int
set #result= #num1/#num2
end TRY
BEGIN CATCH
insert into #Catcherror (Errornumber, Errormessage)
Select ERROR_NUMBER() as Errornumber, ERROR_MESSAGE() as Errormessage
end CATCH
Select * from #Catcherror

Can you capture the bad t-sql in error message

If I run a sql statement such as the following:
SELECT 1/0;
Is there a way to capture the statement "SELECT 1/0;" in an error message? The following does not give me the SQL that failed:
BEGIN TRY
SELECT 1/0;
END TRY
BEGIN CATCH
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;
GO
Also, I want to see if I can avoid using try catch at every statement. I have a SP that is executing a lot of SQL statements between a TRY and a CATCH statement. I want to know which one of the SQL statements failed among the numerous SQL statements in the TRY ... CATCH block.
All I have found so far is giving the error message details but not the T-SQL that failed.
You can leverage the fact that table variables are not rolled back.
After each stament insert a success line into a table variable for logging.
If the proc succeed, nothing needs to be returned from the table variable. If it hits the catch block though, you can rollback the transaction and either retrun the select from the table variable or better, insert that information into a log table. If your proc sets a lot of variables, I would also log those values in this table so you can see what the values were at the time the proc failed. By putting it into a logging table, you have a record for all the times the proc fails, so if it fails on Friday night and fails several times but not every time over the weekend, you have your data about what worked and what the variables were at the time of failure to use to figure out what is happening. This is especially useful if you use dynamic sql because you could log the sql statement produced as well.
If your stored procedure got bunch of statements, it's always good to have a log variable set up at the beginning of the stored procedure. You can adjust the value of this variable to make sure you would want to log steps or not. In your current situation, you can set it to 1 and start logging all/necessary steps. That will help you in finding out what got executed and where is the error occurring .
Ex: DECLARE #bLog BIT = 0 -- Default when you do not want to log
SET #bLog = 1
IF (#bLog = 1)
BEGIN
----- Add log here, take back up of result executed from previous steps --etc.
END
You can use line number for same but if you want to check which statement having problem then you have to define statement number also to check the number and set the same number in a variable like this.
Declare #StmNo as int
BEGIN TRY
set #StmNo=1
SELECT GETDATE();
set #StmNo=2
SELECT 1/0;
END TRY
BEGIN CATCH
SELECT
#StmNo AS StatementNumber,
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;
GO

Why the procedure is not going into the catch block

I have a stored procedure which is selecting the entry from the Entity table which doesn't exists in our database.
In the code below if I execute the stored procedure, it's not going into catch block while for every error in try block it should go to the catch block automatically.
I am not able to understand the reason
Create PROCEDURE AddUpdateEntity
(#Name VARCHAR(20),
#Age SMALLINT)
AS
BEGIN TRY
SELECT NAME, Age FROM Entity WHERE NAME = #name AND Age = #Age
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() StatusCode, ERROR_MESSAGE() [Message]
END CATCH
GO
/* Command to execute the SP */
EXEC AddUpdateEntity 'Sandeep',20
This execute statement is showing the error "Invalid Entity Object" but not calling the catch block.
The stored procedure has crashed and is showing the message
Msg 208, Level 16, State 1, Procedure AddUpdateEntity, Line 10
Nom d'objet 'Entity' non valide.
As per the MSDN (follow link http://msdn.microsoft.com/en-us/library/ms175976.aspx)
Errors Unaffected by a TRY…CATCH Construct
TRY…CATCH constructs do not trap the following conditions:
Warnings or informational messages that have a severity of 10 or lower.
Errors that have a severity of 20 or higher that stop the SQL Server Database Engine task processing for the session. If an error occurs that has severity of 20 or higher and the database connection is not disrupted, TRY…CATCH will handle the error.
Attentions, such as client-interrupt requests or broken client connections.
When the session is ended by a system administrator by using the KILL statement.
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.
These errors are returned to the level that ran the batch, stored procedure, or trigger.
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.
Hope the above description will solve your problem.
It will never enter the CATCH block simply because no rows are returned from a query. A sql query which returns no rows isn't an error; it's expected behavior.
As others have pointed out, the error you're seeing appears to be a compile-time error, because the Entity table does not exist.
You could try using dynamic sql with sp_executesql to force the SP to check for the tables existence only at run-time.
BEGIN TRY
DECLARE #sqlStr NVARCHAR(4000),
#sqlParams NVARCHAR(400);
SET #sqlStr = N'SELECT Name, Age FROM Entity WHERE Name=#Name AND Age=#Age';
SET #sqlParams = '#Name VARCHAR(20), #Age SMALLINT';
sp_executesql #sqlStr, #sqlParams, #Name = #Name, #Age = #Age
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() StatusCode, ERROR_MESSAGE() [Message]
END CATCH

How to log warnings (low-severity errors) in SQL 2008 R2

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;

What is wrong with my Try Catch in T-SQL?

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

Resources