I have the a Requirement where I have to Delete in batches in sql server , and also track the number of count affected in the end. My Sample Code is as Follows:
Declare #count int
Declare #deletecount int
set #count=0
While(1=1)
BEGIN
BEGIN TRY
BEGIN TRAN
DELETE TOP 1000 FROM --CONDITION
SET #COUNT = #COUNT+##ROWCOUNT
IF (##ROWCOUNT)=0
Break;
COMMIT
END CATCH
BEGIN CATCH
ROLLBACK;
END CATCH
END
set #deletecount=#COUNT
Above Code Works fine, but how to keep track of #deletecount if Rollback happens in one of the batch.
Some potential issues with the code :
while loop never exits/breaks
declare #tbl table(foo bit); --deleting from an empty table
declare #COUNT int;
declare #iteration int = 0;
While(1=1)
BEGIN
--loop safety net
if #iteration >= 100
begin
break --exit after 100 iterations
end
select #iteration = #iteration+1
BEGIN TRY
DELETE TOP (1000) FROM #tbl; --#tbl is empty
SET #COUNT = #COUNT+##ROWCOUNT;-- <-- this always sets ##rowcount = 1
IF (##ROWCOUNT)=0 -- ... so this can never be 0 --> will never exit the loop
Break;
END TRY
BEGIN CATCH
END CATCH
END --end while
select #iteration as iterations;
Open transaction at the end of the batch
declare #tbl table(foo bit); --<-- empty table
declare #COUNT int;
declare #myrowcount int;
declare #iteration int = 0;
While(1=1)
BEGIN
--loop safety net
if #iteration >= 100
begin
break --exit after 100 iterations
end
select #iteration = #iteration+1
BEGIN TRY
BEGIN TRAN --<-- transaction begins on each iteration
DELETE TOP (1000) FROM #tbl; --#tbl is empty
set #myrowcount = ##rowcount;
SET #COUNT = #COUNT+#myrowcount;
IF (#myrowcount)=0
Break; --<-- if this breaks... transaction is neither committed nor rolledback
COMMIT --<-- commited on each iteration, when no error
END TRY
BEGIN CATCH
ROLLBACK --<-- or rolled back on each iteration error
END CATCH
END --end while
select #iteration as iterations, ##trancount as open_transactions;
rollback transaction;
loop keeps trying to delete the same erroneous batch (over and over again) and never exits
create table parent (id int primary key clustered);
create table child (parentid int references parent(id));
go
--some parents
insert into parent(id)
select top (200) row_number() over(order by ##spid)
from sys.all_objects;
--and a child
insert into child(parentid) values (75)
go
declare #COUNT int;
declare #myrowcount int;
declare #errormsg nvarchar(max);
declare #iteration int = 0;
While(1=1)
BEGIN
--loop safety net
if #iteration >= 100
begin
break --exit after 100 iterations
end
select #iteration = #iteration+1
BEGIN TRY
DELETE TOP (10) t --changed the batch size..for the example
from (select top (10000) * from parent order by id) as t;
set #myrowcount = ##rowcount;
SET #COUNT = #COUNT+#myrowcount;
IF (#myrowcount)=0
Break;
END TRY
BEGIN CATCH
select #errormsg = isnull(#errormsg, '') + error_message();
END CATCH
END --end while
select #iteration as iterations, #errormsg as errormsg;
--some parents have been deleted (8th batch kept failing, there is an fk to parent 75)
select *
from parent
go
drop table child
drop table parent
go
You could rectify all the above issues...(last one, to skip erroneous batches being a bit more "difficult"), by implementing logic in the where clause (eg. exclude rows which are referenced etc) according to your model. Trying to implement a straight deletion, without any rules, and skipping failures makes it a bit harder.
Just an example: when a batch fails, after 3 attempts, take another deletion route. Since each deletion is atomic, transactions are not really needed here (besides, if the batch is called from a parent module, a plain ROLLBACK in the batch would "invalidate" any transactions opened in the parent module, before the batch execution)
create table parent (id int primary key clustered);
create table child (parentid int references parent(id));
go
--some parents
insert into parent(id)
select top (200) row_number() over(order by ##spid)
from sys.all_objects;
--and two children
insert into child(parentid) values (75), (115) --, (9), (18) --: add 9 and 18 and nothing gets deleted, alternative route in the example does not work
go
declare #COUNT int;
declare #myrowcount int;
declare #iteration int = 0;
declare #errormsg nvarchar(max);
declare #ierrorcnt int=0;
declare #deletedids table(id int);
declare #lastsuccessfullydeletedid int = 0;
select #COUNT = 0;
While(1=1)
BEGIN
--loop safety net
if #iteration >= 100
begin
break --exit after 100 iterations
end
select #iteration = #iteration+1
BEGIN TRY
--when 10 consecutive errors in a single iteration..just quit the loop
if #ierrorcnt >= 10
begin
break;
end
BEGIN TRAN --<-- transaction begins on each iteration
if #ierrorcnt >= 3 --when 3 consecutive errors in the iteration..try to bypass the current batch
begin
delete top (10) t
output deleted.id into #deletedids(id)
from (select top (10) * from (select top (2*10) * from parent where id > #lastsuccessfullydeletedid order by id) as a order by id desc) as t
select #myrowcount = count(*), #lastsuccessfullydeletedid = max(id)
from #deletedids;
delete from #deletedids;
end
else
begin
DELETE TOP (10) FROM parent where id > #lastsuccessfullydeletedid;
set #myrowcount = ##rowcount;
end
SET #COUNT = #COUNT+#myrowcount;
COMMIT --<-- commited on each iteration, when no error
IF (#myrowcount)=0 and #ierrorcnt = 0
Break;
set #ierrorcnt = 0; --everything ok, set iteration error counter to 0
END TRY
BEGIN CATCH
ROLLBACK --<-- or rolled back on each iteration error
if #ierrorcnt = 0
begin
select #errormsg = isnull(#errormsg, '') +';'+ error_message();
end
set #ierrorcnt = #ierrorcnt + 1; --error, increase the iteration error counter
END CATCH
END --end while
select #iteration as iterations, ##trancount as open_transactions;
select #iteration as iterations, #errormsg as errormsg;
--some parents have been deleted
select * /*2 failed batches, 20 rows left*/
from parent
select #COUNT as [count/deleted] --<--this is also the deleted count
go
drop table child
drop table parent
go
..without any errors, in your original code, #count = #deletedcount
declare #tbl table(foo int); --<-- empty table
insert into #tbl(foo)
select top (100000) row_number() over(order by ##spid)
from sys.all_objects as a
cross join sys.all_objects as b;
--batch 1000, max iterations for the #tbl count
declare #maxiterations int;
select #maxiterations = 1+count(*) / 1000
from #tbl
declare #COUNT int;
declare #deletedcount int;
declare #myrowcount int;
declare #iteration int = 0;
select #COUNT = 0, #deletedcount = 0;
While(1=1)
BEGIN
--loop safety net
if #iteration >= #maxiterations
begin
break --exit after #maxiterations
end
select #iteration = #iteration+1;
BEGIN TRY
BEGIN TRAN
DELETE TOP (1000) FROM #tbl
where foo%5 = 0 ;
set #myrowcount = ##rowcount;
SET #COUNT = #COUNT+#myrowcount;
set #deletedcount = #deletedcount + #myrowcount;
COMMIT
IF #myrowcount=0
Break;
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
END --end while
select #iteration as iterations, ##trancount as open_transactions;
select #count as _count, #deletedcount as deletedcount;
I am getting:
Insert values statement can contain only constant literal values or variable references error
while running the below procedure at insertion in dbo.DB_time_log table.
CREATE PROCEDURE [dbo].[sp_chm_dedupe_import] AS
BEGIN
SET NOCOUNT ON;
DECLARE #StartTime DATETIME;
DECLARE #EndTime DATETIME;
SET #StartTime = GETDATE();
PRINT 'Procedure Started at :'
+ RTRIM(CAST(GETDATE() AS nvarchar(30)));
BEGIN TRAN
BEGIN TRY
INSERT INTO [CHM].[UCIC_PAN_INDIA_TEST_TS]
SELECT
CAST(UCID AS bigint),CAST(UCIC_OLD AS bigint),CAST(HID AS bigint),
CAST(HID_OLD AS bigint),CAST(CID AS VARCHAR(20)),CAST(BU AS VARCHAR(10)),
CAST(TIME_STAMP AS VARCHAR(20))
FROM [dbo].[CHM_EXT_All_India_DL]
OPTION (LABEL = 'CHM_AllIndia_EXT_Data_Load');
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRAN;
PRINT 'ROLLBACK';
END
END CATCH;
IF ##TRANCOUNT >0
BEGIN
PRINT 'COMMIT';
COMMIT TRAN;
END
SET #EndTime = GETDATE();
INSERT INTO dbo.DB_time_log
VALUES
(
'SP_CHM_DEDUPE_IMPORT',
#StartTime,
#EndTime,
GETDATE()
);
PRINT 'Procedure Ended at :'
+ RTRIM(CAST(GETDATE() AS nvarchar(30)));
SET NOCOUNT OFF;
END
GO
I need to improve the performance ofa stored procedure in SQL Server 2014.
Here is my procedure and I want optimise this procedure because this running to slowly.
Can anybody explain with some examples, which kind of query is better over what and in what situation?
CREATE PROCEDURE [dbo].[sp_Customer_SendMoney]
#CustomerId bigint,
#MobileNo nvarchar(15),
#Amount money,
#Comment nvarchar(250),
#PassPhrase nvarchar(50),
#Type varchar(50) = null
AS
BEGIN
DECLARE #returnCode INT
DECLARE #RetVal BIGINT, #CustomerBalance MONEY,
#CustomerMoneyRequestedId BIGINT,
#ToCustomerId BIGINT = 0,
#TransactionId BIGINT, #CustomerAccount BIGINT,
#FromCustomerBalance MONEY = 0,
#ToCustomerBalance MONEY = 0
IF EXISTS (SELECT Id FROM Customer
WHERE Id = #CustomerId AND IsDeleted = 0 AND IsActive = 1)
BEGIN
SELECT
#CustomerBalance = Balance
FROM
Customer
WHERE
Id = #CustomerId AND IsDeleted = 0 AND IsActive = 1
select #ToCustomerId = Id, #CustomerAccount = AccountNo,#ToCustomerBalance=Balance From Customer where convert(nvarchar,DecryptByPassPhrase(#PassPhrase, MobileNo)) = #MobileNo and IsDeleted = 0 and IsActive = 1
if(#ToCustomerId > 0)
begin
if( lower(isnull(#Type,'regular')) <> 'suspention')
begin
set #ToCustomerBalance=#ToCustomerBalance+#Amount
end
END
set #FromCustomerBalance=#CustomerBalance-#Amount
if((#CustomerBalance > 0) and (#CustomerBalance >= #Amount) )
Begin
BEGIN TRAN TxnsenMoney
BEGIN TRY
select #TransactionId = TransactionW2W+1 from MstGenerateTransactionID
where [year]=datepart(yyyy,getdate()) and [month]=DATENAME(month,getdate())
update MstGenerateTransactionID set TransactionW2W= #TransactionId
where [year]=datepart(yyyy,getdate()) and [month]=DATENAME(month,getdate())
--set #TransactionId = CONVERT(bigint,replace(convert(varchar, getdate(),111),'/','') + replace(convert(varchar, getdate(),114),':',''))
IF(#ToCustomerId > 0)
BEGIN
--Update sender Customer
update Customer set Balance = Balance - #Amount where Id = #CustomerId
--Update receiver Customer
if(lower(isnull(#Type,'regular')) <> 'suspention')
begin
update Customer set Balance = Balance + #Amount where Id = #ToCustomerId
end
else
begin
update Customer set SuspentionAccount = isnull(SuspentionAccount,0) + #Amount where Id = #ToCustomerId
end
INSERT INTO [TransactionW2W]
([TransactionId]
,[FromCustomerId]
,[ToCustomerId]
,[MobileNo]
,[Amount]
,[Comments]
,[CreatedOn]
,[FromCustomerBalance]
,[ToCustomerBalance])
VALUES
(#TransactionId
,#CustomerId
,#ToCustomerId
,#MobileNo
,#Amount
,#Comment
,GETDATE()
,#FromCustomerBalance
,#ToCustomerBalance)
End --end IF #ToCustomerId > 0
ELSE
BEGIN
--Update sender Customer
update Customer set Balance = Balance - #Amount where Id = #CustomerId
--print 'ELSE'
INSERT INTO [TransactionW2W]
([TransactionId]
,[FromCustomerId]
,[ToCustomerId]
,[MobileNo]
,[Amount]
,[Comments]
,[CreatedOn]
,[FromCustomerBalance])
VALUES
(#TransactionId
,#CustomerId
,#ToCustomerId
,#MobileNo
,#Amount
,#Comment
,GETDATE()
,#FromCustomerBalance)
INSERT INTO [NewCustomer]
([FromCustomerId]
,[MobileNo]
,[Amount]
,[CreatedOn]
)
VALUES
(#CustomerId
,#MobileNo
,#Amount
,GETDATE()
)
END --end ELSE #ToCustomerId > 0
print #RetVal
IF(##TRANCOUNT >0 )
begin
set #RetVal = #TransactionId
print #RetVal
end
else
begin
RAISERROR('records not executed',16,1)
END
COMMIT TRAN TxnsenMoney
END TRY
BEGIN CATCH
ROLLBACK TRAN TxnsenMoney
set #RetVal = -1
declare #error varchar(max)
set #error= ERROR_MESSAGE()
-- RAISERROR(#error,16,1)
print #error
END CATCH
select #RetVal
End
END
End
CREATE PROCEDURE [dbo].[sp_Customer_SendMoney]
#CustomerId bigint,
#MobileNo nvarchar(15),
#Amount money,
#Comment nvarchar(250),
#PassPhrase nvarchar(50),
#Type varchar(50) = null
AS
Begin
DECLARE #returnCode INT ,#RetVal bigint,#CustomerBalance money, #CustomerMoneyRequestedId bigint,#ToCustomerId bigint = 0,#TransactionId bigint,#CustomerAccount bigint,
#FromCustomerBalance money=0,#ToCustomerBalance money=0
IF EXISTS (select Id from Customer WITH(NOLOCK) where Id = #CustomerId and IsDeleted = 0 and IsActive = 1) Begin
select #CustomerBalance = Balance from Customer WITH(NOLOCK) where Id = #CustomerId and IsDeleted = 0 and IsActive = 1
select #ToCustomerId = Id, #CustomerAccount = AccountNo,#ToCustomerBalance=Balance From Customer WITH(NOLOCK)
where convert(nvarchar,DecryptByPassPhrase(#PassPhrase, MobileNo)) = #MobileNo and IsDeleted = 0 and IsActive = 1
IF(#ToCustomerId > 0)
BEGIN
if( lower(isnull(#Type,'regular')) <> 'suspention')
BEGIN
SET #ToCustomerBalance=#ToCustomerBalance+#Amount
END
END
SET #FromCustomerBalance=#CustomerBalance-#Amount
if((#CustomerBalance > 0) and (#CustomerBalance >= #Amount) )
Begin
BEGIN TRAN TxnsenMoney
BEGIN TRY
SELECT #TransactionId = TransactionW2W+1
FROM MstGenerateTransactionID WITH(NOLOCK)
WHERE [year]=datepart(yyyy,getdate()) and [month]=DATENAME(month,getdate())
update MstGenerateTransactionID set TransactionW2W= #TransactionId
where [year]=YEAR(GETDATE()) and [month]= MONTH(GETDATE())
--set #TransactionId = CONVERT(bigint,replace(convert(varchar, getdate(),111),'/','') + replace(convert(varchar, getdate(),114),':',''))
IF(#ToCustomerId > 0) BEGIN
--Update sender Customer
UPDATE Customer set Balance = Balance - #Amount where Id = #CustomerId
--Update receiver Customer
if(lower(isnull(#Type,'regular')) <> 'suspention') BEGIN
UPDATE Customer set Balance = Balance + #Amount where Id = #ToCustomerId
END ELSE BEGIN
UPDATE Customer set SuspentionAccount = isnull(SuspentionAccount,0) + #Amount where Id = #ToCustomerId
END
INSERT INTO [TransactionW2W]
([TransactionId] ,[FromCustomerId],[ToCustomerId] ,[MobileNo] ,[Amount],[Comments],[CreatedOn],[FromCustomerBalance],[ToCustomerBalance])
SELECT #TransactionId ,#CustomerId ,#ToCustomerId ,#MobileNo ,#Amount ,#Comment,GETDATE(),#FromCustomerBalance ,#ToCustomerBalance
END --end IF #ToCustomerId > 0
ELSE BEGIN
--Update sender Customer
UPDATE Customer set Balance = Balance - #Amount where Id = #CustomerId
--print 'ELSE'
INSERT INTO [TransactionW2W]
([TransactionId] ,[FromCustomerId],[ToCustomerId] ,[MobileNo] ,[Amount] ,[Comments] ,[CreatedOn],[FromCustomerBalance])
SELECT #TransactionId ,#CustomerId ,#ToCustomerId ,#MobileNo ,#Amount ,#Comment ,GETDATE() ,#FromCustomerBalance
INSERT INTO [NewCustomer]
([FromCustomerId] ,[MobileNo] ,[Amount] ,[CreatedOn] )
SELECT #CustomerId,#MobileNo ,#Amount,GETDATE()
END --end ELSE #ToCustomerId > 0
print #RetVal
IF(##TRANCOUNT >0 )BEGIN
set #RetVal = #TransactionId
print #RetVal
END ELSE BEGIN
RAISERROR('records not executed',16,1)
END
COMMIT TRAN TxnsenMoney
END TRY
BEGIN CATCH
ROLLBACK TRAN TxnsenMoney
SET #RetVal = -1
DECLARE #error varchar(max)
SET #error= ERROR_MESSAGE()
-- RAISERROR(#error,16,1)
print #error
END CATCH
select #RetVal
End
END
End
The below SP fails when running parallely in multiple threads.
Tried to use different isolation levels in the SP, but still i face the same error.
Violation of UNIQUE KEY constraint
'AK_MerchantTransactionEnd_MerchantReferenceCode_BankReferenceCode'.
Cannot insert duplicate key in object 'dbo.EpayTransaction'. The
duplicate key value is (20160503171525689435, 20160329221725169, 0).
Table has UNIQUE Constraint for MerchantReferenceCode_BankReferenceCode
CREATE PROCEDURE [dbo].[uspUpdateEpayConfirmation]
#merchantReferenceCode VARCHAR(30) ,
#bankGatewayCode VARCHAR(30) ,
#transactionNumber VARCHAR(100)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
DECLARE #timestamp DATETIME;
SET #timestamp = GETDATE();
IF EXISTS ( SELECT 1
FROM [dbo].EpayTransaction WITH (NOLOCK)
WHERE [dbo].EpayTransaction.[MerchantReferenceCode] = #merchantReferenceCode
)
BEGIN
RETURN 0;
END;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
-- update the epayment transaction information with the merchent reference code
-- updating the status of the transaction
UPDATE [dbo].[CustomerTransaction]
SET [dbo].[CustomerTransaction].[ModifiedBy] = 1 ,
[dbo].[CustomerTransaction].[ModifiedOn] = #timestamp
WHERE [dbo].[CustomerTransaction].[MerchantReferenceCode] = #merchantReferenceCode;
-- adding a record to EpayTransaction table to conclude the transaction is successful
INSERT INTO [dbo].EpayTransaction
( [dbo].EpayTransaction.[BankReferenceCode] ,
[dbo].EpayTransaction.[BankTransactionDate] ,
[dbo].EpayTransaction.[MerchantReferenceCode]
)
VALUES ( #bankGatewayCode ,
#timestamp ,
#merchantReferenceCode
);
COMMIT TRANSACTION;
RETURN 1;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Raise an error with the details of the exception
DECLARE #errMsg NVARCHAR(4000) ,
#errSeverity INT;
SELECT #errMsg = ERROR_MESSAGE() ,
#errSeverity = ERROR_SEVERITY();
RAISERROR(#errMsg, #errSeverity, 1);
END CATCH;
END;
I have 8 tables . One parent and 7 children . Inside the while loop and delete from table one by one.
If any error during the loop all transaction rollback . Is it possible inside the while loop.
Example :
declare #Count int, #intFlag int
begin try
set #Count = (select count(ID) from MyTable where [Date] between getdate()-1 and getdate())
if #Count > 0
begin
set #intFlag = 1
begin transaction
while (#intFlag <= #Count)
begin
update MyTable1
set column1 = 1
where [Date] between getdate()-1 and getdate();
update MyTable2
set column2 = 1
where [Date] between getdate()-1 and getdate();
set #intFlag = #intFlag + 1
end;
commit
end
end try
begin catch
if ##trancount > 0 rollback
end catch
If any error during the processes its roll back all child table transaction
try catch block above the loop like following
BEGIN TRANSACTION
BEGIN TRY
/*
* YOUR LOOP
*/
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH