Use THROW througout usp for data validation, with rollback? - sql

I have a query that checks data up front coming from the application to make sure the application is not passing bad data. If there is a piece of bad data a THROW is used to raise an error. Is this keeping my transactions open because I am not hitting the catch block then? If so, how would I handle this?
For Example:
BEGIN TRY
BEGIN TRANSACTION
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
THROW('50001','Test Error',1)
END
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
IF XACT_STATE() = 1 COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() = -1 ROLLBACK TRANSACTION
THROW;
END CATCH
The query is a lot more complex than this, and in the real query the IF EXISTS does need to be in the transaction, so putting it outside of the transaction is not an option.

I would handle this a little differently ,
Do not open a transaction until you have done your validations, If validation fails raise an error in the try block and control will jump to catch block ignoring/skipping rest of the code in try block.
I have added the check IF(##TRANCOUNT <> 0) ROLLBACK TRAN because error maybe raised during validation and in that case a transaction will never be opened.
Only if something goes wrong while the update statement is being executed the control will jump to catch block without committing the transaction and there it will be rolled back and rest of the error logging stuff will be executed.
BEGIN TRY
-- do validatiion before openning transaction
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
RAISERROR('Test Error',16, 1)
END
-- if test passed now open transaction
BEGIN TRANSACTION;
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
-- commit transaction if nothing gone wrong
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Rollback transaction if something went wrong
-- after you opened the trasaction
IF (##TRANCOUNT <> 0)
BEGIN
ROLLBACK TRANSACTION;
END
-- Other error logging
SELECT ERROR_LINE() AS Errorline
,ERROR_MESSAGE() AS ErrorMessage
,ERROR_NUMBER() AS ErrorNumber .......
END CATCH

change the statement in the catch block to this:
if xact_state() = 1 rollback tran
-1 only occurs in very specific circumstances, which I dont think you're getting in your query. If you change it to rollback on any xact_state which is not 0, you should stop getting hung transactions.
Also, I don't think you are supposed to use THROW in the TRY block; just the catch block. You'd want to use raiserror() to trigger an error in the try block, and then if you choose, use throw in the catch block.

Related

Write stored procedure so if one statement fails it should not effect the other?

I have this procedure which basically insert data.
Begin Transaction
Insert into [dbo].Values
(
EQ
)
values
(
#EQ
)
End
--Set #STATUSRet= 'Created'
--Set #ErrorRet= ''
Commit Transaction
End Try
Begin Catch
Set #STATUSRet= 'Failed'
Set #ErrorRet= (Select ERROR_MESSAGE())
Rollback Transaction
End Catch
Now I want to add a piece of code that calls another database server and insert data into the table in that server i.e. remotely. That's ok I will do that but if that fails then that should not effect my current process of inserting the data as I have described above i.e. if the remote data insertion fails it should not effect the prior insert in any way and should return successfully to the calling application behaving like nothing happened.
The default method of controlling transactions is auto-commit:
Any single statement that changes data and executes by itself is
automatically an atomic transaction. Whether the change affects one
row or thousands of rows, it must complete successfully for each row
to be committed. You cannot manually rollback an auto-commit
transaction.
So, if the two inserts are not wrapped in explicit transaction this will be the behavior. If you have more code blocks, then you can use two separate explicit transactions blocks like this:
DECLARE #ExecuteSecondTransaction BIT = 0;
-- local database
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
SET #ExecuteSecondTransaction = 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
-- remote database
IF #ExecuteSecondTransaction = 1
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
END;

SQL Server - semicolon before THROW

As we know ending each statement with semicolon is a good practice. Let's assume we have some old code which uses RAISERROR to rethrow exceptions and we want to exchange it with THROW.
From: THROW:
The statement before the THROW statement must be followed by the
semicolon (;) statement terminator.
Using only ROLLBACK:
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK
THROW;
END CATCH
and we get Incorrect syntax near 'THROW'. which is perfectly valid.
But using ROLLBACK TRANSACTION works without semicolon:
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION
THROW;
END CATCH
LiveDemo
Finally using ROLLBACK TRANSACTION #variable:
DECLARE #TransactionName NVARCHAR(32) = 'MyTransactionName';
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION #TransactionName
THROW;
END CATCH
and once again we get Incorrect syntax near 'THROW'..
Is there any particular reason why the second example works(backward compatiblity/...)?
EDIT:
There is great article written by Erland Sommarskog: Using ;THROW
If you alter the code so it actually throws an error.
BEGIN TRY
-- some code
SELECT 1/0;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION
THROW;
END CATCH
You see
Msg 3903, Level 16, State 1, Line 7 The ROLLBACK TRANSACTION request
has no corresponding BEGIN TRANSACTION.
It is trying to roll back to a save point called THROW.
This is valid syntax but fails at runtime in the above example as no transaction exists. If you are in an open transaction but have no such save point (as below)
BEGIN TRY
BEGIN TRAN
SELECT 1/0;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
THROW;
END CATCH
ROLLBACK
you see the following instead.
Cannot roll back THROW. No transaction or savepoint of that name was
found.
This kind of ambiguity is presumably why the requirement for a preceding semi colon before throw exists.

Sql Server nested transaction rollback ##TRANCOUNT

I've got a stored procedure which begins a new transaction for data manipulation. The procedure itself is executed within another transaction.
I have no influence what happens before my procedure. And it could change.
My idea is to check the ##TRANCOUNT before I begin the nested transaction. Then check the ##TRANCOUNT again in catch block and compare it. In no case I want the outer transaction to be rollbacked. So i wonder if i am safe with this code?
thx for your help!
SET #TRANSCOUNTBEFORE = ##TRANCOUNT;
BEGIN TRANSACTION tx;
BEGIN TRY
/* some data manipulation here */
COMMIT TRANSACTION tx;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > #TRANSCOUNTBEFORE ROLLBACK TRANSACTION tx;
/* some error handling here */
END CATCH;

How to check when a transaction occurs

Does anyone knows the command to check when a transaction occurs?
BEGIN TRAN
BEGIN
--- Do stuff with #id
INSERT INTO tbl_1(id, col_1, col_2)
SELECT #id, #val_1, val_2,
..
.....
.........
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRAN
END ELSE
BEGIN
COMMIT TRAN --> would like to know when this started or logged?
-- Thinking about adding "exe some_trans_log getDate(), 'start - web_sp #id'" here
EXEC web_sp #id --> this takes a while to execute
-- Thinking about adding exe some_trans_log getDate(), 'end - web_sp #id'
END
I don't think it's necessary to add logging inside of your transactions, but I could be wrong.
Your this approach is wrong, first of all ##ERROR function is populated as soon as an error occurs
and if there is any other statement being executed after the error occured ##ERROR is set to null.
To use ##ERROR function properly you have to store its value to a variable as soon as you hve executed the statement. But to anticipate where an error can occur and storing its value to a variable is kind of an over kill. and error might occur somewhere you havent anticpated.
We have TRY..CATCH blocks in sql server which makes this kind of execution very simple.
You execute your main code in try block and during code execution if an error is raised the control jumps to catch block, there you have Sql Server Error Functions to collect detailed information about the error.
I would use the following approach to write a code something like this....
BEGIN TRY
/* Do some obvious validation checks here */
-- Check 1
IF(Something is not true)
BEGIN
RAISERROR('Something has gone wrong', 16,1)
END
-- Check 2
IF(Something is not true)
BEGIN
RAISERROR('Something has gone wrong', 16,1)
END
/* once you have done your checks then open a transations*/
BEGIN TRANSACTION
INSERT INTO tbl_1(id, col_1, col_2)
SELECT #id, #val_1, val_2,
COMMIT TRANSACTION
END TRY
BEGIN CATCH
/* If the validation failed and an error was raised
control will jump to this catch block Transaction was
never BEGAN.
if all the validations passed and something went wrong
when transaction was open. then it will roll back the
open transaction.
*/
IF ##TRANCOUNT <> 0
BEGIN
ROLLBACK TRANSACTION
END
SELECT ERROR_LINE() AS [Error_Line],
ERROR_MESSAGE AS [ERROR_MESSAGE],
ERROR_NUMBER AS [ERROR_NUMBER]
/* Use Error Function to collect information about the error */
/* Do other stuff log information about error bla bla */
END CATCH

t-sql Return Error Codes vs RaiseError

Hi I am writing a stored procedure that will be executing a batch of jobs on an hourly schedule and I am trying to decide whether to return errors or raise them. Assuming I would be logging the errors inside each job which would lead to better performance and maintainability?
e.g.
--With Error Codes
CREATE PROCEDURE Job1
AS
BEGIN
BEGIN TRY
--Do some work
END TRY
BEGIN CATCH
--log error
RETURN 1; --Return 1 for error
END CATCH
RETURN 0;
END
CREATE PROCEDURE USP_BatchJob
AS
BEGIN
BEGIN TRANSACTION;
DECLARE #Error INT;
EXEC #Error = Job1;
IF #Error <> 0
GOTO ErrorHandling;
EXEC #Error = Job1;
IF #Error <> 0
GOTO ErrorHandling;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
RETURN;
ErrorHandling:
IF ##TRANCOUNT > 0
ROLLBACK;
END
e.g. RaiseError
CREATE PROCEDURE Job1
AS
BEGIN
BEGIN TRY
--Do some work
END TRY
BEGIN CATCH
--log error
RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE());
END CATCH
END
CREATE PROCEDURE USP_BatchJob
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
EXEC Job1;
EXEC Job1;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
END
The latter seems to produce more maintainable code
Being about coding style, this may be a religious issue. The main difference between a return code and an exception is that the exception will continue to be passed up the chain of calls.
In general, in the code that I write, I use return values to return the status of a stored procedure as an "application error". As with your example, 0 means success and anything else means failure. Whenever I call a stored procedure in real code, I have checks on the return value as well as any new errors that might arise. By the way, in SQL Server, you cannot check for failure with "#retval <> 0", because stored procedures can return NULL.
Of course, exceptions/database errors can still occur. The idea is that when an exception is recognized, it gets logged and handled. The system "error" turns into an application error.
The one issue I have encountered is the interaction with SQL Server Agent. For this, you want to raise an error to "fork" to the error handling step. This is easily done within a job step, by looking at the return value and then generating an error.