SQL automatically throw if a statement fails? - sql

Is there any way to have a stored procedure automatically throw if any statement fails due to an error?
I'm inside a stored proc with a merge statement which can fail due to a primary key violation, however execution still continues.
Do I have to resort to if ##error != 0 throw ... everywhere?
EDIT: I'm using MS SQL Server 2012
EDIT: This seems to work, but is there a less verbose solution? It seems as if the introduction of try/catch makes flow jump to the catch block when an error is encountered. From there I just rethrow the exception.
begin try
....do lots of sql code
end try
begin catch
throw;
end catch

Use SET xact_abort ON at the beginning of the statement. It will cause an automatic rollback if any particular statement fails.
See What is the benefit of using "SET XACT_ABORT ON" in a stored procedure?.
Edit: the above is for SQL-Server.

How about wrapping it in a transaction so that if anything fails it will roll back any changes and you can have it return an error message.
Something like
BEGIN Transaction
--Do some code
if ##error > 0
BEGIN
--Do your throw here and then
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
END

Related

Begin Transaction with Try Catch Block

I have scenario where I am calling SQL file from JAVA.
The SQL file has Begin Try Catch and checking Transaction State and doing rollback or commit based on any exception.
However, if I want to log that rollback and my JAVA application should send some message saying rollback happened and the task did'nt finished.
Having said, the logic for transaction rollback/commit needs to be part of SQL File only.
Is there any way, I can log the error and throw on JAVA application letting my user know something is wrong ?
Since others haven't answered yet - here's a suggestion, with the caveat I don't know Java.
The fundamental answer here is to put something in the 'catch' block to indicate failure.
For example, if Java could read RETURN values from the stored procedure, you could use those to flag success or failure. The below assumes SQL Server but could easily be tweaked to other databases.
CREATE PROCEDURE P_Test
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
(do your stuff)
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- Add any data to a log if desired
RETURN -1;
END CATCH;
RETURN 1;
END
In the above, if it's successful, it will return 1; if it fails, it will return -1.

SQL Server - Transaction is getting locked in case of error

BEGIN TRANSACTION;
BEGIN TRY
ALTER TABLE dbo.SomeLogs
ADD SomeID NVARCHAR(250) NULL
ALTER TABLE dbo.SomeLogs
ADD SomeID NVARCHAR(250) NULL
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #Msg NVARCHAR(MAX);
SELECT #Msg = ERROR_MESSAGE();
RAISERROR('Error Occured: %s', 20, 101, #Msg) WITH LOG;
END CATCH;
Running the above query gives the following error which is correct as I am trying to add same column twice.
Msg 2705, Level 16, State 4, Line 6
Column names in each table must be unique. Column name 'SomeID' in table 'dbo.SomeLogs' is specified more than once.
But the problem is that the SomeLogs table is locked. When I try to do SELECT on SomeLogs I get this error.
Failed to retrieve data for this request. (Microsoft.SqlServer.Management.Sdk.Sfc)
For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft%20SQL%20Server&LinkId=20476
ADDITIONAL INFORMATION:
Lock request time out period exceeded. (Microsoft SQL Server, Error: 1222)
For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft%20SQL%20Server&ProdVer=11.00.3000&EvtSrc=MSSQLServer&EvtID=1222&LinkId=20476
Why is the catch block not catching this error? And how to avoid table getting locked?
Try..Catch
block will not catch this error because it's compilation error, and compilation errors cannot be catched within the current scope.
The table remains locked because locks are not released until transaction is committed or rolled back. When xact_abort is set to off(default for SSMS sessions) transaction is not rolled backed when the compilation error occurs, that is by (bad!) design, and to fix this you should use
set xact_abort on;
You can catch this error in outer scope, for example, if you wrap this code in stored procedure or dynamic code, executing sp/dynamic code within try..catch
Using TRY…CATCH with XACT_STATE
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql
The following example shows how to use the TRY…CATCH construct to
handle errors that occur inside a transaction. The XACT_STATE function
determines whether the transaction should be committed or rolled back.
In this example, SET XACT_ABORT is ON. This makes the transaction
uncommittable when the constraint violation error occurs.
You can specify SET XACT_ABORT ON to automatically rollback the transaction in the event of an error or attention (i.e. client query cancel or timeout). The general TRY/CATCH pattern I suggest in stored procedures and batches in SQL 2012 and later:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
--do stuff
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
THROW;
END CATCH;
See Erland Sommarskog's error handling article for detailed explanation.

Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0

I have an Insert stored procedure which will feed data to Table1 and get the Column1 value from Table1 and call the second stored procedure which will feed the Table2.
But when I call The second stored procedure as:
Exec USPStoredProcName
I get the following error:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I have read the answers in other such questions and am unable to find where exactly the commit count is getting messed up.
If you have a TRY/CATCH block then the likely cause is that you are catching a transaction abort exception and continue. In the CATCH block you must always check the XACT_STATE() and handle appropriate aborted and uncommitable (doomed) transactions. If your caller starts a transaction and the calee hits, say, a deadlock (which aborted the transaction), how is the callee going to communicate to the caller that the transaction was aborted and it should not continue with 'business as usual'? The only feasible way is to re-raise an exception, forcing the caller to handle the situation. If you silently swallow an aborted transaction and the caller continues assuming is still in the original transaction, only mayhem can ensure (and the error you get is the way the engine tries to protect itself).
I recommend you go over Exception handling and nested transactions which shows a pattern that can be used with nested transactions and exceptions:
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
I had this problem too. For me, the reason was that I was doing
return
commit
instead of
commit
return
in one stored procedure.
This normally happens when the transaction is started and either it is not committed or it is not rollback.
In case the error comes in your stored procedure, this can lock the database tables because transaction is not completed due to some runtime errors in the absence of exception handling
You can use Exception handling like below. SET XACT_ABORT
SET XACT_ABORT ON
SET NoCount ON
Begin Try
BEGIN TRANSACTION
//Insert ,update queries
COMMIT
End Try
Begin Catch
ROLLBACK
End Catch
Source
Be aware of that if you use nested transactions, a ROLLBACK operation rolls back all the nested transactions including the outer-most one.
This might, with usage in combination with TRY/CATCH, result in the error you described. See more here.
This can also occur if your stored procedure encounters a compile failure after opening a transaction (e.g. table not found, invalid column name).
I found i had to use 2 stored procedures a "worker" one and a wrapper one with try/catch both with logic similar to that outlined by Remus Rusanu. The worker catch is used to handle the "normal" failures and the wrapper catch to handle compile failure errors.
https://msdn.microsoft.com/en-us/library/ms175976.aspx
Errors Unaffected by a TRY…CATCH Construct
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.
Hopefully this helps someone else save a few hours of debugging...
In my case, the error was being caused by a RETURN inside the BEGIN TRANSACTION. So I had something like this:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Return
End
commit
and it needs to be:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Rollback Transaction ----- THIS WAS MISSING
Return
End
commit
For me after extensive debugging the fix was a simple missing throw; statement in the catch after the rollback. Without it this ugly error message is what you end up with.
begin catch
if ##trancount > 0 rollback transaction;
throw; --allows capture of useful info when an exception happens within the transaction
end catch
I had the same error message, my mistake was that I had a semicolon at the end of COMMIT TRANSACTION line
Avoid using
RETURN
statement when you are using
BEGIN TRY
...
END TRY
BEGIN CATCH
...
END CATCH
and
BEGIN, COMMIT & ROLLBACK
statements in SQL stored procedures
I encountered this error once after omitting this statement from my transaction.
COMMIT TRANSACTION [MyTransactionName]
In my opinion the accepted answer is in most cases an overkill.
The cause of the error is often mismatch of BEGIN and COMMIT as clearly stated by the error. This means using:
Begin
Begin
-- your query here
End
commit
instead of
Begin Transaction
Begin
-- your query here
End
commit
omitting Transaction after Begin causes this error!
Make sure you don't have multiple transactions in the same procedure/query out of which one or more are left uncommited.
In my case, I accidentally had a BEGIN TRAN statement in the query
This can also depend on the way you are invoking the SP from your C# code. If the SP returns some table type value then invoke the SP with ExecuteStoreQuery, and if the SP doesn't returns any value invoke the SP with ExecuteStoreCommand
For me, the issue was that I forgot to add the output keyword following some output parameters of a SP call within the transaction.
The exact reason for this message is the rule that SQL Server implies: Transaction count should be same at the beginning and the end of execution of a procedure. In other terms, a procedure;
shouldn't commit/rollback a transaction that it didn't start. In this case, previous count displayed in the exception message would be greater zero, and current count is zero. Best way to prevent this is capturing transaction count (##TRANCOUNT) at the very beginning of the execution, and using transaction statements only if it is zero. The sample procedure below is a simplest "safe" structure against this type of mistake. If this procedure is called within an existing transaction, it won't begin a new transaction nor try to commit or rollback the "inherited" one. Instead, it just re-throws the same error to caller context. This is also a good practice to keep the real source procedure of the error.
should decide the fate (commit or rollback) of a transaction it started, before it's execution ends. In this case, current count would be greater than previous count.
I would highly recommend reading Erland Sommarskog's Error and Transaction Handling in SQL Server thoroughly
create or alter proc sp_err266
as
begin
set nocount on
set xact_abort on
declare #trancount int = ##trancount
if #trancount = 0
begin tran
begin try
raiserror('Raise an unexpected error...', 16, 1);
if XACT_STATE() = 1 and #trancount = 0
commit;
end try
begin catch
if XACT_STATE() <> 0 and #trancount = 0
rollback;
else
throw;
end catch
end
If you are having a code structure of something like:
SELECT 151
RETURN -151
Then use:
SELECT 151
ROLLBACK
RETURN -151
For me two begin transactions and multi rollback transaction causing this issue.
------------------------------------------------------------
BEGIN TRANSACTION
-- BEGING TRANSACTION
call of stored procedure -- ROLLBACK TRANASCTION
-- ROLLBACK TRANSACTION
ROLLBACK TRANSACTION
-----------------------------------------------------------
It can rollback only one time, it won't have multi rollback statements, also check the return statements which is causing the issue.
In nested procedures ROLLBACK should be used with care, detailed explanation here https://stackoverflow.com/a/74479802/6204480

Ms Sql Check linkedserver connection check from trigger

I'm trying to create a trigger which will insert some data into a table on a linkedserver in Ms Sql 2012. I want to be able to check if the server connection is active and if it isn't then I want to log the message else where but not abort the entire transaction and roll back. I want the query which started the trigger to continue.
I tried the method described here
How to test linkedserver's connectivity in TSQL
This does sort of work but the error is still thrown and my transaction is rolled back with the message.
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.
An error is thrown on anything involving a downed linkedserver which causes a rollback no matter what and a CATCH doesn't seem to stop it.
Example trigger:
CREATE TRIGGER [dbo].[TEST1_TRG]
ON [dbo].[Test1]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare #srvr nvarchar(128), #retval int;
set #srvr = 'loopback';
begin try
exec #retval = sys.sp_testlinkedserver #srvr;
end try
begin catch
set #retval = sign(##error);
end catch;
print #retval
END
Ok try this
-- All your Code
BEGIN TRY
-- place Query which fires the trigger Here
END TRY
BEGIN CATCH
-- Check for the trigger error and if it is trigger error
-- do nothing
-- else raise an error
END CATCH
-- The subsequenet Code
Now the catch block will get the error, but as there will be no code in catch block, it will go to the subsequent code.

SQL: Try/Catch doesn't catch an error when attempting to access a table that it can't find

I've created a stored procedure that runs a number of commands to modify data. I only want to commit the transaction if everything succeeds. I'm doing this by using a try-catch block in the manner below (where my CATCH block in the real thing uses RAISERROR to return error messages):
BEGIN TRY
BEGIN TRANSACTION
UPDATE Table1 SET MyVarcharColumn = 'test'
UPDATE Table2 SET MyBitColumn = 1
UPDATE Table3 SET MyIntColumn = 42
COMMIT TRANSACTION
END TRY
CATCH
ROLLBACK TRANSACTION
END CATCH
That works the way I want it to. If, for example, I set MyBitColumn to 'b' instead of 1, the error is caught, control flows to the CATCH, and the transaction is not commited.
One issue I've noticed is that if, say, Table3 does not exist in the database then it errors out (invalid object name), but the CATCH block is never executed and the transaction remains open.
I want to handle this to take care of any (remote) possibility that a database gets modified (or something happens where this stored procedure is added properly, but one of the tables isn't).
How should I handle these error cases?
-Thanks for any help.
At the start of your script use SET XACT_ABORT
SET XACT_ABORT ON
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a
run-time error, the entire transaction is terminated and rolled back.
I don't think that's going to be possible:
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.
Ref.
The following example shows how an object name resolution error
generated by a SELECT statement is not caught by the TRY…CATCH
construct, but is caught by the CATCH block when the same SELECT
statement is executed inside a stored procedure.
USE AdventureWorks2012;
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.
EXECUTE ('SELECT * FROM NonexistentTable');