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;
Related
This question already has answers here:
Stored procedure hangs seemingly without explanation
(8 answers)
Closed 1 year ago.
I have this T-SQL that works just fine and it executes about 30 seconds.
DECLARE #BatchSize int = 1000,
#TransactionInterval tinyint = 5,
#MaxToDelete int = 10000,
#FactDeleted int = 0;
DECLARE #counter int = 1;
DECLARE #s datetime,
#e datetime,
#r int = 1;
SET #e = '20200606';
SELECT
#s = MIN(AtTime)
FROM
dbo.DevicePositions;
BEGIN TRANSACTION;
WHILE (#r > 0)
BEGIN
IF #r % #TransactionInterval = 1
BEGIN
COMMIT TRANSACTION;
BEGIN TRANSACTION;
END
DELETE TOP (#BatchSize)
FROM DevicePositions
WHERE AtTime >= #s
AND AtTime <= #e;
SET #FactDeleted = #FactDeleted + #BatchSize;
SET #r = ##ROWCOUNT;
SET #counter = #counter + 1;
IF #FactDeleted >= #MaxToDelete
BREAK;
END
IF ##TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION;
IF #counter % 10 = 0 -- or maybe 100 or 1000
BEGIN CHECKPOINT;
END
END
GO
SELECT
A.Records
FROM
(SELECT
OBJECT_NAME(object_id) as ID,
SUM(row_count) AS Records
FROM
sys.dm_db_partition_stats
WHERE
object_id = OBJECT_ID('DevicePositions')
AND index_id < 2
GROUP BY
OBJECT_NAME(object_id)) A
I converted this code into a stored procedure and it won't complete so it runs forever.
The stored procedure:
ALTER PROCEDURE [dbo].[DeleteOldDevicePositions]
#MaxToDelete int , -- Max amount of records to delete
#BatchSize int , -- it is
#ToEndDate datetime -- Delete until this datetime
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TransactionInterval tinyint = 5, #FactDeleted int = 0;
DECLARE #counter int = 1;
DECLARE #s datetime, #e datetime, #r int = 1;
SELECT #s = MIN(AtTime) FROM dbo.DevicePositions;
BEGIN TRANSACTION;
WHILE (#r > 0)
BEGIN
IF #r % #TransactionInterval = 1
BEGIN
COMMIT TRANSACTION;
BEGIN TRANSACTION;
END
DELETE TOP (#BatchSize) FROM DevicePositions WHERE AtTime >= #s AND AtTime <= #ToEndDate;
SET #FactDeleted = #FactDeleted +#BatchSize;
SET #r = ##ROWCOUNT;
SET #counter = #counter + 1;
IF #FactDeleted >= #MaxToDelete
BREAK;
END
IF ##TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION;
IF #counter % 10 = 0 -- or maybe 100 or 1000
BEGIN
CHECKPOINT;
END
END
SELECT A.Records FROM (
SELECT OBJECT_NAME(object_id) as ID, SUM(row_count) AS Records FROM sys.dm_db_partition_stats WHERE
object_id = OBJECT_ID('DevicePositions') AND index_id < 2
GROUP BY OBJECT_NAME(object_id) ) A
END
And I start it like
EXEC [dbo].[DeleteOldDevicePositions] 10000, 1000, '20200606'
So it starts and has no end.
What did I miss?
Thank you!
Thanks to Stored procedure hangs seemingly without explanation
I have used http://whoisactive.com/ to see that there is a rollback on that table.
So I used
exec sp_who
KILL 53 WITH STATUSONLY;
And see that have to wait until it will be done.
May be Parameter Sniffing is one of the reason for this Issue. Please have a look on the below Link.
https://blog.sqlauthority.com/2019/12/19/sql-server-parameter-sniffing-simplest-example/
I have very big dataset about 40000000 items of data. I am trying to load this to a new table. Since the dataset is really big, I am trying to load them in batch so the log transaction would not be full. I am using the query below but still have issue with log transaction being full.
Is there a way to commit the transition after each batch so that it wont be saved in log and will go for next batch?
Please let me know if there is a way for me to solve this issue.
And SSIS is not an option for me currently.
DECLARE #I BIGINT;
DECLARE #icnt int;
DECLARE #n INT, #flag INT;
SELECT #icnt = (SELECT COUNT(*) FROM table1) A
PRINT #ICNT
BEGIN
SELECT #I = 0
WHILE #I <=#icnt
BEGIN
SELECT #n = 0
SELECT #flag = 1
BEGIN TRANSACTION
WHILE #flag != 0
BEGIN
;WITH A AS
(
SELECT
a.*,
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS batchnum
FROM
table1
)
INSERT INTO table2 (*)
SELECT *
FROM A
WHERE batchnum >= #I AND batchnum <#I + 10000000
SELECT #I = #I + 10000000
SELECT #n = #n + 10000000
SELECT #flag = CASE WHEN #n >= 50000000 THEN 0
WHEN #I >= #icnt THEN 0 ELSE 1 END
PRINT #I
END
COMMIT TRANSACTION
END
END
I wrote a SQL procedure that inserts data into two tables. I started with begin transaction but some data violate constraint of second table and in the end data are added into first table, but not in second. What is wrong with my code?
create procedure [dbo].[procAddConference]
#Conf_name varchar(50),
#Begin_date date,
#End_date date,
#Price money,
#Student_disc numeric(3,2),
#Limit int,
#Disc numeric(3,2),
#Until int
as
set nocount on
begin transaction;
begin try
IF (#Begin_date > #End_date or #Limit < 0 or #Disc <0 or #Disc >1 or #Student_disc < 0 or #Student_disc > 1)
BEGIN
; throw 52000, 'Wrong data', 1
END
insert into Conferences (ConferenceName, BeginDate, EndDate, Price, StudentDiscount)
values (#Conf_name, #Begin_date, #End_date, #Price, #Student_disc)
IF ##ERROR <> 0
begin
RAISERROR('Error, transaction not completed!',16,-1)
rollback transaction;
end
declare #confID INT
set #confID = ##IDENTITY
declare #duration int
declare #iterator int
set #duration = DATEDIFF(dd, #Begin_date, #End_date)
set #iterator = 0
while #iterator <= #duration
begin
insert into ConferenceDay (ConferenceID, Date, Limit)
values (#confID, cast(DATEADD(dd, #iterator, #Begin_date) as date), #Limit)
IF ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback transaction;
RAISERROR('Error, transaction not completed!',16,-1)
end
set #iterator = #iterator + 1
end
commit transaction;
END TRY
BEGIN CATCH
DECLARE #errorMsg nvarchar (2048) = 'Cannot add conference . Error message : ' + ERROR_MESSAGE () ;
; THROW 52000 , #errorMsg ,1
rollback transaction;
END CATCH
END
I want to write some thing like that query:
BEGIN
DECLARE #Unaloacte varchar(200);
DECLARE #Total int;
DECLARE #Flag int;
SET #Unaloacte =( Select count(PD.PropertyDetailId) from PropertyDetail AS PD join
SiteDetail AS SD ON PD.SiteDetailId=SD.SiteDetailId Where PD.CustomerId<1 and PD.SiteDetailId=27);
SET #Total= (Select count(PropertyDetailId) as Total_Count from PropertyDetail where SiteDetailId=27) ;
if( #Unaloacte = #Total)
Delete something and display message
print"Delete";
else
print"Not able to delete"
END
I hope you understand my problem.
You can try like this:
DECLARE #Msg VARCHAR(200)
if( #Unaloacte = #Total)
BEGIN
BEGIN TRAN
DELETE something
SELECT #Msg = CAST(##ROWCOUNT AS VARCHAR(10)) + ' are deleted'
RAISERROR (#Msg, 0, 1) WITH NOWAIT
COMMIT
END
ELSE
BEGIN
SELECT 'Not able to delete'
END
Also I would recommend you to use BEGIN TRAN and COMMIT if you are going to use in Production.
You can check this by USING ##ROWCOUNT and SET NOCOUNT ON
SET NOCOUNT ON
DELETE FROM TableName
IF ##ROWCOUNT > 0
PRINT 'Record Deleted'
ELSE
PRINT 'Record Not Deleted'
Here SET NOCOUNT ON is used since we dont want to see number of rows affected message.
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