SQL Code Guard and nested transactions rollback? - sql

I have a stored procedure that may be called from a transaction. When there is an exception I want to rollback the transaction (the top-level transaction). I would like to use something like this:
...procedure definition...
BEGIN
BEGIN TRY
--work&logic
END TRY
BEGIN CATCH
-- An error occurred!
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
END
The catch with this is, that the SQL Code Guard reports for this the warning EI020 - ROLLBACK TRANSACTION without BEGIN TRANSACTION. Is there a way to write this properly?

Related

Understanding nested transaction SQL

I have this code:
begin transaction outside
go
begin transaction inside
go
print 'inside done'
commit transaction inside
update Action set IdUser = 9999999999999
commit transaction outside
This code throws an exception on purpose at "update Action set IdUser = 999...", because of a foreign key problem.
I wish to rollback in case anything wrong occurs. That's why I have an outside transaction. In real world, I have a SQL file with a lot of scripts, and I want to make sure all the scripts maintain in a transaction, so I want to wrap it in an outside transaction.
That code above is not working. It's printing "inside done", and then throws an exception. This print could be a big insertion.
What's going on? How do I solve it?
Just try your query using the below template:
begin tran outside
begin try
begin tran inside
print 'inside done' --Insert\Update\Delete
Update Action set IdSuer = 999999999
END TRY
BEGIN catch
rollback tran inside
rollback tran outside
GOTO FINISH
END catch
commit tran inside
commit tran outside
:FINISH
print 'END'

Execute update clause even with errors

I'm doing some script testing and I want to make sure that if the Update Clause I'm testing in this Try Catch :
BEGIN TRY
BEGIN TRAN
UPDATE NAME
SET NAME.ADDBY =
(CASE WHEN NAME.ADDBY = 'CONVERSION' THEN 'CONVERTED'
WHEN NAME.ADDBY = 'CJDOG'THEN 'CJDAREME'
WHEN NAME.ADDBY = 'npalerm' THEN 'REALLYLONGDETAILEDTEXT'
ELSE NAME
END)
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT >0
PRINT ERROR_MESSAGE()
ROLLBACK TRAN
END CATCH
PRINT ##TRANCOUNT
fails, that other rows are updated and only the ones that didn't error go through.
Currenty because it is in the TRY CATCH there is a ROLLBACK that makes sure nothing goes through.
But when I try to run just the UPDATE part the errors terminates the whole script, instead of updating those that do not cause an error, in this case npalerm is too long in the case.
Any Ideas?
I know its a long desc I could've just included the Update but for detailed purpose I included it in the Catch.
In order to update the rows individually and catch the errors on each row rather than catching errors on the entire table/view update, you would need to wrap the whole thing in a cursor.
Be aware that cursors are more resource intensive and slower than set-based commands.

When does RAISERROR fire in a stored procedure?

I've got a stored procedure that contains a try-catch block. In the catch block I call raiserror() to rethrow the error with some context.
I was expecting that if an error occurred the raiserror() would be called and execution would immediately return from the stored procedure to the calling code. However, this doesn't appear to be the case. It looks like execution of the stored procedure continues until it hits a return statement, then the raiserror() takes effect.
Is this correct - that raiserror() won't have an effect until return is called or the end of the stored procedure is reached?
I'm using SQL Server 2012.
EDIT:
in reply to request for details of the stored procedure, here's the relevant snippet of code:
DECLARE #ErrMsg VARCHAR(127) = 'Error in stored procedure ' + OBJECT_NAME(##PROCID) + ': %s';
declare #UpdateDateRecordCount table (LastUpdated datetime, NumberRecords int);
begin try;
insert into #UpdateDateRecordCount (LastUpdated, NumberRecords)
exec sp_ExecuteSql
#UpdateCountQuery,
N'#LastUpdated datetime',
#LastUpdated = #LastUpdated;
if ##rowcount <= 0
begin;
return 0;
end;
end try
begin catch;
declare #InsertError varchar(128) = 'Error getting updated date record count: '
+ ERROR_MESSAGE();
RAISERROR (#ErrMsg, 16, 1, #InsertError);
end catch;
-- Attempt to loop through the records in #UpdateDateRecordCount...
The #UpdateCountQuery argument will be set to something like:
N'select LastUpdated, count(*) from dbo.Part where LastUpdated > #LastUpdated group by LastUpdated;'
As I understand it, if you want the execution to stop, you need to raise the error within the TRY block, and then raise the error again in your CATCH block this will make sure that the error is "raised" to the caller.
Or you could add a RETURN statement after your RAISERROR statement in the CATCH block. This will exit the procedure and return to the caller.
Also, as suggested by MSDN you should try to use the THROW statement instead of RAISERROR since it (the RAISERROR) will be phased out.
That's not how it works in T-SQL. Nothing in the documentation for TRY...CATCH or RAISERROR specifies any special cases that would override:
When the code in the CATCH block finishes, control passes to the statement immediately after the END CATCH statement. Errors trapped by a CATCH block are not returned to the calling application. If any part of the error information must be returned to the application, the code in the CATCH block must do so by using mechanisms such as SELECT result sets or the RAISERROR and PRINT statements.
If you want the stored proc to exit, you need a RETURN statement as well.
It depends on the severity level that you use. There's a lot more information in the below link:
http://technet.microsoft.com/en-us/library/ms178592.aspx
But to quote the article:
The errors generated by RAISERROR operate the same as errors generated
by the Database Engine code. The values specified by RAISERROR are
reported by the ERROR_LINE, ERROR_MESSAGE, ERROR_NUMBER,
ERROR_PROCEDURE, ERROR_SEVERITY, ERROR_STATE, and ##ERROR system
functions. When RAISERROR is run with a severity of 11 or higher in a
TRY block, it transfers control to the associated CATCH block. The
error is returned to the caller if RAISERROR is run...
So if your severity level is 11 or higher then the control will be immediately transferred to the CATCH block.
The below example shows a severity level of 16:
RAISERROR ('Error raised in TRY block.', -- Message text.
16, -- Severity.
1 -- State.
);

why the same try catch block have two different results

I want to rollback all statements in the CATCH block when the error is encountered in the TRY block:
BEGIN TRY
begin transaction
create table t3(a int )
insert into t3 values(1)
insert into t3 values(1,2) --error occur
insert into t3 values(3)
END TRY
BEGIN CATCH
--just take care of rollback
IF ##TRANCOUNT <> 0
BEGIN
PRINT 'in catch,ROLLING BACK';
ROLLBACK
END
END CATCH
go
At first the error is caught since the PRINT in the CATCH block works. However, after several changes back and forth, the error seems not be caught in the CATCH any more since no more printing happens.
Therefore, I open a new query and execute the same thing. This time error can be caught again!!
Sorry about the big images
When you run this code in a tool like management studio, your transaction is mantained for your SPID (assigned to a query window).
So the inconsistent reult issue is because you don't close the transaction on all of your code paths (lets say the rollback dosn't get reached), your transaction is still active the next time you run the script.
If you add IF ##TRANCOUNT <> 0 rollback transaction to the begining of your script you will have a consistent output.
Also notice that the try catch block is not meant to catch errors at statement compile level.
If you replace your error by a division by zero for example (print 1 / 0) the catch will work properly.
In 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.

RAISERROR from Catch Block in TSQL Passed to Calling Batch - Need that Passed to Calling Application

I have been researching the TRY/CATCH block and I am a little stumped on how to pass an error the way I need to do so. From what I have read and if I understand correctly, a RAISERROR in a CATCH block in SQL will be passed to the calling batch OR the calling application. The application is running a stored procedure which has a transaction in it. The transaction is wrapped in a TRY/CATCH block. In the CATCH block, I am raising the error if something in the transaction fails causing it to jump to CATCH. If running the procedure via SSMS, the error shows up fine and the transaction rolls back. However, if the application calls the stored procedure and the same error occurs, the application never knows about the error and thus doesn't now the procedure failed.
First of all, am I understanding correctly how the RAISERROR in CATCH works? If so, how can I get that error raised back to the calling application?
BEGIN TRY
BEGIN TRAN
...............
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
DECLARE #ErrMsg NVARCHAR(4000)
SELECT #ErrMsg = ERROR_MESSAGE()
RAISERROR(#ErrMsg, 16, 1)
END CATCH
I am Running Windows 7, SQL Server 2005
The easiest way to do what your trying to do would be to declare #ErrMsg as an output parameter of your procedure and handle that in your calling application.
You can read up on how RAISEERROR handles it's output here if you wish to continue using it to handle your error outputs. http://msdn.microsoft.com/en-us/library/ms178592.aspx