If I exec this batch:
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
drop table dbo.tblPrueba
select * from dbo.tblPrueba
PRINT 'finish'
rollback transaction
The ouput is this:
start
Msg 8134, Level 16, State 1, Line 3
Divide by zero error encountered.
continue
Msg 208, Level 16, State 1, Line 6
Invalid object name 'dbo.tblPrueba'.
I am forcing two errors:
- the first one: PRINT 1/0 (that generates this error:
Msg 8134, Level 16, State 1, Line 3
Divide by zero error encountered.
) And continue executing the batch
- the second one:
drop table dbo.tblPrueba
select * from dbo.tblPrueba
That generates this error:
Msg 208, Level 16, State 1, Line 6
Invalid object name 'dbo.tblPrueba'.
And stops execution of the batch
What is the different between them? Where can I learn those that stop execution and those that doesn´t?
Thanks a lot!!
Since the first error is a divide by zero error, this behavior depends on your ARITHABORT, ARITHIGNORE and ANSI_WARNINGS settings.
From the article:
These three SET commands give you very fine-grained control for a very
small set of errors. When a division by zero or an overflow occurs,
there are no less four choices.
No action at all, result is NULL – when ARITHIGNORE is ON.
Warning message, result is NULL – when all are OFF.
Statement-termination – when ANSI_WARNINGS is ON.
Batch-abortion – when ARITHABORT is ON and ANSI_WARNINGS is OFF.
As far as which errors stop execution and which ones don't, please refer to the same article.
The easiest way to ensure all errors are handled correctly is to use TRY/CATCH
Without this, different errors can be statement, scope or batch aborting depending on settings such as ARITHxx, ANSI_WARNINGS and XACT_ABORT. This is demonstrated and discussed at "Error Handling in SQL 2000"
You can see the different (no SET options changed) with this
CREATE TABLE dbo.tblPrueba (gbn int);
GO
BEGIN TRY
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
drop table dbo.tblPrueba
select * from dbo.tblPrueba
PRINT 'finish'
rollback transaction
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE();
IF XACT_STATE() <> 0 rollback transaction
END CATCH
If I run this twice, I get this because the DROP is never executed
Msg 2714, Level 16, State 6, Line 1
There is already an object named 'tblPrueba' in the database.
Where can I learn those that stop execution
You can use exception handling
Begin try
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
create table #t
(
id int
)
drop table #t
select * from #t
PRINT 'finish'
rollback transaction
End Try
Begin Catch
if( XACT_STATE() == 1)
Rollback Tran
End Catch
You can use Set XACT_ABORT ON like below.
Set XACT_ABORT ON
begin transaction
PRINT 'start'
PRINT 1/0
PRINT 'continue'
create table #t
(
id int
)
drop table #t
select * from #t
PRINT 'finish'
rollback transaction
Related
I have two procedures, one outer procedure and one inner procedure, where I would like to understand the behaviour of the error handling. The inner procedure provokes an error and is trying to insert something in the catch block into a table. After that the error is raised, passed to the outer procedure and then should roll back the transaction.
I'm trying to understand why my code is throwing the error message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
I would expect the following message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
Error converting data type varchar to numeric.
I know that the issue comes from the catch block in the inner procedure and it happens because I'm trying to insert something into my log table before raising the error. When I switch those statements or delete the insert, I get the actual error message. I also know that it is not smart to do the logging in the inner procedure and inside a transaction that is rolled back anyways.
I would like to understand what is making this transaction a "doomed" transaction even though the XACT_ABORT is set to off.
Full code:
My main procedure:
CREATE PROCEDURE [dbo].[OuterProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRANSACTION ;
-- do other stuff
EXEC [dbo].[innerprocedure];
-- do other stuff
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #ErrText NVARCHAR(2000);
SET #ErrText = ISNULL(ERROR_MESSAGE(), 'nothing')
RAISERROR(#ErrText, 11, 1) WITH NOWAIT
END CATCH;
END;
My inner procedure:
CREATE PROCEDURE [dbo].[InnerProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
SET NOCOUNT ON;
BEGIN TRY
-- do other stuff
-- provoke error
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
-- do other stuff
END TRY
BEGIN CATCH
DECLARE #ErrText NVARCHAR(2000);
SELECT
#ErrText = ISNULL(ERROR_MESSAGE(), 'nothing');
INSERT INTO [dbo].[logtable]
(
[Message]
, [ErrNr]
)
VALUES
( #ErrText
, -1
);
RAISERROR(#LogText, 11, 0) WITH NOWAIT;
END CATCH;
END;
I would like to understand what is making this transaction a "doomed"
transaction even though the XACT_ABORT is set to off.
XACT_STATE() is -1 in the catch block so the transaction is doomed.
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
Throws error
Error converting data type varchar to numeric.
"Most conversion errors" is one of the error types that Erland Sommarskog puts in the category of errors.
Batch Abortion with Rollback This is the strongest reaction SQL Server
can take to a user error. These are errors that abort execution on the
spot if there is no CATCH handler on the stack and they also roll back
any open transaction. If there is a CATCH handler, the error is
caught, but any open transaction is doomed and must be rolled back.
The behaviour is the same, no matter whether XACT_ABORT is ON or OFF.
The categorisation of error behaviours is somewhat cryptic, undocumented and not intuitive. Read his article for more details.
Perhaps I am missing something, but even though the RAISERRORs below have severity of 16 (as per documentation) the transaction is still committed as if XACT_ABORT ON has no effect.
CREATE PROCEDURE [ExploringGroups].[RemoveMember]
#groupId uniqueidentifier,
#adminUsername nvarchar(50),
#targetUsername nvarchar(50)
AS
SET XACT_ABORT ON
BEGIN TRANSACTION
DECLARE
#adminUserId uniqueidentifier = dbo.fn_userId(#adminUsername),
#targetUserId uniqueidentifier = dbo.fn_userId(#targetUsername)
IF #targetUserId IS NULL OR ExploringGroups.IsMember(#groupId, #targetUserId) = 0
RAISERROR('Target user was not located', 16, 1)
IF ExploringGroups.IsInRole(#groupId, #adminUserId, 'adm') = 0
RAISERROR('Specified user is not an administrator of this group', 16, 2)
IF #adminUserId = #targetUserId
RAISERROR('You cannot remove yourself', 16, 3)
-- statements below still execute and commit even though there was an error raised above
DELETE FROM ExploringGroups.MemberRole WHERE GroupId = #groupId AND UserId = #targetUserId
DELETE FROM ExploringGroups.Membership WHERE GroupId = #groupId AND UserId = #targetUserId
COMMIT
RETURN 0
Calling
exec exploringgroups.removemember '356048C5-BAB3-45C9-BE3C-A7227225DFDD', 'Crypton', 'Crypton'
Produces
Msg 50000, Level 16, State 2, Procedure RemoveMember, Line 20
Specified user is not an administrator of this group
Msg 50000, Level 16, State 3, Procedure RemoveMember, Line 24
You cannot remove yourself
I thought XACT_ABORT was supposed to roll back the whole transaction if it's set to ON?
Actually, it behaves exactly as it was supposed to. XACT_ABORT really caused the transaction to roll back, so were there any data modifications up to the point of the error they will be rolled back. However, it didn't affect the flow of execution, and it didn't stop running the stored procedure, so the following two DELETEs were executed as implicite transactions. Explicit RAISERRORs don't abort the batch.
See this simplified version:
create table #t(i int);
insert #t values(1);
go
alter procedure sp
as
set xact_abort on
begin tran
raiserror ('x', 16, 1);
print 'deleting';
delete #t;
commit;
go
exec sp
go
select * from #t
go
The only funny thing was that the error about COMMIT not having a corresponding BEGIN TRAN was swallowed.
With SEH, it would jump into CATCH block.
You should be using a "THROW" statement versus the RAISERROR approach.
Use a BEGIN TRY and BEGIN CATCH and commit the transaction normally or rollback in the CATCH block.
BEGIN TRY
-- Do your inserts or throw error
-- Commit transaction
END TRY
BEGIN CATCH
-- Rollback
END CATCH;
Based on the Books Online documentation of SET XACT_ABORT ON, i get the impression that if a T-SQL statement raises a run-time error, the entire transaction is terminated and rolled back:
Remarks
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.
Testing this in SQL Server 2008 R2:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
PRINT 'TranCount befor an error = '+CAST(##Trancount AS varchar(50))
DROP TABLE QuertyAsdf
PRINT 'TranCount after an error = '+CAST(##Trancount AS varchar(50))
Gives the output:
TranCount befor an error = 1
Msg 3701, Level 11, State 5, Line 6
Cannot drop the table 'QwertyAsdf', because it does not exist or you do not have permission.
TranCount after an error = 1
i was also under the impression that SET XACT_ABORT ON terminates the batch if there's an error:
SET XACT_ABORT ON instructs SQL Server to rollback the entire transaction and abort the batch when a run-time error occurs.
That sounds handy. How can i make it do that too?
The SQL Server only rollback transactions when Severity level greater or equals 16.
See example:
Msg 544, Level 16, State 1, Line 1
Cannot insert explicit value for identity column in table 'ORC_ORCAMENTO' whenIDENTITY_INSERT is set to OFF.
Test on SQL Server 2008 R2
SET XACT_ABORT ON;
BEGIN TRANSACTION;
PRINT 'TranCount befor an error = '+CAST(##Trancount AS varchar(50))
insert into ORC_ORCAMENTO (ORCID, ORCNOME, ORCATIVO) VALUES (1, 'TESTE_ALEXP', 0);
PRINT 'TranCount after an error = '+CAST(##Trancount AS varchar(50))
Returns
TranCount befor an error = 1
Msg 544, Level 16, State 1, Line 5
Cannot insert explicit value for identity column in table 'ORC_ORCAMENTO' when IDENTITY_INSERT is set to OFF.
TranCount after an error = 0
See Microsoft Error Message Levels on
https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors
When you use xact abort on, in the try catch statement, you can manually raise an error to make the transaction roll back.
set xact_abort on;
begin try
...dml statements here....
if conditions here...
raiseerror(....);
end try
begin catch
end catch
I have a trigger type INSTEAD OF Insert, Update.
It looks something like below:
IF EXISTS(some select staement)
updade
set
where
ELSE IF (some other select staement)
insert
values
ELSE--(3)
RAISERROR ('msg',16,1)
RETURN; SET NOCOUNT ON;
The issue is that in 3rd - "else" option I would like to show only error message without any "row(s) affected" message.
SET NOCOUNT ON dosen't work for me. I've already tried different configurations, put this with and without return. I was putted it everywhere in my statement.
It doesn't work anywhere. I use SQL Server 2005 Can anybody help me please?
Rows affected massage should appear always. The only exception is else statement.
Use SET NOCOUNT ON; before a query and then use GO
SET NOCOUNT ON;
GO
In a simplified example:
create table T (
ID int not null
)
go
create trigger TT
on T
instead of insert
as
RAISERROR('No',16,1)
go
insert into T (ID)
select 1
We get the output:
Msg 50000, Level 16, State 1, Procedure TT, Line 5
No
(1 row(s) affected)
The only way to suppress that "1 row affected" message is to rollback the transaction (by including ROLLBACK after the error message). And that will generate this instead:
Msg 50000, Level 16, State 1, Procedure TT, Line 5
No
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
There's no way to suppress these messages further. The "1 row affected" message is being generated in the outer scope (the scope in which the INSERT statement is being run) rather than within the scope of the trigger, and is being generated because the trigger is running to completion - so far as the outer statement is concerned, it's been succesful.
The usual advice about NOCOUNT is to use it to suppress additional rowcounts being returned from within a trigger. If you're using it for that, it should be the first statement within the body of the trigger. IN your sample, you have it as the last statement, where it will have no effect anyway.
SeeSnapshot
create TRIGGER triggerdeletefordesignation
ON designation
instead of INSERT,DELETE,update
AS
BEGIN
print '"operation on this table is not allowed"'
rollback
END
GO
insert into designation values(6,'lead');
exec getDesg
and output:
"operation on this table is not allowed"
Msg 3609, Level 16, State 1, Line 12
The transaction ended in the trigger. The batch has been aborted.
and record not inserted into table
i have a series of T-SQL statements separated by the special Query Analyzer batch separator keyword:
GO
If one batch fails, i need Query Analyzer to not try subsequent batches - i want it to stop processing the series of batches.
For example:
PRINT 'This runs'
go
SELECT 0/0, 'This causes an error'
go
PRINT 'This should not run'
go
Output:
This runs
Server: Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.
This should not run
Possible?
Update
An example of this in real use might be:
sp_rename 'Shelby', 'Kirsten'
go
DROP VIEW PeekAView
go
CREATE VIEW PeekAViewAS
SELECT * FROM Kirsten
go
Here is how I'd do it:
PRINT 'This runs'
go
SELECT 0/0, 'This causes an error'
go
if (##error <> 0)
Begin
set nocount on
set noexec on
End
GO
PRINT 'This should not run'
go
set noexec off
set nocount off
GO
The "noexec" mode puts SSMS is a state where it just compiles the T-SQL and doesn't actually execute it. It is similar to accidentally pressing the Parse toolbar button (Ctrl+F5) instead of Execute (F5).
Don't forget to turn noexec back off at the end of your script. Otherwise users are going to get confused by permanent "Command(s) completed successfully." messages.
I use the check against ##error in the subsequent batch instead of using TRY CATCH blocks. Using ##error in the next batch will catch compile errors, like "table doesn't exist".
In addition to the noexec mode, I also toggle the nocount mode. With noexec mode on and nocount off, your queries will still report a message "(0 rows(s) affected)". The message always reports zero rows, because you're in noexec mode. However, turning nocount on suppresses these messages.
Also note that if running SQL Server 2005 the command you are skipping might still give error messages if it references a table that doesn't exist and the command if the first command in the batch. Forcing the command to be the second command in the batch with a bogus Print statement can suppress this. See MS Bug #569263 for more details.
You can activate the "Query, SQLCMD Mode" menu option and place the following at the beginning of the script:
:on error exit
This will stop execution when an error occurs, even if there are subsequent batches.
Just make sure that you don't accidentally run the script without SQLCMD mode on because you will get the typical behavior where errors are ignored.
When I need to do this, I issue a RAISERROR of severity 20. This, or higher, will kill the current connection, and prevent subsequent "GO batches" from executing. Yes, it can be awkward, but it does the job.
Create a temporary table; and update it after each step (if successful); and then check the success of the previous step by validating against the table.
create table #ScriptChecker (SuccessfullStep int)
-- Do Step One
Insert into #ScriptChecker
Select 1
-- Step 2
If exists (select * from #ScriptChecker where SuccessfullStep = 1)
-- Do Step 2 ...
based on #u07ch idea, but only insert on failure...
create table #test (failure int)
if not exists (select * from #test)
BEGIN
print 'one' --sql here
END
go
if not exists (select * from #test)
BEGIN
print 'two'--sql here
END
go
if not exists (select * from #test)
BEGIN
print 'three' ---SQL SERVER 2000 version
--error--
SELECT 0/0, 'This causes an error'
IF ##ERROR!=0
BEGIN
insert into #test values (1)
PRINT 'ERROR'
END
end
go
if not exists (select * from #test)
BEGIN
print 'three' ---SQL SERVER 2005/2008 version
BEGIN TRY
--error--
SELECT 0/0, 'This causes an error'
END TRY
BEGIN CATCH
insert into #test values (1)
PRINT 'ERROR'
END CATCH
END
go
if not exists (select * from #test)
BEGIN
--sql here
print 'four'
END
go
output 2000:
one
two
three
----------- --------------------
Msg 8134, Level 16, State 1, Line 7
Divide by zero error encountered.
(1 row(s) affected)
ERROR
output 2005/2008:
one
two
three
----------- --------------------
(0 row(s) affected)
(1 row(s) affected)
ERROR
Erland Sommarskog in the microsoft.public.sqlserver.programming group had a very good idea:
In a change script such as the one you
posted, you need to be defensive, and
start each batch with IF ##trancount >
0.
Using
IF ##trancount > 0
is much cleaner.