Can you capture the bad t-sql in error message - sql-server

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

Related

Does a transaction require a try catch?

I am a c# developer learning more TSQL. I wrote a script like this:
begin transaction
--Insert into several tables
end transaction
But I was told that was not a good idea and to use something like this:
BEGIN TRANSACTION;
BEGIN TRY
-- Generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
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;
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
GO
I don't see why the second example is more correct. Would the first one not work the same way? It seems the first one would either update all tables, or not at all? I don't see why checking the ##TRANCOUNT is necessary before the commit.
Only Open a Transaction once you are inside the try block and just before the actual statement, And commit it straightaway, do not wait for your control to go to the end of the batch to commit your transactions.
Once you are in Try Block and you have opened a transaction, If something goes wrong The control will jump to CATCH block, Simply rollback your transaction there and do other error handling as required.
I have added a little check before actually rolling back the transaction checking for any open transaction using ##ROWCOUNT function, It doesnt really make much sence in this scenario. It is more useful when you are doing some validations checks in your try block before you open a transaction like checking param values and other stuff and raising error in try block if any of the validation checks fail, In that case control will jump to catch block without even opening a transaction there you can check for any open transaction and rollback if there are any open ones. In your case as it is, you really dont need to check for any open transaction as you will not entre the catch block unless something goes wrong inside your transaction.
BEGIN TRY
BEGIN TRANSACTION
-- Multiple Inserts
INSERT INTO....
INSERT INTO....
INSERT INTO....
COMMIT TRANSACTION
PRINT 'Rows inserted successfully...'
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
PRINT 'Error detected, all changes reversed'
END
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
I want to give my perspective here as a C# developer:
In the simple scenario given above (just inserting into a few tables from a script), there is no reason to add a try/catch, as it adds no benefit to the transaction. Both examples will produce exactly the same result: Either all tables will be inserted, or they will not be. The state of the database remains consistent. (Since COMMIT TRANSACTION is never called, the rollback is implicitly called by Sql Server at the end of the script.)
However, there are times when you can do things in the try/catch that are not possible with the integrated error handling. For example, logging the error to an error table.
In my C# experience, the only time to use a Try/Catch is when there are things which are outside of the developer's control, such as attempting to open a file. In such a case, the only way to manage an exception generated by the .Net framework is through Try/Catch.
If I were doing a stored procedure, and wanted to manually check on the state of data and manually call ROLLBACK TRANSACTION, I could see that. But it still would not require a try/catch.
I'm in two minds about TRY...CATCH in T-SQL.
While it's a potentially-useful addition to the language, the fact that it's available is not always a reason to use it.
Taking your
DELETE FROM Table WHERE...
example (which I realise is only an example). The only way that will ever fail with an error is if the code has got seriously out of whack from the schema. (for example, if someone creates a foreign key with Table on the PK end of it).
Given proper testing, that kind of mismatch between the code and the schema should never make it into production. Assuming that it does, IMHO the "rude", unmediated error message that will result is a better indication of what's gone wrong than a "polite" wrapping of it into a SELECT statement to return to the client. (Which can amount to the try/squelch antipattern SeanLange mentions).
For more complicated scenarios, I can see a use for TRY...CATCH. Though IMHO it's no substitute for careful validation of input parameters.

how to write a t-sql program to catch transaction raised error at the very end

I am new to T-SQL programming. I need to write a main procedures to execute multiple transactions. How could i structure the program so that each transaction will not abort. Instead, the procedure will raise the error and report them back to the main program in the output parameters after all the transaction finish running. Please provide me with pseudo code if you can. Thanks.
You need to follow the template from Exception handling and nested transactions
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
go
As you can see you can't always continue, because sometime the exception has already aborted the transaction by the time you catch it (the typical example being deadlock exception 1205). And you must use a savepoint and revert to the savepoint in case of exception, to keep the database consistent. However, you do not abort the caller's work, if possible.
You could use try/catch
BOL - TRY/CATCH
Here's an example
I have previously encapsulated logic into stored procedures and put in exec statements in the TRY/CATCH block. In the CATCH you can use this link to get error information (example B in the link)
BOL - ERROR_MESSAGE
Something similar to -
BEGIN TRY
BEGIN TRAN
EXEC StoredProcedure01
EXEC StoredProcedure02
COMMIT
END TRY
BEGIN CATCH
ROLLBACK TRAN
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
I might consider trying this. Each transaction is a separate stored proc with a an overall stored proc that calls each in turn. Save the error information you want in a table variable. IN the catch block of each proc, rollback that transaction and then insert the data form the table variable with the error information into a logging table. Do not return a failure to the calling proc.
If you want to report the errors out of the main proc in real time, you can do a select from the logging table at the end.
It would work best if you create a batchid at the start of the calling proc and have that be an input variable to each of the procs you call and also include that data in the information you add to to the logging table. Then if the procs fail multiple times during nonworking hours, you have the errors for all of them and can see the batch they were associated with. This helps tremendously in tracking down problems.
You will need to give some thought as to what information you will want for each proc when designing your logging table. I would suggest that part of what you store is any input variables that are sent into the proc. Also if you are using dynamic SQl, then store the generated sql as well. If you can identify the user who ran the proc, that too is useful for tracking down permissions issues for instance.
Having a logging table is far more useful than just returning errors at run time. You can look for trends, see if the same error is frequently happening, look at the information that caused failures and the information that succeeded if you choose to also log the variables for successful runs.
None of this is out of the box easy to write code. It requires a great deal of design on your part to determine, exactly what information will be useful over time in troubleshooting issues with the process. How detailed you need to get is a business decision based on both how the data is used and what you will want to know about a failure. As such, we cannot make this determination for you.

SQL Server Stored Procedure Error Handling

I have a stored procedure which is runs automatically every morning in SQL Server 2008 R2, part of this stored procedure involves executing other stored procedures. The format can be summarised thus:
BEGIN TRY
-- Various SQL Commands
EXECUTE storedprocedure1
EXECUTE storedprocedure2
-- etc
END TRY
BEGIN CATCH
--This logs the error to a table
EXECUTE errortrappingprocedure
END CATCH
storedprocedure1 and storedprocedure2 basically truncate a table and select into it from another table. Something along the lines of:
BEGIN TRY
TRUNCATE Table1
INSERT INTO Table1 (A, B, C)
SELECT A, B, C FROM MainTable
END TRY
BEGIN CATCH
EXECUTE errortrappingprocedure
END CATCH
The error trapping procedure contains this:
INSERT INTO
[Internal].dbo.Error_Trapping
(
[Error_Number],
[Error_Severity],
[Error_State],
[Error_Procedure],
[Error_Line],
[Error_Message],
[Error_DateTime]
)
(
SELECT
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
GETDATE()
)
99% of the time this works, however occasionally we will find that storedprocedure1 hasn't completed, with Table1 only being part populated. However no errors are logged in our error table. I've tested the error trapping procedure and it does work.
When I later run storedprocedure1 manually it completes fine. No data in the source table will have changed by this point so it's obviously not a problem with the data, something else has happened in that instant which has caused the procedure to fail. Is there a better way for me to log errors here, or somewhere else within the database I can look to try and find out why it is failing?
Try to use SET ARITHABORT (see link). It must ROLLBACK in your case. Also the answer of #Kartic seem reasonable.
I recommned also to read about implicit and explicit transactions - I think that this is your problem. You have several implicit transactions and when error happeneds you are in the middle of the job - so only part is rollbackеd and you have some data in that tables.
There are some type of Errors that TRY..CATCH block will not handle them, look here for more information https://technet.microsoft.com/en-us/library/ms179296(v=sql.105).aspx . for such Errors you should handle them in your application.
also I think you might have transaction management problem in your application too.
I am not sure if I understood you completely. Below code is too big for comment. So posting as an answer for your reference. If this is not what you want, I'll delete it.
Can we add transaction handling part as well.
DECLARE #err_msg NVARCHAR(MAX)
BEGIN TRY
BEGIN TRAN
-- Your code goes here
COMMIT TRAN
END TRY
BEGIN CATCH
SET #err_msg = ERROR_MESSAGE()
SET #err_msg = REPLACE(#err_msg, '''', '''''')
ROLLBACK TRAN
-- Do something with #err_msg
END CATCH

Try and Catch on TSQL - catch not catching

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."

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;

Resources