do you see any problem with that stored procedure template? - sql-server-2005

so I created that(I used some stuff found on other website) to handle transactions and having a sort of stacktrace while executing stored procedure that could call other stored procedure that need transaction and etc.
so if I have A calling B and B is calling C and C got an error, I can correctly rollback my stuff and returning a stacktrace saying: error in C follow the trace to find out where/how/etc...
do any of you find a problem with this logic?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[NAME]
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
declare #trancount int
set #trancount = ##trancount
declare #savePointName varchar(40)
set #savePointName = newid()
BEGIN TRY
if #trancount = 0
begin transaction
else
save transaction #savePointName
/*
// STUFF HERE
*/
if #trancount = 0
commit transaction
END TRY
BEGIN CATCH
declare #xstate int
set #xstate = XACT_STATE()
if #xstate = -1 and #trancount = 0
rollback transaction
if #xstate = 1 and #trancount = 0
rollback transaction
if #xstate = 1 and #trancount > 0
rollback transaction #savePointName
declare #message varchar(max)
set #message = ERROR_MESSAGE() +
' (' + ERROR_PROCEDURE() +
':' + ltrim(str(ERROR_LINE())) +
', Raised ' + ltrim(str(ERROR_NUMBER())) +
', Severity ' + ltrim(str(ERROR_SEVERITY())) +
', State ' + ltrim(str(ERROR_STATE())) + ')'
RAISERROR(#message,16,1)
END CATCH
END

No, I can't spot anything wrong with this code.

Related

How to handle error in a recursive procedure?

How to handle error in a recursive procedure? I'm using SQL Server 2012.
CREATE PROCEDURE [dbo].[TEST]
#i INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION [T]
BEGIN TRY
PRINT #i
IF #i = 10
BEGIN
COMMIT TRANSACTION [T]
RETURN;
END
ELSE
BEGIN
SET #i = #i + 1;
EXEC DBO.TEST #i;
END
COMMIT TRANSACTION [T]
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
ROLLBACK TRANSACTION [T]
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
This is only a example, because I'm not authorized to post original code.
CREATE PROCEDURE [dbo].[TEST]
#i INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #InNestedTransaction BIT;
BEGIN TRY
IF (##TRANCOUNT = 0)
BEGIN
SET #InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET #InNestedTransaction = 1;
END;
/*********************************************/
PRINT #i
IF #i = 8
BEGIN
DECLARE #ForceError INT;
--set #ForceError = 1/0; -- uncomment this line to force error.
END
IF #i = 10
BEGIN
RETURN;
END
ELSE
BEGIN
SET #i = #i + 1;
EXEC DBO.TEST #i;
END
/*********************************************/
IF (##TRANCOUNT > 0 AND #InNestedTransaction = 0)
BEGIN
COMMIT;
END;
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0 AND #InNestedTransaction = 0)
BEGIN
ROLLBACK;
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END;
ELSE
BEGIN
THROW;
END
END CATCH
END

Exception Handling Not working in SQL SERVER

we have a below procedure to update/insert a table. I have added exception handling in stored procedure as below.
CREATE PROCEDURE USP_UPDATESP
#Workstationlist worktable READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE #rerror As int
SET #rerror = 0
BEGIN TRY
BEGIN TRAN
MERGE [dbo].WORKTABLE AS [ofc]
USING #Workstationlist AS [Source] ON [ofc].officeid = [Source].id
WHEN MATCHED THEN
UPDATE
SET NumWorkStations = [Source].wsno,
ModifiedBy = [Source].modifiedby,
ModifiedByUsername = [Source].modifieduser,
ModifiedDate = GETDATE()
WHEN NOT MATCHED THEN
INSERT ( officeid, NumWorkStations, ModifiedBy, ModifiedByUsername, ModifiedDate )
VALUES ([Source].ID,[Source].wsno, [Source].modifiedby, [Source].modifieduser,GETDATE() );
SET #rerror = #rerror + ##error
If #rerror = 0
BEGIN
COMMIT TRAN
END
END TRY
BEGIN CATCH
SELECT #rerror AS ErrNum
ROLLBACK TRAN
End Catch
SET NOCOUNT off
END
GO
When I execute the procedure with an exception (passing null to id column) as below
declare #Workstationlist worktable
insert into #Workstationlist VALUES ( NULL,500,106720,106720)
EXEC USP_UPDATESP #Workstationlist
I got #error as 0 Always . Is there any problem this way of error handling?
to me it looks like you are mixing TRY...CATCH error handling with old style error handling.
the code:
SET #rerror = #rerror + ##error
cannot be reached because when an exception occurs the control is passed to the catch block so the #rerror variable will always be 0, the value initially set.
in the catch block you should leverage the proper structures/objects to access error information and drop all the old way completely.
something like this:
BEGIN CATCH;
DECLARE #ErrSev INT,
#ErrMsg NVARCHAR(MAX),
#ErrState INT;
SELECT #ErrSev = ERROR_SEVERITY(),
#ErrState = ERROR_STATE(),
#ErrMsg = isnull(ERROR_PROCEDURE(), '(unknown procedure)') + ': ' + isnull(ERROR_MESSAGE(), '(unknown message)');
RAISERROR(#ErrMsg, #ErrSev, #ErrState);
END CATCH;
Catch the error in proper way.
BEGIN TRY
BEGIN TRANSACTION;
COMMIT TRANSACTION;
SELECT 'Success' AS Result
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
SELECT 'Failed' AS Result
,ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage
END CATCH;
END

There was also a ROLLBACK ERROR and tSQLt.ExpectException

Here is the scenario:
Stored procedure sproc_a calls sproc_b. Then sproc_b calls sproc_c. A typical nested procedure.
Sproc_a did a SET XACT_ABORT ON; and used named transaction.
Sproc_c raised an error.
tSQLt.ExpectException failed to acknowledge the error. The test should be successful but it failed.
Below is the code to replicate the scenario.
create procedure sproc_c
as
RAISERROR('An error is found', 11, 1)
go
create procedure sproc_b
as
exec dbo.sproc_c;
go
create procedure sproc_a
as
SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS ON
SET NOCOUNT ON
SET XACT_ABORT ON
SET ANSI_WARNINGS OFF
declare #transactionName as varchar(50) = '[POC]';
begin tran #transactionName
save tran #transactionName
exec dbo.sproc_b;
commit tran #transactionName
go
CREATE PROCEDURE [test sproc_a]
AS
-- Assert
BEGIN
EXEC tSQLt.ExpectException
#ExpectedMessage = 'An error is found'
END
-- Act
BEGIN
EXEC dbo.sproc_a
END
GO
EXEC tSQLt.Run '[test sproc_a]'
When I removed the SET XACT_ABORT ON, the unit test is successful but it hitches an error with it: Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
This is more like a bug report. Well I guess maybe the question is: anyone who has an idea on how to fix it? :)
Applying the logic from How to ROLLBACK a transaction when testing using tSQLt add a TRY CATCH that checks to see if a ROLLBACK is still needed.
create procedure sproc_c
as
RAISERROR('An error is found', 11, 1)
go
create procedure sproc_b
as
exec dbo.sproc_c;
go
create procedure sproc_a
as
SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS ON
SET NOCOUNT ON
SET XACT_ABORT ON
SET ANSI_WARNINGS OFF
declare #transactionName as varchar(50) = '[POC]';
BEGIN TRY
begin tran #transactionName
save tran #transactionName
exec dbo.sproc_b;
commit tran #transactionName
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK;
-- Do some exception handling
-- You'll need to reraise the error to prevent exceptions about inconsistent
-- ##TRANCOUNT before / after execution of the stored proc.
RAISERROR('An error is found', 11, 1);
END CATCH
go
CREATE PROCEDURE [test sproc_a]
AS
-- Assert
BEGIN
EXEC tSQLt.ExpectException
#ExpectedMessage = 'An error is found'
END
-- Act
BEGIN
EXEC dbo.sproc_a
END
GO
EXEC tSQLt.Run '[test sproc_a]'
I'm making a separate comment to answer my own question. I've investigated tSQLt.Private_RunTest. It turns out:
Private_RunTest has a BEGIN TRAN. Then it will save a named transaction.
It is followed by a TRY-CATCH, it will do an exec(#cmd). This basically executes your unit tests. For example: "EXEC tSQLt.Run '[test sproc_a]'".
When sproc_a raises an error, Private_RunTest will try to do a ROLLBACK TRAN #TranName. This will fail because it does not rollback the named transaction in sproc_a.
As a resolution, I hacked tSQLt.Private_RunTest and replaced the code "ROLLBACK TRAN #TranName;" with "ROLLBACK TRAN;".
I also added a condition when doing a commit.
I'm not sure what the implications are after doing this change. We'll see how it goes. Below are my changes:
CREATE PROCEDURE tSQLt.Private_RunTest
#TestName NVARCHAR(MAX),
#SetUp NVARCHAR(MAX) = NULL
AS
BEGIN
DECLARE #Msg NVARCHAR(MAX); SET #Msg = '';
DECLARE #Msg2 NVARCHAR(MAX); SET #Msg2 = '';
DECLARE #Cmd NVARCHAR(MAX); SET #Cmd = '';
DECLARE #TestClassName NVARCHAR(MAX); SET #TestClassName = '';
DECLARE #TestProcName NVARCHAR(MAX); SET #TestProcName = '';
DECLARE #Result NVARCHAR(MAX); SET #Result = 'Success';
DECLARE #TranName CHAR(32); EXEC tSQLt.GetNewTranName #TranName OUT;
DECLARE #TestResultId INT;
DECLARE #PreExecTrancount INT;
TRUNCATE TABLE tSQLt.CaptureOutputLog;
CREATE TABLE #ExpectException(ExpectException INT,ExpectedMessage NVARCHAR(MAX), ExpectedSeverity INT, ExpectedState INT, ExpectedMessagePattern NVARCHAR(MAX), ExpectedErrorNumber INT, FailMessage NVARCHAR(MAX));
IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE name = N'SetFakeViewOnTrigger')
BEGIN
RAISERROR('Test system is in an invalid state. SetFakeViewOff must be called if SetFakeViewOn was called. Call SetFakeViewOff after creating all test case procedures.', 16, 10) WITH NOWAIT;
RETURN -1;
END;
SELECT #Cmd = 'EXEC ' + #TestName;
SELECT #TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(#TestName)), --tSQLt.Private_GetCleanSchemaName('', #TestName),
#TestProcName = tSQLt.Private_GetCleanObjectName(#TestName);
INSERT INTO tSQLt.TestResult(Class, TestCase, TranName, Result)
SELECT #TestClassName, #TestProcName, #TranName, 'A severe error happened during test execution. Test did not finish.'
OPTION(MAXDOP 1);
SELECT #TestResultId = SCOPE_IDENTITY();
BEGIN TRAN;
SAVE TRAN #TranName;
SET #PreExecTrancount = ##TRANCOUNT;
TRUNCATE TABLE tSQLt.TestMessage;
DECLARE #TmpMsg NVARCHAR(MAX);
BEGIN TRY
IF (#SetUp IS NOT NULL) EXEC #SetUp;
EXEC (#Cmd);
IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1))
BEGIN
SET #TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.';
EXEC tSQLt.Fail #TmpMsg;
END
END TRY
BEGIN CATCH
IF ERROR_MESSAGE() LIKE '%tSQLt.Failure%'
BEGIN
SELECT #Msg = Msg FROM tSQLt.TestMessage;
SET #Result = 'Failure';
END
ELSE
BEGIN
DECLARE #ErrorInfo NVARCHAR(MAX);
SELECT #ErrorInfo =
COALESCE(ERROR_MESSAGE(), '<ERROR_MESSAGE() is NULL>') +
'[' +COALESCE(LTRIM(STR(ERROR_SEVERITY())), '<ERROR_SEVERITY() is NULL>') + ','+COALESCE(LTRIM(STR(ERROR_STATE())), '<ERROR_STATE() is NULL>') + ']' +
'{' + COALESCE(ERROR_PROCEDURE(), '<ERROR_PROCEDURE() is NULL>') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '<ERROR_LINE() is NULL>') + '}';
IF(EXISTS(SELECT 1 FROM #ExpectException))
BEGIN
DECLARE #ExpectException INT;
DECLARE #ExpectedMessage NVARCHAR(MAX);
DECLARE #ExpectedMessagePattern NVARCHAR(MAX);
DECLARE #ExpectedSeverity INT;
DECLARE #ExpectedState INT;
DECLARE #ExpectedErrorNumber INT;
DECLARE #FailMessage NVARCHAR(MAX);
SELECT #ExpectException = ExpectException,
#ExpectedMessage = ExpectedMessage,
#ExpectedSeverity = ExpectedSeverity,
#ExpectedState = ExpectedState,
#ExpectedMessagePattern = ExpectedMessagePattern,
#ExpectedErrorNumber = ExpectedErrorNumber,
#FailMessage = FailMessage
FROM #ExpectException;
IF(#ExpectException = 1)
BEGIN
SET #Result = 'Success';
SET #TmpMsg = COALESCE(#FailMessage+' ','')+'Exception did not match expectation!';
IF(ERROR_MESSAGE() <> #ExpectedMessage)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Message: <'+#ExpectedMessage+'>'+CHAR(13)+CHAR(10)+
'Actual Message : <'+ERROR_MESSAGE()+'>';
SET #Result = 'Failure';
END
IF(ERROR_MESSAGE() NOT LIKE #ExpectedMessagePattern)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Message to be like <'+#ExpectedMessagePattern+'>'+CHAR(13)+CHAR(10)+
'Actual Message : <'+ERROR_MESSAGE()+'>';
SET #Result = 'Failure';
END
IF(ERROR_NUMBER() <> #ExpectedErrorNumber)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Error Number: '+CAST(#ExpectedErrorNumber AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual Error Number : '+CAST(ERROR_NUMBER() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(ERROR_SEVERITY() <> #ExpectedSeverity)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Severity: '+CAST(#ExpectedSeverity AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual Severity : '+CAST(ERROR_SEVERITY() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(ERROR_STATE() <> #ExpectedState)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected State: '+CAST(#ExpectedState AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual State : '+CAST(ERROR_STATE() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(#Result = 'Failure')
BEGIN
SET #Msg = #TmpMsg;
END
END
ELSE
BEGIN
SET #Result = 'Failure';
SET #Msg =
COALESCE(#FailMessage+' ','')+
'Expected no error to be raised. Instead this error was encountered:'+
CHAR(13)+CHAR(10)+
#ErrorInfo;
END
END
ELSE
BEGIN
SET #Result = 'Error';
SET #Msg = #ErrorInfo;
END
END;
END CATCH
BEGIN TRY
-- Replaced "ROLLBACK TRAN #TranName;" with "ROLLBACK TRAN;". The prior approach can't handle nested named transactions.
--ROLLBACK TRAN #TranName;
ROLLBACK TRAN;
END TRY
BEGIN CATCH
DECLARE #PostExecTrancount INT;
SET #PostExecTrancount = #PreExecTrancount - ##TRANCOUNT;
IF (##TRANCOUNT > 0) ROLLBACK;
BEGIN TRAN;
IF( #Result <> 'Success'
OR #PostExecTrancount <> 0
)
BEGIN
SELECT #Msg = COALESCE(#Msg, '<NULL>') + ' (There was also a ROLLBACK ERROR --> ' + COALESCE(ERROR_MESSAGE(), '<ERROR_MESSAGE() is NULL>') + '{' + COALESCE(ERROR_PROCEDURE(), '<ERROR_PROCEDURE() is NULL>') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '<ERROR_LINE() is NULL>') + '})';
SET #Result = 'Error';
END
END CATCH
If(#Result <> 'Success')
BEGIN
SET #Msg2 = #TestName + ' failed: (' + #Result + ') ' + #Msg;
EXEC tSQLt.Private_Print #Message = #Msg2, #Severity = 0;
END
IF EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Id = #TestResultId)
BEGIN
UPDATE tSQLt.TestResult SET
Result = #Result,
Msg = #Msg
WHERE Id = #TestResultId;
END
ELSE
BEGIN
INSERT tSQLt.TestResult(Class, TestCase, TranName, Result, Msg)
SELECT #TestClassName,
#TestProcName,
'?',
'Error',
'TestResult entry is missing; Original outcome: ' + #Result + ', ' + #Msg;
END
-- Add "IF (##TRANCOUNT > 0)" so that it will only do the commit if there is a transaction.
IF (##TRANCOUNT > 0)
COMMIT;
END;

SQL raisserror not showing

I'm trying to execute a sproc but I'm not sure if I'm going in the right direction. When my IF conditions are true it will not print my raiseerrors...
set transaction isolation level repeatable read
declare #return_value int = 0
declare #someValue int = 3
SET #retry = 3;
--Keep trying to update
--table if this task is
--selected as the deadlock
--victim.
WHILE (#retry > 0)
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
--check someValue
if #someValue < 5
begin
raiserror ('number is less than 5', 16,1)
ROLLBACK TRANSACTION
return 99
end
--all o.k , set retry 0 ending the while, commit transaction--
SET #retry = 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
RAISERROR ('Errors found, please fix these errors and retry. Transaction Rolled back', 16, 2);
-- Check error number.
-- If deadlock victim error,
-- then reduce retry count
-- for next update retry.
-- If some other error
-- occurred, then exit
-- retry WHILE loop.
IF (ERROR_NUMBER() = 1205)
SET #retry = #retry - 1;
ELSE
SET #retry = -1;
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
END CATCH;
END; -- End WHILE loop.
The first RaIsError will be consumed by the catch block. If you want to preserve it, and perhaps add additional information, you can do something like this:
set xact_abort, nocount on;
set transaction isolation level repeatable read; -- Scope is this stored procedure.
declare #LocalTransaction as Bit = case when ##TranCount = 0 then 1 else 0 end;
declare #Return_Value as Int = 0;
declare #SomeValue int = 3;
declare #Retry as Int = 3;
while #Retry > 0
begin
begin try
if #LocalTransaction = 1
begin transaction;
if #SomeValue < 5
RaIsError ( 'Number is less than 5.', 16, 1 );
set #Retry = 0;
if #LocalTransaction = 1
commit transaction;
end try
begin catch
if Error_Number() = 1205
set #Retry -= 1;
else
begin
set #Retry = -1;
-- Save the exception.
declare #ErrorLine as Int = Error_Line();
declare #ErrorMessage as NVarChar(4000) = Error_Message();
declare #ErrorNumber as Int = Error_Number();
declare #ErrorProcedure as NVarChar(126) = Error_Procedure();
declare #ErrorSeverity as Int = Error_Severity();
declare #ErrorState as Int = Error_State();
declare #NewLine as Char(2) = Char( 13 ) + Char( 10 ); -- '\r\n'.
-- Rollback only transactions when there is an active transaction that we started.
if Xact_State() <> 0 and #LocalTransaction = 1
rollback transaction;
-- Exit with the exception.
RaIsError( '%s%s#%i [Proc: %s/Line %i]', #ErrorSeverity, #ErrorState, #ErrorMessage, #NewLine, #ErrorNumber, #ErrorProcedure, #ErrorLine );
return 99;
end;
end catch;
end;
Note that the error return is also handled by the catch block since code in the try block after the RaIsError shouldn't execute.

Check whether these 2 SQLs are same?

I want to optimize my SQL. I have made some changes. Not sure these changes are correect or not,
Is this SQL,
ALTER PROCEDURE [dbo].[DeleteOldDeviceID]
(
#OldDeviceID VARCHAR(500)
,#NewDeviceID VARCHAR(500)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION DeleteOldDeviceID;
IF #NewDeviceID <> '-1' AND NOT EXISTS(SELECT 1 FROM DeviceCatalog WHERE [UniqueID] = #NewDeviceID)
BEGIN
INSERT INTO [DeviceCatalog]
([os]
,[uniqueid]
,[address]
,[location]
,[culture]
,[city]
,[country]
,[other]
,[lastmodifieddate]
,[createddate]
,[IsActive]
,[IPAddress]
,[NativeDeviceID]
,[IsDeleted])
SELECT [os]
,#NewDeviceID
,[address]
,[location]
,[culture]
,[city]
,[country]
,[other]
,GETDATE()
,GETDATE()
,[IsActive]
,[IPAddress]
,[NativeDeviceID]
,[IsDeleted]
FROM [DeviceCatalog]
WHERE [UniqueID] = #OldDeviceID;
END
DELETE FROM DeviceCatalog WHERE [UniqueID] = #OldDeviceID; -- Always Delete old one
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 DeleteOldDeviceID;
RAISERROR ('DeleteOldDeviceID: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
is equal to this,
ALTER PROCEDURE [dbo].[DeleteOldDeviceID]
(
#OldDeviceID VARCHAR(500)
,#NewDeviceID VARCHAR(500)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION DeleteOldDeviceID;
IF #NewDeviceID <> '-1' AND NOT EXISTS(SELECT 1 FROM DeviceCatalog WHERE [UniqueID] = #NewDeviceID)
BEGIN
UPDATE [DeviceCatalog]
SET [UniqueID] = #NewDeviceID
WHERE [UniqueID] = #OldDeviceID;
END
ELSE
BEGIN
DELETE FROM DeviceCatalog WHERE [UniqueID] = #OldDeviceID; -- Always Delete old one
END
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 DeleteOldDeviceID;
RAISERROR ('DeleteOldDeviceID: %d: %s', 16, 1, #error, #message) ;
END CATCH
END