##ERROR and/or TRY - CATCH - sql-server

Will Try-Catch capture all errors that ##ERROR can? In the following code fragment, is it worthwhile to check for ##ERROR? Will RETURN 1111 ever occur?
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
--do sql command here <<<<<<<<<<<
SELECT #Error=##ERROR
IF #Error!=0
BEGIN
IF XACT_STATE()!=0
BEGIN
ROLLBACK TRANSACTION
END
RETURN 1111
END
END TRY
BEGIN CATCH
IF XACT_STATE()!=0
BEGIN
ROLLBACK TRANSACTION
END
RETURN 2222
END CATCH
IF XACT_STATE()=1
BEGIN
COMMIT
END
RETURN 0

The following article is a must read by Erland Sommarskog, SQL Server MVP: Implementing Error Handling with Stored Procedures
Also note that Your TRY block may fail, and your CATCH block may be bypassed
One more thing: Stored procedures using old-style error handling and savepoints may not work as intended when they are used together with TRY … CATCH blocks.Avoid mixing old and new styles of error handling.

TRY/CATCH traps more. It's hugely and amazingly better.
DECLARE #foo int
SET #foo = 'bob' --batch aborting pre-SQL 2005
SELECT ##ERROR
GO
SELECT ##ERROR --detects 245. But not much use, really if the batch was a stored proc
GO
DECLARE #foo int
BEGIN TRY
SET #foo = 'bob'
SELECT ##ERROR
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH
GO
Using TRY/CATCH in triggers also works. Trigger rollbacks used to be batch aborting too: no longer if TRY/CATCH is used in the trigger too.
Your example would be better if the BEGIN/ROLLBACK/COMMIT is inside, not outside, the construct

Try Catch will not trap everything
here is some code to demonstrate that
BEGIN TRY
BEGIN TRANSACTION TranA
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT 'a'
END CATCH;
COMMIT TRAN TranA
Server: Msg 3930, Level 16, State 1, Line 9
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Server: Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

I don't believe control will ever reach the RETURN statement-- once you're in a TRY block, any error raised will transfer control to the CATCH block. However, there are some very serious errors that can cause the batch or even the connection itself to abort (Erland Sommarskog has written on the topic of errors in SQL Server here and here-- unfortunately, he hasn't updated them to include TRY...CATCH). I'm not sure if you can CATCH those kind of error, but then, ##ERROR is no good either.

It has been my experience that, as per Books Online, TRY...CATCH blocks will trap all events that would generate errors (and, thus, set ##ERROR to a non-zero value). I can think of no circumstances where this would not apply. So no, the return value would never be set to 1111, and it would not be worthwhile to include that ##Error check.
However, error handling can be very critical, and I'd hedge my bets for fringe situations such as DTC, linked servers, notification or brokerage services, and other SQL feature that I've had very little experience with. If you can, test your more bizarre situations to see what will actually happen.

The whole point of "Try..Catch" is so that you don't have to check for ##ERROR for every statement.
So it's not worthwhile.

Related

How can ##trancount be zero within catch of a transaction?

I have this statement block with transaction and try/catch:
begin try
begin transaction;
insert ....
update ....
delete ....
commit transaction;
end try
begin catch
if (##trancount > 0)
rollback transaction;
throw;
end catch;
I am using ##trancount here without fully understanding what happens.
Why exactly could it ever happen that ##trancount is zero in this case?
The most common scenario to receive ##trancount = 0 requires a bit more elaborated logic than your sample uses.
If you have other stored procedures called in the try block, they might have their own understanding of how transactions should be managed, and either due to a poorly written code, or some other mishap, they can either commit the outer transaction by accident, or roll it back (remember, there is no really such thing in SQL Server as a nested transaction, so any rollback statement that doesn't reference a previously declared savepoint wipes out everything). The error in this case might vary, either the one that caused the inner procedure to misbehave in the first place, or if nothing else, you'll get error 266, "Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = %ld, current count = %ld."
Note that this behaviour can also be caused by rollback done by a trigger, as well.
It is possible that there might be some other situations when you end up with no current transaction in the catch block, but apparently they are so rare that I can't think of anything else off the top of my head.
Personally, I use the following template for all my stored procedures whenever possible:
create procedure dbo.ProcTemplate
(
#Error int = null output,
#Message nvarchar(2048) = null output
) as
/*
20191223, RW - to be completed
*/
set nocount, quoted_identifier, ansi_nulls, ansi_warnings, ansi_padding, concat_null_yields_null, arithabort on;
set xact_abort, implicit_transactions, numeric_roundabort off;
declare #XTran bit = cast(sign(##trancount) as bit);
begin try
if #XTran = 0
begin tran;
-- Put your code here
if #XTran = 0
commit;
end try
begin catch
if nullif(#Error, 0) is null
select #Error = error_number(), #Message = error_message();
if ##trancount > 0 and #XTran = 0
rollback;
end catch;
return;
go
One can argue that explicitly issuing set xact_abort off might result in some unpleasant side effects, such as a batch-terminating error (208, for instance) skips the catch and leaves the current transaction open. That's up to you; the trade-offs here are:
Better diagnostic. When all stored procs in a database follow this template, they bubble up the error to the outermost procedure by the means of output parameters and gracefully rollback everything.
Possibility to continue execution after the error. For example, log the error after the transaction has been rolled back and make sure the log record won't disappear with the rest of transaction.

TSQL: TRY CATCH error handling with logging errors into a table

Using SQL Server 2014:
I am going through the following article that includes useful patterns for TSQL error handling:
https://msdn.microsoft.com/en-IN/library/ms175976.aspx
I like to log errors so later on I can query, monitor, track and inspect the errors took place in my application's store procedures.
I was thinking to create a table and insert the error details as a row into the table in the CATCH block; however I am concern this might not be a good pattern OR there might be a built-in SQL server feature that can log the errors generated by the ;THROW statement.
What would be the best way to log the errors?
Update 1
I should mention that I always set XACT_ABORT on top of my SPs:
SET XACT_ABORT, NOCOUNT ON
Is it safe to assume that there is no way to log errors when XACT_ABORT is ON?
Update 2
The SET XACT_ABORT ON is according to this post:
http://www.sommarskog.se/error_handling/Part1.html#jumpXACT_ABORT
Can xp_logevent be a better alternative than adding an error record to a log table?
You have to be very careful with logging from CATCH locks. First and foremost, you must check the XACT_STATE() and honor it. If xact_state is -1 (
'uncommittable transaction') you cannot do any transactional operation, so the INSERT fail. You must first rollback, then insert. But you cannot simply rollback, because you may be in xact_state 0 (no transaction) in which case rollback would fail. And if xact_state is 1, you are still in the original transaction, and your INSERT may still be rolled back later and you'll loose all track of this error ever occurring.
Another approach to consider is to generate a user defined profiler event using sp_trace_generateevent and have a system trace monitoring your user event ID. This works in any xact_state state and has the advantage of keeping the record even if the encompassing transaction will roll back later.
I should mention that I always set XACT_ABORT
Stop doing this. Read Exception handling and nested transactions for a good SP pattern vis-a-vis error handling and transactions.
Yes it is better.
If you want to store then try this.
declare #Error_msg_desc varchar(500)
,#Error_err_code int
,#Error_sev_num int
,#Error_proc_nm varchar(100)
,#Error_line_num int
begin try
select 1/0
end try
begin catch
select #Error_err_code = ERROR_NUMBER()
,#Error_msg_desc = ERROR_MESSAGE()
,#Error_sev_num = ERROR_SEVERITY()
,#Error_proc_nm = ERROR_PROCEDURE()
,#Error_line_num = ERROR_LINE()
--create SqlLog Table
--Insert into Log Table
Insert into Sqllog values(#Error_err_code,#Error_msg_desc,#Error_sev_num,#Error_proc_nm,#Error_line_num)
end catch

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.

Do I need to use a try..catch block, and explicit rollback in a SQL Server procedure?

If I am coding a SQL Server (2008r2) procedure, and I wrap it in a transaction, do I need to explicitly enclose it in a try..catch block, and then explicitly call rollback in the catch block, or will it exit and rollback the same on its own?
i.e.:
How does this:
begin transaction
begin try
delete from....
insert into...
end try
begin catch
rollback transaction
return
end catch
commit transaction
Compare with:
begin transaction
delete from....
insert into...
commit transaction
Thank you for any help.
The answer to your question depends on the SET XACT_ABORT setting:
Specifies whether SQL Server automatically rolls back the current
transaction when a Transact-SQL statement raises a run-time error.
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a
run-time error, the entire transaction is terminated and rolled back.
When SET XACT_ABORT is OFF, in some cases only the Transact-SQL
statement that raised the error is rolled back and the transaction
continues processing. Depending upon the severity of the error, the
entire transaction may be rolled back even when SET XACT_ABORT is OFF.
OFF is the default setting.
Compile errors, such as syntax errors, are not affected by SET
XACT_ABORT.
For example, try the following code. The first division by 0 raises an error but continues execution. The second division by zero raises an error and halts execution:
begin transaction
set xact_abort off
select 1 / 0 -- causes divide by zero error, but continues
select ##trancount -- returns 1
set xact_abort on
select 1 / 0 -- causes divide by zero error and terminates execution
select ##trancount -- we never get here
rollback
If XACT_ABORT is ON, then errors will abort the transaction, and you don't need a TRY / CATCH.
If XACT_ABORT is OFF, you will need to check the status of each statement to see if an error occurred:
begin transaction
delete from...
if ##error <> 0
begin
if ##trancount > 0
rollback
return
end
insert into...
if ##error <> 0
begin
if ##trancount > 0
rollback
return
end
commit
However, if you ever find a case where you need to TRY / CATCH, you may need to do something special when the error occurs. If so, don't forget to TRY / CATCH the exception handling:
begin transaction
set xact_abort on
begin try
select 1 / 0 -- causes divide by zero error and terminates execution
select ##trancount -- we never get here
commit
end try
begin catch
select xact_state() -- this will be -1 indicating you MUST rollback before doing any other operations
select ##trancount -- this will probably be one, because we haven't ended the transaction yet
if xact_state() <> 0
begin try
select 'rollback'
rollback
-- do something to handle or record the error before leaving the current scope
select 'exception processing here'
--insert into...
end try
begin catch
-- ignore rollback errors
end catch
end catch
rollbacks will occur automatically if there is an error IN MOST CASES BUT NOT ALL
if you want to guarantee a rollback for all errors precede the begin transaction with SET XACT_ABORT ON
Best practice is to explicity catch errors with a try-catch block and take action there, including perhaps a rollback and reporting/logging the error.
It depends on the severity level of the error. Sufficiently high -- 16, perhaps? -- the process may stop at the failing line, leaving the transaction open and your locks in place. If there's any chance of error within a transaction, you definitely want to wrap it in the try-catch block, as you did in your first example.

Resources