Check whether these 2 SQLs are same? - sql

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

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

does it run anything after a catch block?

I'm working with SQL Server Express 2012 and I have this stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[usp_AsyncExecActivated]
as
begin
set nocount on;
declare #h uniqueidentifier
, #messageTypeName sysname
, #messageBody varbinary(max)
, #xmlBody xml
, #procedureName sysname
, #startTime datetime
, #finishTime datetime
, #execErrorNumber int
, #execErrorMessage nvarchar(2048)
, #xactState smallint
, #token uniqueidentifier;
begin transaction;
begin try;
receive top(1)
#h = [conversation_handle]
, #messageTypeName = [message_type_name]
, #messageBody = [message_body]
from [AsyncExecQueue];
if (#h is not null)
begin
if (#messageTypeName = N'DEFAULT')
begin
-- The DEFAULT message type is a procedure invocation.
-- Extract the name of the procedure from the message body.
--
select #xmlBody = CAST(#messageBody as xml);
select #procedureName = #xmlBody.value(
'(//procedure/name)[1]'
, 'sysname');
update dbo.Configurations with (serializable) set conf_value = 1
where sp_name = #procedureName
if ##rowcount = 0
begin
insert dbo.Configurations(sp_name, conf_value) values (#procedureName, 1)
end
save transaction usp_AsyncExec_procedure;
select #startTime = GETUTCDATE();
begin try
exec #procedureName;
end try
begin catch
-- This catch block tries to deal with failures of the procedure execution
-- If possible it rolls back to the savepoint created earlier, allowing
-- the activated procedure to continue. If the executed procedure
-- raises an error with severity 16 or higher, it will doom the transaction
-- and thus rollback the RECEIVE. Such case will be a poison message,
-- resulting in the queue disabling.
--
select #execErrorNumber = ERROR_NUMBER(),
#execErrorMessage = ERROR_MESSAGE(),
#xactState = XACT_STATE();
if (#xactState = -1)
begin
rollback;
raiserror(N'Unrecoverable error in procedure %s: %i: %s', 16, 10,
#procedureName, #execErrorNumber, #execErrorMessage);
end
else if (#xactState = 1)
begin
rollback transaction usp_AsyncExec_procedure;
end
end catch
select #finishTime = GETUTCDATE();
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
if (#token is null)
begin
raiserror(N'Internal consistency error: conversation not found', 16, 20);
end
update [AsyncExecResults] set
[start_time] = #starttime
, [finish_time] = #finishTime
, [error_number] = #execErrorNumber
, [error_message] = #execErrorMessage
where [token] = #token;
if (0 = ##ROWCOUNT)
begin
raiserror(N'Internal consistency error: token not found', 16, 30);
end
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
begin
declare #errorNumber int
, #errorMessage nvarchar(4000);
select #xmlBody = CAST(#messageBody as xml);
with xmlnamespaces (DEFAULT N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
select #errorNumber = #xmlBody.value ('(/Error/Code)[1]', 'INT'),
#errorMessage = #xmlBody.value ('(/Error/Description)[1]', 'NVARCHAR(4000)');
-- Update the request with the received error
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
update [AsyncExecResults] set
[error_number] = #errorNumber
, [error_message] = #errorMessage
where [token] = #token;
end conversation #h;
end
else
begin
raiserror(N'Received unexpected message type: %s', 16, 50, #messageTypeName);
end
end
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if (#xactState <> 0)
begin
rollback;
end;
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
raiserror(N'Error: %i, %s', 1, 60, #error, #message) with log;
end catch
update dbo.Configurations with (serializable) set conf_value = 0
where sp_name = #procedureName
end
I have to do this:
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
Every time before stored procedure ends. I'll check dbo.Configurations to see if usp_AsyncExecActivated is running or not.
Do I have to add that update on CATCH BLOCK and after the CATCH BLOCK?
I'm not sure if after catch block runs anything else or it ends stored procedure execution.
It depends on severity, if there's a session sborting error it will stop executing the procedure. Otherwise, it will continue after CATCH block. See this simplified example:
create proc x
as
begin try
select 1/0
end try
begin catch
select error_message()
raiserror (N'Received unexpected message type', 16, 50);
end catch
select 'after catch'
go
exec x;

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.

Getting Request Timeout sometimes from only one SP?

I have a lot of stored procedures. But I am only getting Request Timeout sometimes only for this SP ?
ALTER PROCEDURE [dbo].[Insertorupdatedevicecatalog]
(#OS NVARCHAR(50)
,#UniqueID VARCHAR(500)
,#Longitude FLOAT
,#Latitude FLOAT
,#Culture VARCHAR(10)
,#Other NVARCHAR(200)
,#IPAddress VARCHAR(50)
,#NativeDeviceID VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
DECLARE #OldUniqueID VARCHAR(500) = ''-1'';
SELECT #OldUniqueID = [UniqueID] FROM DeviceCatalog WHERE (#NativeDeviceID != '''' AND [NativeDeviceID] = #NativeDeviceID);
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION Insertorupdatedevicecatalog;
DECLARE #Geo GEOGRAPHY = geography::STGeomFromText(''POINT('' + CONVERT(VARCHAR(100), #Longitude) + '' '' + CONVERT(VARCHAR(100), #Latitude) + '')'', 4326);
IF EXISTS(SELECT 1 FROM DeviceCatalog WHERE [UniqueID] = #UniqueID)
BEGIN
DECLARE #OldGeo GEOGRAPHY
,#OldCity NVARCHAR(100)
,#OldCountry NVARCHAR(100)
,#OldAddress NVARCHAR(100);
SELECT #OldGeo = [LastUpdatedLocationFromJob]
,#OldCity = [City]
,#OldCountry = [Country]
,#OldAddress = [Address]
FROM DeviceCatalog
WHERE [UniqueID] = #UniqueID;
UPDATE DeviceCatalog
SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
WHERE [UniqueID] = #UniqueID;
IF (#OldGeo IS NULL OR #OldAddress IS NULL OR #OldCity IS NULL OR #OldCountry IS NULL OR ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50)
BEGIN
UPDATE DeviceCatalog
SET [Lastmodifieddate] = Getdate()
WHERE [UniqueID] = #UniqueID;
END
END
ELSE
BEGIN
INSERT INTO DeviceCatalog
([OS]
,[UniqueID]
,[Location]
,[Culture]
,[Other]
,[IPAddress]
,[NativeDeviceID])
VALUES (#OS
,#UniqueID
,#Geo
,#Culture
,#Other
,#IPAddress
,#NativeDeviceID);
IF(#OldUniqueID != ''-1'' AND #OldUniqueID != #UniqueID)
BEGIN
EXEC DeleteOldAndroidDeviceID #OldUniqueID, #UniqueID;
END
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 Insertorupdatedevicecatalog;
RAISERROR (''Insertorupdatedevicecatalog: %d: %s'', 16, 1, #error, #message) ;
END CATCH
END
The timeout occurs due to two updates to same table inside same transaction. You could avoid it with a case statement. Also whole IF ELSE can be replaced with a merge.
MERGE INTO DeviceCatalog DC
USING (SELECT #UniqueID AS UniqueID) T ON (DC.UniqueID = T.UniqueID)
WHEN MATCHED THEN
UPDATE SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
,[Lastmodifieddate] = (CASE
WHEN (LastUpdatedLocationFromJob IS NULL OR [Address] IS NULL OR [City] IS NULL OR [Country] IS NULL OR ISNULL(#Geo.STDistance(LastUpdatedLocationFromJob) / 1000,0) > 50)
THEN Getdate()
ELSE [Lastmodifieddate]
END)
WHEN NOT MATCHED THEN
INSERT INTO DeviceCatalog
([OS]
,[UniqueID]
,[Location]
,[Culture]
,[Other]
,[IPAddress]
,[NativeDeviceID])
VALUES (#OS
,#UniqueID
,#Geo
,#Culture
,#Other
,#IPAddress
,#NativeDeviceID)
WHEN NOT MATCHED BY SOURCE AND #OldUniqueID != ''-1'' AND #OldUniqueID != #UniqueID THEN
DELETE;
Try it and check whether this is what you expected.
Already discussed here
You can achieve it using sp_getapplock in TSQL.
But you need a wrapper storedproc or batch for this. Check the following example it will help you to desing your wrapper sp/batch statement.
Sample Code Snippet
Create table MyTable
(
RowId int identity(1,1),
HitStartedAt datetime,
HitTimestamp datetime,
UserName varchar(100)
)
Go
Create proc LegacyProc (#user varchar(100), #CalledTime datetime)
as
Begin
Insert Into MyTable
Values(#CalledTime, getdate(), #user);
--To wait for 10 sec : not required for your procedures, producing the latency to check the concurrent users action
WAITFOR DELAY '000:00:10'
End
Go
Create Proc MyProc
(
#user varchar(100)
)
as
Begin
Declare #PorcName as NVarchar(1000), #CalledTime datetime
Begin Tran
--To get the Current SP Name, it should be unique for each SP / each batch
SET #PorcName = object_name(##ProcID)
SET #CalledTime = Getdate()
--Lock the Current Proc
Exec sp_getapplock #Resource = #PorcName, #LockMode = 'Exclusive'
--Execute Your Legacy Procedures
Exec LegacyProc #user, #CalledTime
--Release the lock
Exec sp_releaseapplock #Resource = #PorcName
Commit Tran
End
You are doing two seperate updates on the DeviceCatalog table where [UniqueID] = #UniqueID in the same transaction.
I bet your locking/request timeout issue is happening when:
IF (#OldGeo IS NULL OR #OldAddress IS NULL OR #OldCity IS NULL OR #OldCountry IS NULL OR ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50) is true.
Try something like this in place of the two updates.
Obviously test in dev first.
In the else clause, you want to have it insert something if the when is false. Here I am just inserting the current before update field contents.
UPDATE DeviceCatalog
SET [OS] = #OS
,[Location] = #Geo
,[Culture] = #Culture
,[Other] = #Other
,[IPAddress] = #IPAddress
,[Lastmodifieddate] =
case when (
#OldGeo is NULL
OR
#OldAddress is NULL
OR
#OldCity is NULL
OR
#OldCountry is NULL
OR
ISNULL(#Geo.STDistance(#OldGeo) / 1000,0) > 50
) then Getdate()
else [Lastmodifieddate]
end
WHERE [UniqueID] = #UniqueID

do you see any problem with that stored procedure template?

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.