In SQL Server 2016.
I created a column with User-Defined Data Type that binding Rule.
T-SQL script like:
CREATE RULE [dbo].[R_typeYesNo]
AS #column IN ('Y', 'N')
GO
CREATE TYPE [dbo].[typeYesNo]
FROM VARCHAR (1) NULL
GO
EXECUTE sp_bindrule [R_typeYesNo], [typeYesNo]
GO
CREATE TABLE dbo.MyRuleTable (
ID int identity,
ACTIVEYN [dbo].[typeYesNo] NULL,
CONSTRAINT [PK_MyRuleTable] PRIMARY KEY CLUSTERED ([ID] ASC)
)
GO
INSERT INTO dbo.MyRuleTable(ACTIVEYN)
VALUES('A')
GO
-- Exception here
DROP TABLE dbo.MyRuleTable;
DROP TYPE [dbo].[typeYesNo];
DROP RULE [dbo].[R_typeYesNo];
GO
When a wrong value inserted , the Rule check will return exception message:
Msg 513, Level 16, State 0, Line 20 A column insert or update
conflicts with a rule imposed by a previous CREATE RULE statement. The
statement was terminated. The conflict occurred in database 'DB1',
table 'dbo.MyRuleTable', column 'ACTIVEYN'.
Is possible, can customize the message?
Create a custom error message for your Rules
EXEC sp_addmessage
500021,
10,
'This is a custom error message for MyRule'
And use that in CATCH block using RAISERROR or THROW
BEGIN try
-- Exception here
END try
BEGIN catch
IF Error_message() LIKE '%MyRuleTable%'
BEGIN
RAISERROR (500021,10,1)
END
ELSE
BEGIN
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE #ErrorSeverity INT = ERROR_SEVERITY();
DECLARE #ErrorState INT = ERROR_STATE();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END
END catch
Related
When attempting to INSERT INTO a table that does not exist (MigrationLog) I expected the Try Catch to catch this error and execute the code within the CATCH Block however that is not occurring. After the below script is run I'm left with hanging transactions and I am manually Rolling back the Transaction.
When the MigrationLog table does exist and I attempt to insert a varchar into an INT field that error is properly caught and the script behaves as expected.
Are there errors that are NOT caught by Try Catch blocks or am I doing this wrong? - Comments by Dan Guzman and Stu answered this question. There are compile time and run time errors.
Since my error is due to the table not existing and is a Compilation Error the issue I am still having is the fact that the remainder of the script is still running due to it skipping the RETURN within the CATCH Block. How do I stop the script from executing? The script cannot run without that table and needs to be exited. I thought perhaps SET XACT_ABORT ON would be the solution however this setting says it is for Run Time Errors. How do I stop the remainder of the script from executing and also ROLLBACK the open transactions? If Run Time errors are already caught by TRY CATCH Blocks my thought is this would be redundant.
The Error I receive is:
Msg 208, Level 16, State 1, Line 14 Invalid object name 'MigrationLog'
/* DECLARE Individual Error Detail Variables */
DECLARE
#ErrorProcedure VARCHAR(200)
, #ErrorMessage NVARCHAR(4000)
, #ErrorSeverity VARCHAR(20) /* Level aka Severity Level */
, #ErrorState VARCHAR(20)
, #ErrorLine VARCHAR(20);
/* Insert Main Log */
BEGIN
BEGIN TRANSACTION
BEGIN TRY
/* This will error due to the MigrationLog table not having been created */
INSERT INTO MigrationLog (Status, Comments, CreatedDate, UpdatedDate)
VALUES (0, 'Migration Testing', GetDate(), GetDate())
END TRY
BEGIN CATCH
ROLLBACK;
PRINT 'Transaction ROLLED BACK in MigrationLog INSERT';
/* Store Individual Error Details in variables for later output */
SELECT
#ErrorProcedure = ERROR_PROCEDURE()
, #ErrorMessage = ERROR_MESSAGE()
, #ErrorSeverity = ERROR_SEVERITY()
, #ErrorState = ERROR_STATE()
, #ErrorLine = ERROR_LINE();
--ERROR_NUMBER() AS ErrorNumber,
/* RAISE the Error */
RAISERROR(N'An error occurred in %s Inserting the Main MigrationLog Record. Level %s, State %s, Line %s, ErrorMsg: %s', 11, 2, #ErrorProcedure, #ErrorSeverity, #ErrorState, #ErrorLine, #ErrorMessage);
Return;
END CATCH /* END MigrationLog INSERT */
COMMIT;
END
--SELECT ##TRANCOUNT;
When executing my stored procedure, why do I get the following error message?
Msg 266, Level 16, State 2, Procedure spAddCustomer, Line 0 [Batch Start Line 21]
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 2.
Anything help, thanks.
Stored procedure code:
CREATE PROC spAddCustomer
#FirstName VARCHAR = INPUT,
#LastName VARCHAR = INPUT,
#EmailAddress VARCHAR = INPUT,
#PhoneNumber VARCHAR = INPUT
AS
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO sales.CustomerPII (FirstName, LastName, EmailAddress, PhoneNumber)
VALUES (#FirstName, #LastName, #EmailAddress, #PhoneNumber);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
--Rows inserted still exist
--SELECT ERROR_NUMBER()
--ROLLBACK TRANSACTION --Any transaction work will be undone
END CATCH;
Executed
EXEC spAddCustomer 'FirstTest', 'LastTest', 'EmailTest', 'AddressTest';
Un-comment this line:
ROLLBACK TRANSACTION --Any transaction work will be undone
Try setting XACT_ABORT ON in the stored procedure. When SET XACT_ABORT is ON and T-SQL statement raises a run-time error, SQL Server automatically rolls back the current transaction. Try it as follows:
USE AdventureWorks2016CTP3
GO
CREATE PROC spAddCustomer
#FirstName varchar = INPUT,
#LastName varchar = INPUT,
#EmailAddress varchar = INPUT,
#PhoneNumber varchar = INPUT
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO sales.CustomerPII (FirstName,LastName,EmailAddress,PhoneNumber)
VALUES(#FirstName, #LastName, #EmailAddress, #PhoneNumber);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
END CATCH;
Have a stored procedure and have wrapped the code in the format
BEGIN TRY
BEGIN TRAN
...code
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
...code
END CATCH
Which works fine until I started doing some testing for various errors to make sure they were being entered into my log table correctly.
However, when I run this, it fails, and does not rollback and requires a manual rollback. It's like the code does not realise that it is in a TRY block.
Any ideas? (Code below, hoping it can be recreated on someone else's system and not some bizarre way on how the systems where I am are configured)
BEGIN
SET NOCOUNT ON
BEGIN TRY
BEGIN TRAN
---------------------------------------------------------------------
-- Create test table
---------------------------------------------------------------------
IF OBJECT_ID('tempdb..#DateOfBirth') IS NOT NULL DROP TABLE #DateOfBirth
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
)
INSERT INTO #DateOfBirth
VALUES
('1984-12-09')
,('1977-12-09')
,('2015-03-12')
,('1967-01-15')
---------------------------------------------------------------------
-- Date Of Birth
-- This Insert errors
---------------------------------------------------------------------
IF OBJECT_ID('tempdb..#DOB') IS NOT NULL DROP TABLE #DOB
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
)
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth
COMMIT
END TRY
BEGIN CATCH
PRINT 'Rollback'
ROLLBACK
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
END
GO
Compilation errors within the same scope of the CATCH block cannot be caught. To wit, add a PRINT before each statement:
PRINT 'BATCH STARTED';
BEGIN
SET NOCOUNT ON
BEGIN TRY
PRINT 'BEGIN TRAN';
BEGIN TRAN;
IF OBJECT_ID('tempdb..#DateOfBirth') IS NOT NULL DROP TABLE #DateOfBirth;
PRINT 'CREATING #DateOfBirth';
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
);
PRINT 'INSERTING INTO #DateOfBirth';
INSERT INTO #DateOfBirth
VALUES
('1984-12-09')
,('1977-12-09')
,('2015-03-12')
,('1967-01-15')
IF OBJECT_ID('tempdb..#DOB') IS NOT NULL DROP TABLE #DOB;
PRINT 'CREATING #DOB';
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
);
PRINT 'INSERTING INTO #DOB';
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth;
PRINT 'COMMIT';
COMMIT;
END TRY
BEGIN CATCH
PRINT 'ROLLBACK';
ROLLBACK;
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH;
END;
GO
When this batch is initially executed on a new session, the resultant messages are:
BATCH STARTED
BEGIN TRAN
CREATING #DateOfBirth
INSERTING INTO #DateOfBirth
CREATING #DOB
INSERTING INTO #DOB
Msg 206, Level 16, State 2, Line 36
Operand type clash: date is incompatible with int
The important point is that this error occurs during statement compilation, not execution. Because the temp tables do not exist when the batch is compiled, compilation of the statements referencing those tables is deferred until the statements are run. Any errors that occur during the compilation can't be caught in by the CATCH block in the same scope.
The compilation error can be caught if you execute the statements using dynamic SQL so that the compilation occurs in a different scope:
PRINT 'BATCH STARTED';
BEGIN
SET NOCOUNT ON
BEGIN TRY
PRINT 'BEGIN TRAN';
BEGIN TRAN;
EXECUTE('
IF OBJECT_ID(''tempdb..#DateOfBirth'') IS NOT NULL DROP TABLE #DateOfBirth;
PRINT ''CREATING #DateOfBirth'';
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
);
PRINT ''INSERTING INTO #DateOfBirth'';
INSERT INTO #DateOfBirth
VALUES
(''1984-12-09'')
,(''1977-12-09'')
,(''2015-03-12'')
,(''1967-01-15'')
IF OBJECT_ID(''tempdb..#DOB'') IS NOT NULL DROP TABLE #DOB;
PRINT ''CREATING #DOB'';
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
);
PRINT ''INSERTING INTO #DOB'';
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth;
PRINT ''COMMIT'';
COMMIT;
');
END TRY
BEGIN CATCH
PRINT 'ROLLBACK';
ROLLBACK;
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH;
END;
GO
In this case, the CATCH block is entered and the transaction rolled back:
BATCH STARTED
BEGIN TRAN
CREATING #DateOfBirth
INSERTING INTO #DateOfBirth
CREATING #DOB
INSERTING INTO #DOB
ROLLBACK
Msg 50000, Level 16, State 2, Line 58
Operand type clash: date is incompatible with int
BTW, I strongly recommend you specify 'SET XACT_ABORT ON' to help ensure the transaction is rolled back after errors in cases where the CATCH block is not executed (e.g. a client query timeout error).
The error you're having is this:
Msg 206, Level 16, State 2, Line 39
Operand type clash: date is incompatible with int
For this type of error, it is suggested to handle in the application scope, since the batch is simply aborted.
I've compiled a few resources:
Book
Post on SQLSunday.com
Post from Brent Ozar & Team
Hope this helps you.
The question should be quite simple, but I can't figure out the answer nor why my stored procedure is not working.
CREATE PROCEDURE spTest_Delete
#ID int
AS
begin tran
declare #err int
declare #errMesage nvarchar(max)
set #errMesage = ''
set #err = 0
delete from Test
where ID = #ID
set #err = ##ERROR
set #errMesage = ERROR_MESSAGE()
if #err = 0
commit tran
else
begin
RAISERROR(N'Could not delete !Error nr: %d. Message: %s', 1, 16, #err, #errMesage)
rollback tran
end
This procedure runs ok, but in case of FK constraint on the delete statement it runs into an error (which is good) and I would like to catch the error.
Msg 547, Level 16, State 0, Procedure spTest_Delete, Line 12
The DELETE statement conflicted with the REFERENCE constraint
"FK_TEstFK_Test". The conflict occurred in database "Test", table
"dbo.Test", column 'ID'. The statement has been terminated.
Could not delete!
Error nr: 547. Message: (null) Msg 50000, Level 1, State 16
I always get null for my message variable, even though the delete statement throws an error.
You might want to start using TRY..CATCH block in your procedures
Implements error handling for Transact-SQL that is similar to the
exception handling in the Microsoft Visual C# and Microsoft Visual C++
languages. A group of Transact-SQL statements can be enclosed in a TRY
block. If an error occurs in the TRY block, control is passed to
another group of statements that is enclosed in a CATCH block.
So your procedure could be rewritten as:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION
DELETE
FROM Test
WHERE ID = #ID;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Also, please note that you're running as single delete statement. It means that it doesn't need to be wrapped up in a transaction. This question explains why.
Your code becomes this:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DELETE
FROM Test
WHERE ID = #ID;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Now why your #errMessage is always NULL? Because ERROR_MESSAGE() is valid ONLY IN CATCH BLOCK. That's written in documentation:
Returns the message text of the error that caused the CATCH block of a
TRY…CATCH construct to be run.
Using TRY..CATCH in Transact-SQL tells this:
Error information is retrieved by using these functions from anywhere
in the scope of the CATCH block of a TRY…CATCH construct. The error
functions will return NULL if called outside the scope of a CATCH
block.
Try to use TRY CATCH and catch your error like this:
BEGIN TRY
delete from Test
where ID = #ID
END TRY
BEGIN CATCH
SET #ErrorMessage = ERROR_MESSAGE()
SET #ErrorSeverity = ERROR_SEVERITY()
SET #ErrorState = ERROR_STATE()
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
BREAK
END CATCH
Whenever I use ExpectException I receive the following error: (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,140})
It appears that tSQLt is attempting a ROLLBACK after the fact that MSSQL has already performed the ROLLBACK due to the RAISERROR.
I wrapped the following SELECT and SET statement in Private_RunTest with the following IF statement and it appeared to resolve the problem.
IF ISNULL(#ExpectException,0) <> 1
BEGIN
SELECT #Msg = COALESCE(#Msg, '') + ' (There was also a ROLLBACK ERROR --> ' +
COALESCE(ERROR_MESSAGE(), '') + '{' +
COALESCE(ERROR_PROCEDURE(), '') + ',' +
COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})';
SET #Result = 'Error';
END
Is this truly a bug and/or an appropriate fix?
You are probably not using TRY/CATCH blocks in your tsqlt test.
The error message "There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction." can be reproduced as follows:
1) Create a Trigger that throws an error and rolls back as follows:
CREATE TRIGGER [dbo].[MyTable_IUDTR] ON [dbo].[MyTable]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
BEGIN TRY
RAISERROR('MyError', 16, 1)
END TRY
BEGIN CATCH
THROW;
END CATCH
END
GO
2) Create a tsqlt test to check the error message returned:
CREATE PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
SET #ErrorMsg = ERROR_MESSAGE()
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
3) Run the test:
EXEC [tSQLt].Run 'ut_MyTable_IUDTR.test that the error is returned'
4) You get the following Error:
There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
THE FIX:
Change the tsqlt test to include TRY/CATCH blocks as follows:
ALTER PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
BEGIN TRY
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
END TRY
BEGIN CATCH
SET #ErrorMsg = ERROR_MESSAGE()
END CATCH
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
You may be using SET XACT_ABORT ON in your tsqlt test
CREATE PROC [testClass].[test foo]
AS
BEGIN
SET XACT_ABORT ON
EXEC tsqlt.AssertEquals #Expected = 1, #Actual = 0
END
Don't do this.
You'll get a message like this
[testClass].[test foo] failed: (Error) Expected: <1> but was: <0> (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,160})