Rare is the time do we have to delete the data row by row. But…. Recently I needed to delete some xml fields in a database as they were no longer required. It was a massive amount of xml; the table itself was 50,000 rows and nearly 300gb, and I needed to delete roughly 9000 rows.

I decided to delete a row at a time. Whilst this contradicts how sql works, I did not want to fill up the log with one massive delete. And as the database has its log backed up every 15 minutes, this will ensure that there is free space in the log for new deletes. So we’ll delete a row every 2 minutes and check that there is space in the log before deleting. If the log is looking full we’ll check again in 5 minutes until there is space to run another delete. So I can run the script and leave it to run knowing that the script won’t kill the database.

The script is below with an example using AdventureWorksDW2014.

SET NOCOUNT ON
SET XACT_ABORT ON

DECLARE @id NVARCHAR (20)
DECLARE @pct FLOAT
DECLARE @s VARCHAR(1024)
DECLARE @startTime DATETIME

DECLARE cur CURSOR READ_ONLY FOR
SELECT X.SalesOrderNumber
FROM [dbo].[FactInternetSales] X
WHERE X.CurrencyKey =
(SELECT CurrencyKey
FROM [dbo].[DimCurrency]
WHERE CurrencyAlternateKey = 'FRF')
OPEN cur

FETCH NEXT FROM cur INTO @id
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
-- Get how much transaction log space there is
SET @startTime = GETDATE()
SELECT @pct = convert(decimal(38,8),round(100*(sf.size- CAST(fileproperty(sf.name,'SpaceUsed') as float))/128.000,8)/(sf.size/128.000))
from dbo.sysfiles sf
where sf.fileid= 2
SELECT @s = (CONVERT( VARCHAR(24), @startTime, 121)) + ' : ' + CAST(@id AS VARCHAR) + ' : Txn used: ' + CAST(@pct AS VARCHAR)
RAISERROR(@s, 0, 1) WITH NOWAIT

-- if not enough left then wait until there is
WHILE @pct < 25
BEGIN
RAISERROR('Not enough txn log space', 0, 1) WITH NOWAIT;
WAITFOR DELAY '00:05:00';
SELECT @pct = convert(decimal(38,8),round(100*(sf.size- CAST(fileproperty(sf.name,'SpaceUsed') as float))/128.000,8)/(sf.size/128.000))
from dbo.sysfiles sf
where sf.fileid= 2
END

-- do the delete
-- do the delete
DELETE
FROM [dbo].[FactInternetSalesReason]
WHERE [dbo].[FactInternetSalesReason].SalesOrderNumber = @id
DELETE
FROM [dbo].[FactInternetSales]
WHERE [dbo].[FactInternetSales].SalesOrderNumber = @id
SELECT @s = @ID +' deleted'
RAISERROR(@s, 0, 1) WITH NOWAIT;
-- small delay to take it easy
WAITFOR DELAY '00:02:00';

END
FETCH NEXT FROM cur INTO @id
END

CLOSE cur
DEALLOCATE cur
GO