SQL Server - semicolon before THROW - sql

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.

Related

Proper syntax for try statement, if previous statement errors, do this

I wanted to use a control-of-flow statement in a stored procedure that says:
begin try
sql_statement
end try
begin try
sql statement
end try
begin catch
print error
end catch
But the documentation on try catch says:
A TRY block must be immediately followed by an associated CATCH block.
Including any other statements between the END TRY and BEGIN CATCH
statements generates a syntax error.
What would the proper syntax for this be in sql:
execute x_sql_statement
if x_sql_statement errors
execute y_sql_statement
else end
The problem in the above if you're beginning a TRY, ending it, and then starting another TRY. The error message is literally telling you the problem here:
A TRY block must be immediately followed by an associated CATCH block
Maybe do something like this...?
DECLARE #Error bit = 0;
BEGIN TRY
execute x_sql_statement;
END TRY
BEGIN CATCH
SET #Error = 1;
execute y_sql_statement;
END CATCH
IF #Error = 0 BEGIN
execute z_sql_statement;
END
But then, why not do...
BEGIN TRY
execute x_sql_statement;
execute z_sql_statement;
END TRY
BEGIN CATCH
execute y_sql_statement;
END CATCH
Unless you don't want statement y to run if z fails?
Typically something like this
begin try
execute x_sql_statement
end try
begin catch
execute y_sql_statement
end catch

Try catch in trigger not suppressing error

I have a trigger and I want to surround the dml statements in the trigger by a try catch block so that any exception that occur in the trigger does not throw any exceptions outside the trigger. But the error is not suppressed.
My trigger is :
ALTER TRIGGER [dbo].[Deal.OnInsertUpdateAddDealAuditDetails]
ON [dbo].[Deal]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
BEGIN TRY
--SOME DML STATEMENTS
select 1/0
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
The error output is:
Msg 3616, Level 16, State 1, Line 1
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.
XACT_ABORT is implicitly ON inside triggers. It rolls back the current transaction when a Transact-SQL statement raises a run-time error.
You have to handle the exception in original Insert query to aboid throwing the error.
BEGIN TRY
INSERT INTO deal(col1,col2,..)
VALUES (val1,val2,..)
END TRY
BEGIN CATCH
SELECT Error_number() AS ErrorNumber,
Error_message() AS ErrorMessage;
END CATCH
Note : The inserted records will not be present in the table. If you want the inserted records to be present in table though the trigger failed then you may have to commit the transaction first inside the trigger

Use THROW througout usp for data validation, with rollback?

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.

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.