TSQL Transaction behaviour with disconnected queries - dynamic SQL - sql-server

I have the below stored procedure, that dynamically calls a list of stored procedures. It's been in place for a few months and has been running fine (unfortunately my access to the server is pretty restricted so couldn't manage this any other way)
Alter Proc [Process].[UspLoad_LoadController]
(
#HoursBetweenEachRun Int
)
As
Begin
--find all procedures that need to be updated
Create Table [#ProcsToRun]
(
[PID] Int Identity(1 , 1)
, [SchemaName] Varchar(150)
, [ProcName] Varchar(150)
);
Insert [#ProcsToRun]
( [SchemaName]
, [ProcName]
)
Select [s].[name]
, [p].[name]
From [sys].[procedures] [p]
Left Join [sys].[schemas] [s]
On [s].[schema_id] = [p].[schema_id]
Where [s].[name] = 'Process'
And [p].[name] Like 'UspUpdate%';
Declare #MaxProcs Int
, #CurrentProc Int = 1;
Select #MaxProcs = Max([PID])
From [#ProcsToRun];
Declare #SQL Varchar(Max)
, #SchemaName sysname
, #ProcName sysname;
--run through each procedure, not caring if the count changes and only updating if there have been more than 23 hours since the last run
While #CurrentProc <= #MaxProcs
Begin
Select #SchemaName = [SchemaName]
, #ProcName = [ProcName]
From [#ProcsToRun]
Where [PID] = #CurrentProc;
Select #SQL = #SchemaName + '.' + #ProcName
+ ' #PrevCheck = 0,#HoursBetweenUpdates = '
+ Cast(#HoursBetweenEachRun As Varchar(5));
Exec (#SQL);
Set #CurrentProc = #CurrentProc + 1;
End;
End;
Go
However, the environment this is running in occasionally suffers from communications errors, with the query being cancelled whilst it is still executing.
My question is - can I wrap the entire procedure with a transaction statement and if I can what would happen in the event of the query being terminated early?
BEGIN Tran Test
Exec [Process].[UspLoad_LoadController] #HoursBetweenEachRun = 1;
COMMIT TRANSACTION Test
What I want to happen would be for the transaction to be rolled back - would this cater for this?

Yes it works,but you might have to see how many stored procs you have and impact of rollback.Normally you can use Set XACT_ABORT ON inside stored proc,but due to dynamic SQL,it wont have any effect..
Sample demo on how to wrap your proc
begin try
begin tran
exec usp_main
commit
end try
begin catch
rollback
end catch
some tests i did on trying to use XACT_ABORT with out any success.but wrapping your main proc in a tran and rolling back when any error occurs,rollback all stored procs too.
create table test2
(
id int)
create table test3
(
id int)
create proc usp_test2
as
begin
insert into test2
select 1
end
alter proc usp_test3
as
begin
insert into test3
select 1/0
end
alter proc usp_main
as
begin
set xact_abort on
declare #sql1 nvarchar(2000)
set #sql1='exec usp_test2'
declare #sql2 nvarchar(2000)
set #sql2='exec usp_test3'
exec (#sql1)
exec(#sql2)
end

Related

Executing T-SQL query from table

I have the following stored procedure:
CREATE PROCEDURE MergeTable #TableName VARCHAR(256)
AS
BEGIN
DECLARE #Test VARCHAR(max)
SET #Test = (SELECT Query
FROM dbo.QueryMergeDWH
WHERE SourceTableName = #TableName)
EXEC #Test
END
GO
EXEC MergeTable #TableName = 'SGPREINVOICE'
The table dbo.QueryMergeDWH holds queries for different tables I want to run using the stored procedure. The variable #Test holds the query (a MERGE statement) for the table 'SGPREINVOICE' in this case.
However, when I run this code I get the following error:
Msg 203, Level 16, State 2, Procedure MergeTable, Line 11 [Batch Start Line 19]
The name 'MERGE dwh.SGPREINVOICE t
USING stg.SGPREINVOICE s
ON t.VOUCHER = s.VOUCHER
WHEN NOT MATCHED
THEN INSERT VALUES (BATCHNAME, COMPANYNR, BUDGETHOUDER, VENDORNR, BANKACCOUNTID, [DESCRIPTION], EXTERNALINVOICENR, TAXVERLEGD, INVOICEDATE, BOOKINGDATE, INVOICEAMOUNTDEB, INVOICEAMOUNTCRED, SGAMOUNTHIGH, SGAMOUNTLOW, SGAMOUNTNOTAX, SGTAXAMOUNTLOW, SGTAXAMOUNTHIGH, SGTAXTTV, G_ACCOUNT, G_AMOUNT, INVOICETYPE, SGAUTOINCASSO, SGIMPORTPROCESSID, SGINVOICEBLOCKED, SGCOMPANYNRORIG, SGVENDORNRORIG, SGBANKACCOUNTIDORIG, SGG_ACCOUNTORIG, SGPOSTBUSNR, SGADDRESS, SGHUISNR, SGHUISNRTOEV, SGPOSTCODE, SGWOONPL, SGBTWNR, SGBEDRIJFSNM, SGINVOICEAMOUNTORIG, V' is not a valid identifier.
It seems like the variable #Test does not contain the entire statement but only part of it. How can I solve this problem?
The actual reason for this error is the difference between EXEC #Test (to execute a stored procedure or function) and EXEC (#Test) (to execute a dynamic statement). Note, that you may use sp_executesql to execute a dynamically generated statement:
CREATE PROCEDURE MergeTable #TableName VARCHAR(256)
AS
BEGIN
DECLARE #Test VARCHAR(max)
SELECT #Test = Query
FROM dbo.QueryMergeDWH
WHERE SourceTableName = #TableName
EXEC (#Test)
-- Or using sp_executesql
-- DECLARE #err int
-- EXEC #err = sp_executesql #Test
-- IF #err <> 0 PRINT 'Error'
END
GO
EXEC MergeTable #TableName = 'SGPREINVOICE'

Will all transaction in this stored procedure be rolled back

I have created a stored procedure (shown below) in SQL Server and tried to include a Rollback Transaction as I need to have a "stored procedure that has a transaction around it, so that if/when it fails all inserts will be rolled back."
I am unsure if this work or not, or will work, I cannot test yet as only developing locally, but wondered if someone wouldn't mind looking over the Rollback Transaction part of the stored procedure and advise if on the right path?
USE AutomatedTesting
GO
ALTER PROCEDURE [dbo].[spInsertTestCases]
(#AddedTFS INT,
#Scenario NVARCHAR(500),
#TargetTableName NVARCHAR(100),
#TargetFieldName NVARCHAR(100),
#ExpectedResult NVARCHAR(100),
#TargetTableDBName NVARCHAR(100),
#TargetTableSchema NVARCHAR(100),
#TargetFieldIsDateTime NVARCHAR(1),
#TestCaseIdentifiers dbo.TestCaseIdentifiers READONLY ) -- can only be READONLY. meaning you cannot amend the param
/* #TestCaseIdentifiers var will be prepopulated
TestDataIdentifiersNEW is a custom data type which has fields (TestSequence ColumnName ColumnValue IsAlphaNumeric)
so stored procedure is called like:
EXEC [dbo].[spTest_UserDefinedDatatype] 'param1','param2' #temp_testdata
#temp_testdata will already be defined and popualted(INSERT INTO ) before exec to add in 1 to many rows.
for example:
ColumnName ColumnValue
PATIENTID 123456
SOURCESYS PAS
in simple terms above EXEC is:
EXEC [dbo].[spTest_UserDefinedDatatype] 'param1','param2' 'PATIENTID 123456'
'SOURCESYS PAS'
*/
AS
BEGIN TRY
BEGIN TRANSACTION
BEGIN
--DECLARE #TableNameUpdate SYSNAME = #TargetTableName
--DECLARE #CDI SYSNAME = REPLACE(#TargetTableName,'CDO','CDI') -- so if targettablename param is CDO then swap it to CDI. why?
DECLARE #sql VARCHAR(MAX) = ' INSERT INTO [dbo].[TestCasesIdentifier] ([TestCaseId], [TestCaseSequence], [FieldName], [FieldValue], [AlphaNumeric]) VALUES '
DECLARE #i INT = 1
DECLARE #TableNameUpdate SYSNAME = #TargetTableName
DECLARE #CDI SYSNAME = REPLACE(#TargetTableName,'CDO','CDI')
DECLARE #ColName SYSNAME
DECLARE #Ret NVARCHAR(256)
DECLARE #sql2 NVARCHAR(MAX)
DECLARE #TestCaseID INT = -1 --does this need default variable?
DECLARE #ErrorCode INT = ##error
DECLARE #TestSequence INT
DECLARE #ColumnName VARCHAR(100)
DECLARE #ColumnValue VARCHAR(100)
DECLARE #IsAlphaNumeric BIT
DECLARE #TableTestSequence INT = ISNULL((SELECT MAX([TableTestSequence]) + 1 FROM TestCases WHERE #TargetTableName = [TargetTableName]), 1)
-- INSERT into TestCases. 1 record
-- An assumption that a number of fields will have defaults on them - if not, extra fields will need adding
INSERT INTO [dbo].[TestCases] ([AddedTFS], [TableTestSequence], [Scenario],
[TargetTableName], [TargetFieldName], [ExpectedResult],
[TargetTableDBName], [TargetTableSchema], [TargetFieldIsDateTime])
VALUES (#AddedTFS, -- AddedTFS (The TFS Number of the Development carried out)
ISNULL((SELECT MAX([TableTestSequence]) + 1 -- TableTestSequence (Generates the next Sequence Number for a Table)
FROM TestCases -- if table doesnt exist in TestCases then sets to 1
WHERE #TargetTableName = [TargetTableName]), 1),
#Scenario, -- Scenario (A description of the scenario use GIVEN and WHERE)
#TargetTableName, -- TargetTableName (References the Target Table entered at the top of this SQL - SET #TableName = 'CDO_APC_ELECTIVE_ADMISSION_LIST')
#TargetFieldName, -- TargetFieldName (The Field in which we want to test)
#ExpectedResult, -- ExpectedResult (The expected output/result of the field in which we want to test)
#TargetTableDBName, -- The DB to be used
#TargetTableSchema, -- the schema to be used
#TargetFieldIsDateTime) ---- 1 = Yes, 0 = No (Is Target field a datetime field)
-- Grab the identity value just generated by the last statement and the last error code generated
-- in order to reference TestCases PK when adding to TestCaseIdentifiers
SELECT #TestCaseID = SCOPE_IDENTITY(), #ErrorCode = ##error
IF #ErrorCode = 0 --OR #TestCaseID <> -1 -- #ErrorCode <> 0 if error then back out testcases INSERT? surely should use BEGIN/ROLLBACK tran
--IF #ErrorCode = 0 OR #TestCaseID <> -1
-- If there was no error creating the TestCase record, create the records for the WHERE clause
BEGIN
/*
rollback insert if no matching records
rollback insert if SQL returns more than 1 record
return error message to user
*/
SELECT
ic.index_column_id, c.name
INTO #tmp
FROM sys.indexes i
JOIN sys.index_columns ic ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
JOIN sys.columns c ON c.column_id = ic.column_id
AND c.object_id = ic.object_id
JOIN sys.tables t ON c.object_id = t.object_id
WHERE t.name = #CDI
AND i.is_primary_key = 1
IF (SELECT COUNT(*) FROM #TestCaseIdentifiers) = 0
--IF #PKValues IS NULL
BEGIN
WHILE #i <= (SELECT COUNT(*) FROM #tmp)
BEGIN
SELECT #ColName = [name]
FROM #tmp
WHERE index_column_id = #i
-- if #expectedvalue IS NULL
SET #sql2 = 'SELECT TOP 1 #RetvalOut = ' + QUOTENAME(#ColName) + ' FROM ' + QUOTENAME(#CDI) + ' ORDER BY NEWID()'
-- else
-- SET #sql2 = ''
EXECUTE sp_executesql #command = #sql2, #ParmDefinition = N'#RetvalOut NVARCHAR(MAX) OUTPUT', #retvalOut = #Ret OUTPUT
SET #sql += '(' + CONVERT(VARCHAR(100),#TestCaseID) + ',' + CONVERT(VARCHAR(10),#i) + ',''' + #ColName + ''',''' + #Ret + ''',1),'
SET #i+=1
SELECT #sql = REVERSE(SUBSTRING(REVERSE(#sql),2,8000))
PRINT #sql
EXEC #sql
END
END
ELSE
BEGIN
--PRINT 'got here'
DECLARE csr_TestCaseIdentifierInsert CURSOR FOR
SELECT [TestSequence],[ColumnName],[ColumnValue],[IsAlphaNumeric]
FROM #TestCaseIdentifiers
ORDER BY [TestSequence]
OPEN csr_TestCaseIdentifierInsert
FETCH NEXT FROM csr_TestCaseIdentifierInsert INTO #TestSequence, #ColumnName, #ColumnValue, #IsAlphaNumeric
WHILE ##fetch_status = 0
BEGIN
INSERT INTO [dbo].[TestCasesIdentifier]
([TestCaseId],
[TestCaseSequence],
[FieldName],
[FieldValue],
[AlphaNumeric])
VALUES
(#TestCaseID, #TestSequence, #ColumnName, #ColumnValue,#IsAlphaNumeric)
FETCH NEXT FROM csr_TestCaseIdentifierInsert INTO #TestSequence, #ColumnName, #ColumnValue, #IsAlphaNumeric
END
CLOSE csr_TestCaseIdentifierInsert
DEALLOCATE csr_TestCaseIdentifierInsert
END -- loop to add records to testcasesidentifier
END
END
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = 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, #ErrorSeverity, #ErrorState);
END CATCH
You are almost there. I usually wrap the stored proc code within a BEGIN..END block as well, then next comes the most important part: you must add SET XACT_ABORT ON; before your TRY..CATCH and BEGIN TRAN, as SQL Server defaults the XACT_ABORT to OFF. Otherwise not everything will be rolled back.
Example setup:
CREATE PROCEDURE dbo.uspMyTestProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Do your magic stuff here before committing...
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
-- Add extra error logging here if you want...
END CATCH;
END;
GO
Also, if you want to add a possible stacktrace if you are using nested procedures et cetera you might want to consider using a generic error handler à la Erland Sommerskog. We adapted this approach completely. See for more details How to handle Transaction in Nested procedure in SQL server?

Deadlock in SQL Server

I'm facing deadlock
was deadlocked on lock resources with another process and has been
chosen as the deadlock victim.
problem In SQL-Server as i'm inserting data in database by picking max id against a specific column then add a increment got the value against which record will be inserted.
i'm calling a procedure as code mentioned below:
CREATE
PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #MaxID BIGINT;
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN
BEGIN TRAN
SET #MaxID = (
SELECT Col_Counter
FROM Maintenance_Counter WITH (XLOCK, ROWLOCK)
WHERE COL_NAME = #Col_Name
)
UPDATE Maintenance_Counter
SET Col_Counter = #MaxID + 1
WHERE COL_NAME = #Col_Name
COMMIT
END
SELECT (
CONVERT(
VARCHAR,
(
SELECT office_id
FROM Maintenance
)
) + '' + CONVERT(VARCHAR, (#MaxID))
) AS MaxID
END
any one help me out .....
As Marc already answered, use SEQUENCE. It's available in all supported versions of SQL Server, ie 2012 and later. The only reason to avoid it is targeting an unsupported version like 2008.
In this case, you can set the counter variable in the same statement you update the counter value. This way, you don't need any transactions or locks, eg:
declare #counterValue bigint
UPDATE Maintenance_Counter
SET Col_Counter = Col_Counter + 1 , #counterValue=Col_Counter+1
WHERE COL_NAME = #Col_Name
select #counterValue
Yo can use sequences to generate incremental values avoiding any blocking.
I have adapted my own Counter Generator to be a direct replacement for yours. It creates dynamically the SQL statements to manage sequences, if a Sequence doesn't exist for the value we are looking for, it creates it.
ALTER PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
declare #Value bigint;
declare #SQL nvarchar(64);
BEGIN
if not exists(select * from sys.objects where object_id = object_id(N'dbo.MY_SEQUENCES_' + #Col_Name) and type = 'SO')
begin
set #SQL = N'create sequence dbo.MY_SEQUENCES_' + #Col_Name + ' as bigint start with 1';
exec (#SQL);
end
set #SQL = N'set #Value = next value for dbo.MY_SEQUENCES_' + #Col_Name;
exec sp_executesql #SQL, N'#Value bigint out', #Value = #Value out;
select #Value ;
END
The only inconvenience is that your values can get gaps within (because you could have retrieved a value but finally not used it). This is not a problem on my tables, but you have to consider it.

How to pass a view name to a stored procedure in SQL Server 2014

CREATE PROCEDURE [dbo].[sp_HeatMap_Paper]
#Grade varchar(150)=NULL,
#Site varchar(250)=NULL,
#TRef varchar(15)=NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #uregref varchar(50), #regTID varchar(8),
#testValue varchar(80), #testResultID int,
#lowerL1 varchar(20), #upperL1 varchar(20),
#lowerL2 varchar(20), #upperL2 varchar(20)
BEGIN TRANSACTION
BEGIN TRY
DELETE FROM HeatMap;
select top 1 #uregref = URegRef from NA_PAPER_HEAT_MAP where RSDESCRIPTION= #Grade and BOX_PLANT1= #Site;
select #regTID = RegTID from REGKEY where URegRef = #uregref;
select #testValue=TestResult,#testResultID=Result_ID from RESULTDATA where RegTID=#regTID and TRef=#TRef;
SELECT #lowerL1=Lower, #upperL1=Upper from ResultLimit WHERE Priority = 1 and Result_Id=#testResultID;
SELECT #lowerL2=Lower, #upperL2=Upper from ResultLimit WHERE Priority = 2 and Result_Id=#testResultID;
Insert into HeatMap (Grade,Site,TestValue,TRef,LowerLimitL1,UpperLimitL1,LowerLimitL2,UpperLimitL2)
values (#Grade,#Site,#testValue,#TRef,#lowerL1,#upperL1,#lowerL2,#upperL2)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
Return Error_Message()
END CATCH
END
GO
I want to pass a view name into this stored procedure, here 'NA_PAPER_HEAT_MAP' is the view instead of this I want to pass a parameter #viewName
You can build dynamic SQL and execute it using sys.sp_executesql to execute it.
I'll give you an example for how to use it.
CREATE PROCEDURE usp_selectView
#id INT,
#viewName NVARCHAR(1000)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX), #paramDef NVARCHAR(MAX);
-- build dynamic SQL
-- you can build whatever SQL you want. This is just an example
-- make sure you sanitize #viewName to avoid SQL injection attack
SET #sql = 'SELECT * FROM ' + #viewName + ' WHERE Id = #selectedId';
-- dynamic SQL parameter definition
SET #paramDef = '#selectedId INT';
-- here, execute the dynamic SQL
EXEC sys.sp_executesql #sql, #paramDef, #selectedId = #id
END

SQL SERVER : How to execute an SP specifed number of time without using loop

I have a scenario wherein i have to execute an SP for specified number of time(number of execution will be mentioned by user) without using loop.
My SP is setting an OUTPUT variable of varchar type. I am willing to insert the output of my SP into a temp table and use it for further processing.
I am unable to modify this SP into function as it contain an Update statement.
Kindly suggest if we can do so without loop.
Whit this solution you do not need an output; #res is your result directly set in a temp table.
CREATE PROCEDURE [dbo].[myStoredProc]
#Counter int,
#params nvarchar(64),
#CreateTable bit
AS
DECLARE #res varchar(64)
IF #CreateTable = 1
BEGIN
IF EXISTS (SELECT 1 FROM dbo.sysobjects WHERE id = object_id(N'[#tempTable]'))
DROP TABLE #tempTable
CREATE TABLE #tempTable ([Res] [nvarchar] (64))
END
SET #res = CONVERT(varchar(64), #Counter)
SET #Counter = #Counter - 1
IF #Counter > 0
exec myStoredProc #Counter, #params, 0
INSERT #tempTable VALUES (#res)
IF #CreateTable = 1
BEGIN
SELECT * FROM #tempTable
DROP TABLE #tempTable
END
GO
DECLARE #o varchar(64)
exec [myStoredProc] 5, '', 1

Resources