I have been dealing with a similar issue to the one posed in this question - except in my context I am performing the command from an ASP.NET webpage.
Remus Rusanu posted an excellent solution using Service Broker Activation on his personal website, which I was able to get working great.
However I have had no luck getting his example which accepts parameters to function. It is supposed to insert data into the withParam table.
exec usp_AsyncExecInvoke #procedureName = N'usp_withParam'
, #p1 = 1.0, #n1 = N'#id'
, #p2 = N'Foo', #n2='#name'
, #p3 = 0xBAADF00D, #n3 = '#bytes'
, #token = #token output;
waitfor delay '00:00:05';
select * from AsyncExecResults;
select * from withParam;
instead it completes without error and SELECT * withParam shows an empty table.
The SELECT * AsyncExecResults table shows the command was submitted but doesn't display any error information (as it should if there was an error).
Passing Parameters to a Background Procedure
It has been suggested in the comments of the webpage that there is an issue using quotename if you use a schema it ends up wrapping it like this [MySchema.SomeFunction]
Removing quotename has not made a difference.
select #stmt = #stmtDeclarations + #stmtValues + N'
exec ' + #x.value(N'(//procedure/name)[1]', N'sysname');
As the query is executing without error I have very little to go on to debug this further. Does anyone know what else might cause this effect?
The Code
create table [AsyncExecResults] (
[token] uniqueidentifier primary key
, [submit_time] datetime not null
, [start_time] datetime null
, [finish_time] datetime null
, [error_number] int null
, [error_message] nvarchar(2048) null);
go
create queue [AsyncExecQueue];
go
create service [AsyncExecService] on queue [AsyncExecQueue] ([DEFAULT]);
GO
-- Dynamic SQL helper procedure
-- Extracts the parameters from the message body
-- Creates the invocation Transact-SQL batch
-- Invokes the dynmic SQL batch
create procedure [usp_procedureInvokeHelper] (#x xml)
as
begin
set nocount on;
declare #stmt nvarchar(max)
, #stmtDeclarations nvarchar(max)
, #stmtValues nvarchar(max)
, #i int
, #countParams int
, #namedParams nvarchar(max)
, #paramName sysname
, #paramType sysname
, #paramPrecision int
, #paramScale int
, #paramLength int
, #paramTypeFull nvarchar(300)
, #comma nchar(1)
select #i = 0
, #stmtDeclarations = N''
, #stmtValues = N''
, #namedParams = N''
, #comma = N''
declare crsParam cursor forward_only static read_only for
select x.value(N'#Name', N'sysname')
, x.value(N'#BaseType', N'sysname')
, x.value(N'#Precision', N'int')
, x.value(N'#Scale', N'int')
, x.value(N'#MaxLength', N'int')
from #x.nodes(N'//procedure/parameters/parameter') t(x);
open crsParam;
fetch next from crsParam into #paramName
, #paramType
, #paramPrecision
, #paramScale
, #paramLength;
while (##fetch_status = 0)
begin
select #i = #i + 1;
select #paramTypeFull = #paramType +
case
when #paramType in (N'varchar'
, N'nvarchar'
, N'varbinary'
, N'char'
, N'nchar'
, N'binary') then
N'(' + cast(#paramLength as nvarchar(5)) + N')'
when #paramType in (N'numeric') then
N'(' + cast(#paramPrecision as nvarchar(10)) + N',' +
cast(#paramScale as nvarchar(10))+ N')'
else N''
end;
-- Some basic sanity check on the input XML
if (#paramName is NULL
or #paramType is NULL
or #paramTypeFull is NULL
or charindex(N'''', #paramName) > 0
or charindex(N'''', #paramTypeFull) > 0)
raiserror(N'Incorrect parameter attributes %i: %s:%s %i:%i:%i'
, 16, 10, #i, #paramName, #paramType
, #paramPrecision, #paramScale, #paramLength);
select #stmtDeclarations = #stmtDeclarations + N'
declare #pt' + cast(#i as varchar(3)) + N' ' + #paramTypeFull
, #stmtValues = #stmtValues + N'
select #pt' + cast(#i as varchar(3)) + N'=#x.value(
N''(//procedure/parameters/parameter)[' + cast(#i as varchar(3))
+ N']'', N''' + #paramTypeFull + ''');'
, #namedParams = #namedParams + #comma + #paramName
+ N'=#pt' + cast(#i as varchar(3));
select #comma = N',';
fetch next from crsParam into #paramName
, #paramType
, #paramPrecision
, #paramScale
, #paramLength;
end
close crsParam;
deallocate crsParam;
select #stmt = #stmtDeclarations + #stmtValues + N'
exec ' + quotename(#x.value(N'(//procedure/name)[1]', N'sysname'));
if (#namedParams != N'')
select #stmt = #stmt + N' ' + #namedParams;
exec sp_executesql #stmt, N'#x xml', #x;
end
go
create procedure usp_AsyncExecActivated
as
begin
set nocount on;
declare #h uniqueidentifier
, #messageTypeName sysname
, #messageBody varbinary(max)
, #xmlBody xml
, #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.
--
select #xmlBody = CAST(#messageBody as xml);
save transaction usp_AsyncExec_procedure;
select #startTime = GETUTCDATE();
begin try
exec [usp_procedureInvokeHelper] #xmlBody;
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: %i: %s', 16, 10,
#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;
raiserror(N'Error: %i, %s', 1, 60, #error, #message) with log;
end catch
end
go
alter queue [AsyncExecQueue]
with activation (
procedure_name = [usp_AsyncExecActivated]
, max_queue_readers = 1
, execute as owner
, status = on);
go
-- Helper function to create the XML element
-- for a passed in parameter
create function [dbo].[fn_DescribeSqlVariant] (
#p sql_variant
, #n sysname)
returns xml
with schemabinding
as
begin
return (
select #n as [#Name]
, sql_variant_property(#p, 'BaseType') as [#BaseType]
, sql_variant_property(#p, 'Precision') as [#Precision]
, sql_variant_property(#p, 'Scale') as [#Scale]
, sql_variant_property(#p, 'MaxLength') as [#MaxLength]
, #p
for xml path('parameter'), type)
end
GO
-- Invocation wrapper. Accepts arbitrary
-- named parameetrs to be passed to the
-- background procedure
create procedure [usp_AsyncExecInvoke]
#procedureName sysname
, #p1 sql_variant = NULL, #n1 sysname = NULL
, #p2 sql_variant = NULL, #n2 sysname = NULL
, #p3 sql_variant = NULL, #n3 sysname = NULL
, #p4 sql_variant = NULL, #n4 sysname = NULL
, #p5 sql_variant = NULL, #n5 sysname = NULL
, #token uniqueidentifier output
as
begin
declare #h uniqueidentifier
, #xmlBody xml
, #trancount int;
set nocount on;
set #trancount = ##trancount;
if #trancount = 0
begin transaction
else
save transaction usp_AsyncExecInvoke;
begin try
begin dialog conversation #h
from service [AsyncExecService]
to service N'AsyncExecService', 'current database'
with encryption = off;
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
select #xmlBody = (
select #procedureName as [name]
, (select * from (
select [dbo].[fn_DescribeSqlVariant] (#p1, #n1) AS [*]
WHERE #p1 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p2, #n2) AS [*]
WHERE #p2 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p3, #n3) AS [*]
WHERE #p3 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p4, #n4) AS [*]
WHERE #p4 IS NOT NULL
union all select [dbo].[fn_DescribeSqlVariant] (#p5, #n5) AS [*]
WHERE #p5 IS NOT NULL
) as p for xml path(''), type
) as [parameters]
for xml path('procedure'), type);
send on conversation #h (#xmlBody);
insert into [AsyncExecResults]
([token], [submit_time])
values
(#token, getutcdate());
if #trancount = 0
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048)
, #xactState smallint;
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if #xactState = -1
rollback;
if #xactState = 1 and #trancount = 0
rollback
if #xactState = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror(N'Error: %i, %s', 16, 1, #error, #message);
end catch
end
go
-- Sample invocation example
-- The usp_withParam will insert
-- all the received parameters into this table
--
create table [withParam] (
id numeric(4,1) NULL
, name varchar(150) NULL
, date datetime NULL
, value int NULL
, bytes varbinary(max) NULL);
go
create procedure usp_withParam
#id numeric(4,1)
, #name varchar(150)
, #date datetime = NULL
, #value int = 0
, #bytes varbinary(max) = NULL
as
begin
insert into [withParam] (
id
, name
, date
, value
, bytes)
select #id as [id]
, #name as [name]
, #date as [date]
, #value as [value]
, #bytes as [bytes]
end
go
declare #token uniqueidentifier;
exec usp_AsyncExecInvoke #procedureName = N'usp_withParam'
, #p1 = 1.0, #n1 = N'#id'
, #p2 = N'Foo', #n2='#name'
, #p3 = 0xBAADF00D, #n3 = '#bytes'
, #token = #token output;
waitfor delay '00:00:05';
select * from AsyncExecResults;
select * from withParam;
go
It does appear that Remus's blog post neglected to note that we need to use SET ENABLE_BROKER to make the async tasks actually run.
This worked for me (only run this if you are OK with all connections to your DB being closed, this command is from SET ENABLE_BROKER never completes in SQL Server):
ALTER DATABASE [Database_name] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;
After running the command above I ran this and was happy to see that the "start_time", "finish_time" and if there were errors the "error_message" columns are now populated!
SELECT * FROM [dbo].[AsyncExecResults]
Edit: See also, this file for the complete code (which the author posted from his blog post) which worked for me once I used the "SET ENABLE_BROKER" command above): https://github.com/rusanu/async_tsql/blob/master/src/with_params.sql
Related
I was creating a Stored Procedure for validation based on not allowing special characters in LeaveTypeText, for my project that I am learning now. And I want LeaveTypeText to not allow duplicate values and update LeaveTypeKey based on LeaveTypeText by removing space in between two characters.
How can I correct this?
ALTER PROCEDURE [dbo].[PreSaveAnd_AfterSave_Validation] (
#FBFormId INT
,#PkId INT
,#TF_ProjectId INT
,#UserDetails NVARCHAR(MAX)
,#IsPostSave BIT
,#ContactId INT
,#Who NVARCHAR(100)
,#RoleId INT
,#Culture NVARCHAR(200)
)
AS
BEGIN
DECLARE #GlobalDateFormatCode INT
DECLARE #ErrorMsg NVARCHAR(Max)
SELECT #GlobalDateFormatCode = dbo.TF_FN_GetCurrentDateFormat()
IF ISNULL(#IsPostSave, 0) = 0
BEGIN
DECLARE #ParaMeterTable TABLE (
Id INT IDENTITY(1, 1) PRIMARY KEY
,ParameterName NVARCHAR(MAX)
,ParameterValue NVARCHAR(MAX)
)
INSERT INTO #ParaMeterTable (
ParameterName
,ParameterValue
)
SELECT LTRIM(LEFT(Value, CHARINDEX('=', Value, 0) - 1)) AS ParameterName
,LTRIM(SUBSTRING(Value, CHARINDEX('=', Value, 0) + 1, LEN(Value))) AS ParameterValue
FROM dbo.TF_FN_Split(#UserDetails, '||')
WHERE ISNULl(Value, '') <> ''
--SELECT * FROM #ParaMeterTable
DECLARE #LeaveTypeText NVARCHAR(1000)
DECLARE #LeaveTypeKey NVARCHAR(1000)
--SELECT #LeaveTypeText = ParameterValue
--FROM #ParaMeterTable
--WHERE ParameterName LIKE '#LeaveTypeText%'
--select #LeaveTypeKey = LeaveTypeKey from DBO.Master_LeaveType_Sample
--SELECT #LeaveTypeText
SET #ErrorMsg = ' "Leave Type Key" field does not allow Special characters '
--Validate Here
IF EXISTS (#LeaveTypeKey like '%$%')
BEGIN
SELECT 0 AS IsValid
,#ErrorMsg AS [ErrorMessage]
END
IF (ISNULL(#ErrorMsg, '') != '')
BEGIN
SELECT 0 AS IsValid
,#ErrorMsg AS [ErrorMessage]
END
ELSE
BEGIN
SELECT 1 IsValid
,dbo.TF_FN_GetCultureMessage('TF_DB_Success', #ContactId, #Who, #RoleId, #Culture) AS [ErrorMessage]
END
END
ELSE IF ISNULL(#IsPostSave, 0) = 1
BEGIN
-- Post Save Logic
UPDATE DBO.Master_LeaveType_Sample
SET LeaveTypeKey = Replace(ParameterValue, ' ', '')
FROM DBO.Master_LeaveType_Sample
WHERE LeaveTypeText = ParameterValue
SELECT 1 IsValid
,dbo.TF_FN_GetCultureMessage('TF_DB_Success', #ContactId, #Who, #RoleId, #Culture) AS [ErrorMessage]
END
END
I am creating a stored procedure to insert to a table. Following would provide my table columns are
Create Table Item
(
ID char(20),
Name varchar(max) NOT NULL,
Brand char(10) NOT NULL,
Category char(10) NOT NULL,
Unit_Of_Measure char(5) NOT NULL,
Price decimal(18,2) NOT NULL,
[Image] image NOT NULL,
Active bit NOT NULL Default('True')
Constraint PK_Item Primary Key (ID),
Constraint FK_Item_Brands Foreign Key (Brand) References Brands(ID),
Constraint FK_Item_Item_Category Foreign Key (Category) References Item_Category(ID),
Constraint FK_Item_Unit_Of_Measure Foreign Key (Unit_Of_Measure) References Unit_Of_Measure(ID)
);
go
Table insert is working fine. Before we go further I need to show the error that I am getting.
Msg 50000, Level 16, State 2, Procedure stpItem, Line 66
Operand type clash: image is incompatible with nvarchar
The above error occurs when I'm executing the command below and after that you would see the stored procedure
exec stpItem '','GG','2','2','2',123.12, null, 'true', 'false'
Stored procedure code:
Create Proc stpItem
#ID varchar(max),
#Name varchar(max),
#Brand varchar(max),
#Category varchar(max),
#UOM varchar(max),
#Price decimal,
#Image Image,
#Active bit,
#Update bit
As
Set Transaction Isolation Level Serializable
Begin Transaction
Begin Try
-- If it is an insertion
IF #Update = 'False'
Begin
Declare #Pre char(1),
#Len int,
#Next int,
#Start int,
#SetKey varchar(max);
Set #SetKey = '';
Select #Pre = Prefix, #Len = [Length], #Next = [Next] From Key_Generation Where Table_Name = 'Item';
Set #Start = 1;
Set #Len = #Len - 1;
While #Start < #Len
Begin
set #SetKey = #SetKey + '0'
set #Start = #Start + 1;
End
Exec('Declare #LID char(20); Set #LID = (select ''' + #Pre + ''' + right(''' + #SetKey + ''' + cast(' + #Next + ' as varchar(' + #Len + ')), ' + #Len + '));' +
'Insert into Item Values (#LID,''' + #Name + ''',''' + #Brand + ''',''' + #Category + ''',''' + #UOM + ''',' + #Price + ',' + #Image + ' ,' + #Active + ');' +
'Update Key_Generation Set [Next] = [Next] + 1 Where Table_Name = ''Item'';');
End
Else
Begin
Update Item
Set Name = #Name,
[Brand] = #Brand,
[Category] = #Category,
[Unit_Of_Measure] = #UOM,
Price = #Price,
[Image] = #Image,
Active = #Active
Where ID = #ID;
End
Commit Transaction;
End Try
Begin Catch
Rollback Transaction;
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
Could anyone tell me why I am getting this error, and how to fix this issue?
You are not casting non varchar variables to varchar here:
#Price + ',' + #Image + ' ,' + #Active + ');'
Try:
cast(#price as varchar(max)) + ',' + cast(#image as varchar(max)) + ...
cast(cast(#Image as binary) as nvarchar(max));
I used a nvarchar variable to convert it from outside then use it inside og execution command
Declare #txtImage nvarchar(max);
if #Image IS NULL
Begin Set #txtImage = 'NULL'; END
Else
Begin Set #txtImage = cast(cast(#Image as binary) as nvarchar(max)); END
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;
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
Hello Every One The Following Error Comes To Me Suddenly When I Changed Simple And Few Thing On My Stored Procedure Code And It Was Working Great And If I Cleared The Updated Code It Works Fine Again So I Do Not Know What Is The Reason
And Here Is The Error "The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction"
Thanks In Advance
ALTER Procedure [dbo].[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
#English Bit = 0 ,
#ID BigInt = NULL Output ,
#Company_ID SmallInt = NULL ,
#Cust_ID NVarChar (20) = NULL ,
#Cust_Cat NVarChar (10) = NULL ,
#Debit_Limit Decimal (18,3) = NULL ,
#From_Date NVarChar (19) = NULL ,
#To_Date NVarChar (19) = NULL ,
#User_ID NVarChar(50) = NULL
As
BEGIN
Create Table #Errors (ErrorNumber int Not Null, ErrorValue nvarchar(300) Collate Arabic_CI_AS Null);
Begin Tran trn_INV_CustDebLim_setupInsert;
Begin Try
Insert Into INV_Cust_Debit_Limit_setup
( Company_ID , Cust_ID , Cust_Cat , Debit_Limit , From_Date , To_Date )
Values
( #Company_ID , #Cust_ID , #Cust_Cat , #Debit_Limit , #From_Date , #To_Date )
set #ID = ##IDENTITY
-- From Here Is New Part --
declare #str nvarchar(50)
IF(#Cust_ID IS NOT NULL)
set #str = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_Cat
set #str += ' xxxxx' +#Debit_Limit + ' xxxx xx' +#From_Date
IF(#English = 1)
BEGIN
IF(#Cust_ID IS NOT NULL)
set #str = 'Credit Limit Added On Customer ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'Credit Limit Added On Customer Category ' + #Cust_Cat
set #str = ' With Value ' +#Debit_Limit + ' From Date ' +#From_Date
END
exec usp_GLApplicationAudit #Company_ID , NULL , 10 , #User_ID , #str ,#ID ,10, NULL , NULL ,NULL ,NULL,NULL,'SAl'
-- To Here Is New Part --
End Try
Begin Catch
Insert #Errors Values(1002,ERROR_MESSAGE());
Goto Finished;
End Catch
-- Return Error Records
Finished:
-- retrieve Errors and commit/Rollbak Trans.
If (Select count(E.ErrorNumber)
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number
Where G.Err_Type=0 ) > 0
Begin
-- The Following are Errors
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Rollback Tran trn_INV_CustDebLim_setupInsert;
End
Else
Begin
-- The Following Not Errors They are Warnings or Information
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Commit Tran trn_INV_CustDebLim_setupInsert;
End
DROP Table #Errors;
END
In the CATCH code you must check the state of XACT_STATE() and act accordingly. For a procedure template that handles transactions and try/catch blocks correctly see Exception Handling and Nested Transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
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 usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end