SQL TRY Error not CATCHING - sql-server

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.

Related

Cannot see expected PRINT or RAISERROR output when a later error is raised

I'm getting an error message in a stored procedure, saying that I can't insert a NULL value into a table, when I should be getting errors earlier in the code if the value is null.
Here's the relevant part of the stored procedure:
CREATE PROCEDURE [dbo].[udp_AddUpdateStaffVariable]
-- Add the parameters for the stored procedure here
#StaffID int=null,
#VariableTypeID int,
#VariableIntValue int=null,
#VariableVarcharValue varchar(max)=null,
#VariableDatetimeValue datetime=null,
#VariableDecimalValue decimal=null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN TRY
DECLARE #PrintOutput varchar(150)
SET #PrintOutput = '#StaffID = ' + CASE WHEN #StaffID = NULL THEN 'Null' ELSE CONVERT(varchar(20), #StaffID) END
RAISERROR (#PrintOutput, 10, 1) WITH NOWAIT
IF (#StaffID = NULL) -- If the staffid of the current user was not supplied, find it in the Staff table
BEGIN
DECLARE #CurrentUser nvarchar(255) = SUSER_SNAME();
SELECT #StaffID = [StaffID] FROM [dbo].[Staff] WHERE [UserName] = #CurrentUser;
SET #PrintOutput = '#StaffID = ' + CASE WHEN #StaffID = NULL THEN 'Null' ELSE CONVERT(varchar(20), #StaffID) END
RAISERROR (#PrintOutput, 10, 1) WITH NOWAIT
IF #StaffID = NULL -- raise error if staffid wasn't found
BEGIN
RAISERROR (50001 --error number
, 16 -- severity
, 1 --state
, #CurrentUser -- parameter
)
END
END
-- Get the variable data type (used to determine where the variable is stored)
DECLARE #VarDataTypeDesc varchar(20)
DECLARE #StaffVarID int
SELECT #VarDataTypeDesc = dt.[StaffVariableDataType]
FROM [list].[DataTypes] dt INNER JOIN [list].[StaffVariableTypes] svt ON dt.DataTypeID = svt.DataTypeID
WHERE svt.VariableTypeID = #VariableTypeID
-- update or add the staff variable
IF EXISTS (SELECT 1 FROM [dbo].[StaffVariables] WHERE StaffID = #StaffID AND [VariableTypeID] = #VariableTypeID) -- update
BEGIN
IF #VarDataTypeDesc = 'int'
BEGIN -- only update here - other data types are updated further down
UPDATE [dbo].[StaffVariables] SET VariableIntValue = #VariableIntValue WHERE StaffID = #StaffID AND VariableTypeID = #VariableTypeID
END
ELSE -- StaffVariableID is only needed if the variable type is not int
BEGIN
SELECT #StaffVarID = StaffVariableID FROM [dbo].[StaffVariables] WHERE StaffID = #StaffID AND [VariableTypeID] = #VariableTypeID
END
END
ELSE -- insert
BEGIN
IF #VarDataTypeDesc = 'int'
BEGIN
INSERT INTO [dbo].[StaffVariables] (StaffID, VariableTypeID, VariableIntValue)
VALUES (#StaffID, #VariableTypeID, #VariableIntValue)
END
ELSE -- StaffVariableID is only needed if the variable type is not int
BEGIN
DECLARE #StaffVarIDTbl table(ID int)
INSERT INTO [dbo].[StaffVariables] (StaffID, VariableTypeID, VariableIntValue)
OUTPUT INSERTED.[StaffVariableID] INTO #StaffVarIDTbl
VALUES (#StaffID, #VariableTypeID, #VariableIntValue)
SELECT #StaffVarID = ID FROM #StaffVarIDTbl
END
END
-- Cutting out the section where I deal with other variable types besides int here - not relevant to this problem
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH;
END
Here's the test procedure run code:
DECLARE #return_value int
EXEC #return_value = [dbo].[udp_AddUpdateStaffVariable]
#VariableTypeID = 1,
#VariableIntValue = 10
SELECT 'Return Value' = #return_value
GO
...and here's the response:
Msg 50000, Level 16, State 2, Procedure dbo.udp_AddUpdateStaffVariable, Line 130 [Batch Start Line 2]
Cannot insert the value NULL into column 'StaffID', table 'SnippingDbName.dbo.StaffVariables'; column does not allow nulls. INSERT fails.
(1 row affected)
Completion time: 2020-06-01T21:17:08.2049072-05:00
So... here's the question. The error seems to indicate that it either never ran the whole, if #StaffID = NULL portion of the code, or it did, and didn't find the StaffID and set the #StaffID variable. But if that were the case, why can't I see the results of my earlier RAISERROR statements?
I initially tried PRINT and switched to RAISERROR when PRINT wasn't working.
SQL Server 2017 Developer Edition, SSMS 15.0.18183.0
It was a syntax error, that people commenting on the question figured out. IF (#StaffID = NULL) should have been, IF (#StaffID IS NULL) Fixing that in all places in the procedure fixed the problem, and altering my test Staff record so UserName doesn't match SUSER_SNAME() resulted in the expected error.

Stored procedure commit after DELETE and UPDATE run successfully

This is my stored procedure
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
AS
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id=#id
if #FK_idNextProcess=11
Begin
UPDATE [dbo].[ProjectShipping]
SET
QualityAssemblyQty = QualityAssemblyQty- #Quantity
WHERE id=#idPartShip
End
I want when both DELETE and UPDATE run successfully COMMIT the changes otherwise ROLLBACK .
I was wondering if adding COMMIT in the end of stored procedure do the job or I need other method
Here is one way you could tackle this. This is adding a transaction which you will need to handle multiple DML statements in one autonomous block. Then added a try/catch so that if either statement fails the transaction will deal with both statements as one unit of work.
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
(
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
) AS
set nocount on;
begin transaction
begin try
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id = #id
if #FK_idNextProcess = 11
begin
UPDATE [dbo].[ProjectShipping]
SET QualityAssemblyQty = QualityAssemblyQty - #Quantity
WHERE id = #idPartShip
end
commit transaction
end try
begin catch
rollback transaction
declare #error int
, #message varchar(4000);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
raiserror ('DeleteQualityAssemblyProduction: %d: %s', 16, 1, #error, #message) ;
end catch

SQL Server - error after executing stored procedure

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;

How to custom error message when rule check occurred ? T-SQL

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

Using Transaction

How do I alter the following procedure in such a way that if insert statement is not successfully executed because of PrimaryKey or something the Delete Statement must also not be executed and further more it should generate an error message that I would Write myself.
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
begin
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId) values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
end
You use BEGIN TRAN & COMMIT to create a transaction that will be rolled back if your INSERT or DELETE fails:
CREATE PROCEDURE [dbo].[Readmissioninsert] #GRNo VARCHAR(4),
#ClassId NUMERIC(2),
#FacultyId NUMERIC(1),
#Section VARCHAR(1),
#SessionId NUMERIC(1)
AS
BEGIN
BEGIN TRAN --<= Starting point of transaction
INSERT INTO readmissiondtl
(grno,
classid,
facultyid,
section,
sessionid)
VALUES (#GRNo,
#ClassId,
#FacultyId,
#Section,
#SessionId)
DELETE FROM discharge
WHERE grno = #GRNo
COMMIT --<= End point of transaction
END
Documentation
You can use a TRY CATCH for the error message:
How to rollback a transaction in TSQL
TRY CATCH THROW: Error handling changes in T-SQL
Use a transaction and a try catch block. Raise your error in the catch block, like so:
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
begin
Begin transaction
Begin try
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId)
values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
Commit transaction
End try
Begin catch
Rollback
Raiserror(999999,'my message',16,1)
End catch
end
Best Practice for Writing SQL Server Stored Procedure with Transaction -
Enclose withing TRY..CATCH block
Check for ##TRANCOUNT and ROLLBACK Transaction on ERROR
RAISE actual ERROR to alarm calling program
Sample -
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
BEGIN TRAN
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId)
values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
/*ERROR OCCURED*/
DECLARE #ERROR_MESSAGE NVARCHAR(4000);--MESSAGE TEXT
DECLARE #ERROR_SEVERITY INT;--SEVERITY
DECLARE #ERROR_STATE INT;--STATE
SELECT #ERROR_MESSAGE = ERROR_MESSAGE(),
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE()
/*RETURN ERROR INFORMATION ABOUT THE ORIGINAL ERROR THAT CAUSED
EXECUTION TO JUMP TO THE CATCH BLOCK.*/
RAISERROR (#ERROR_MESSAGE, #ERROR_SEVERITY, #ERROR_STATE)
END CATCH
END
Notice that ##TRANCOUNT is checked to verify if there is any open transactions and ERROR messages are retained and raised so program will receive SqlException.

Resources