Why Use Paginated Deletions?
Deleting large amounts of records from a table at once may:
-
Balloon the SQL Transaction Log file.
-
Cause disk space issues.
-
Strain system performance.
Paginated deletions divide the workload into manageable chunks, reducing resource consumption and ensuring stable performance.
Step 1: Creating a Sample Table
CREATE TABLE [Logs] (
[LogId] INTEGER NOT NULL IDENTITY(1, 1),
[Text] VARCHAR(MAX) NULL,
[Severity] VARCHAR(7) NULL,
[Created] DATETIME,
PRIMARY KEY ([LogId])
);
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 1','Error',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 2','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 3','Info',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 4','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 5','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 6','Error',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 7','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 8','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 9','Info',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 10','Info',GETDATE())
Step 2: Preparing for Deletion
Set up the necessary variables and temporary table:
DECLARE @pageSize INTEGER = 2; -- Records per batch
DECLARE @tempLogIdWithPageIndexTable TABLE (
LogId INT,
PageIndex INT
);
Assign page indices to the records based on the batch size:
INSERT INTO @tempLogIdWithPageIndexTable (LogId, PageIndex)
SELECT LogId,
CEILING(CAST(ROW_NUMBER() OVER(ORDER BY LogId) AS DECIMAL) / CAST(@pageSize AS DECIMAL)) AS PageIndex
FROM [Logs];
Calculate total pages:
DECLARE @totalCount INTEGER;
DECLARE @pageCount INTEGER;
SELECT @totalCount = COUNT(1) FROM @tempLogIdWithPageIndexTable;
SET @pageCount = CEILING(CAST(@totalCount AS DECIMAL) / CAST(@pageSize AS DECIMAL));
Step 3: Paginated Deletion Logic
Perform the deletion page by page:
DECLARE @pageIndex INTEGER = 1;
WHILE (@pageIndex <= @pageCount)
BEGIN
DELETE FROM [Logs]
WHERE LogId IN (
SELECT LogId FROM @tempLogIdWithPageIndexTable WHERE PageIndex = @pageIndex
);
PRINT 'Processed page ' + CAST(@pageIndex AS VARCHAR(MAX)) + '/' + CAST(@pageCount AS VARCHAR(MAX));
SET @pageIndex = @pageIndex + 1;
END
Step 4: Post-Deletion Analysis
Verify the deletion results:
PRINT 'Script finished at : ' + CONVERT(varchar(25), getdate(), 120)
SET @totalCount = (SELECT count(1) FROM dbo.Logs)
-- print the report when the job is done
PRINT 'Number of records after deletion : ' + CAST(@totalCount as VARCHAR(MAX))
Example output:
Total number of records to remove: 10
Page size: 2
Total pages: 5
Script started at : 2020-09-05 15:36:21
(2 rows affected)
Processed page 1/5
(2 rows affected)
Processed page 2/5
(2 rows affected)
Processed page 3/5
(2 rows affected)
Processed page 4/5
(2 rows affected)
Processed page 5/5
Script finished at : 2020-09-05 15:36:21
Number of records after deletion : 0
Complete Script for Paginated Deletions
use [dbName]
/* Removing data from large table in MSSQL using batch */
CREATE TABLE [Logs] (
[LogId] INTEGER NOT NULL IDENTITY(1, 1),
[Text] VARCHAR(MAX) NULL,
[Severity] VARCHAR(7) NULL,
[Created] DATETIME,
PRIMARY KEY ([LogId])
);
GO
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 1','Error',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 2','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 3','Info',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 4','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 5','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 6','Error',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 7','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 8','Warning',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 9','Info',GETDATE())
INSERT INTO [Logs]([Text],[Severity],[Created]) VALUES('Lorem ipsum 10','Info',GETDATE())
SELECT count(1) FROM dbo.Logs;
DECLARE @tempLogIdWithPageIndexTable TABLE
(
LogId INT,
PageIndex INT
)
DECLARE @pageSize INTEGER = 2;
DECLARE @totalCount INTEGER
INSERT INTO @tempLogIdWithPageIndexTable (LogId, PageIndex) (
SELECT LogId,
CEILING(CAST(ROW_NUMBER() OVER(ORDER BY LogId) AS DECIMAL)/CAST(@pageSize AS DECIMAL)) AS PageIndex
FROM [Logs] -- You can add WHERE conditions here
)
-- Get total counts of records to remove
SELECT @totalCount = count(1) FROM @tempLogIdWithPageIndexTable
DECLARE @pageCount INTEGER
SET @pageCount = CEILING(CAST(@totalCount as DECIMAL) / CAST(@pageSize as DECIMAL))
DECLARE @pageIndex INTEGER = 1
-- print the report with metrics before start
PRINT 'Total number of records to remove : ' + CAST(@totalCount as VARCHAR(MAX))
PRINT 'Page size : ' + CAST(@pageSize as VARCHAR(MAX))
PRINT 'Total pages : ' + CAST(@pageCount as VARCHAR(MAX))
PRINT 'Script started at : ' + CONVERT(varchar(25), getdate(), 120)
-- delete records in a loop by paging
WHILE(@pageIndex <= @pageCount)
BEGIN
-- you can delete related entries first using JOIN here
DELETE [dbo].Logs where LogId in
(SELECT LogId FROM @tempLogIdWithPageIndexTable WHERE PageIndex = @pageIndex)
PRINT 'Processed page ' + CAST(@pageIndex as VARCHAR(MAX)) + '/' + CAST(@pageCount as VARCHAR(MAX))
SET @pageIndex = @pageIndex + 1
END
PRINT 'Script finished at : ' + CONVERT(varchar(25), getdate(), 120)
SET @totalCount = (SELECT count(1) FROM dbo.Logs)
-- print the report when the job is done
PRINT 'Number of records after deletion : ' + CAST(@totalCount as VARCHAR(MAX))
SELECT count(1) FROM dbo.Logs;
Remember When Deleting All Records from Table
The fastest way to clear an entire table is to use the TRUNCATE command:
TRUNCATE TABLE [Logs];
Handle Foreign Key Constraints
If TRUNCATE isn’t possible due to foreign key constraints:
-
Drop foreign key constraints.
-
Use TRUNCATE.
-
Recreate foreign key constraints.
Key Takeaways
-
Paginated deletions help manage large datasets efficiently without overwhelming system resources.
-
This method avoids common pitfalls like bloated transaction logs and performance bottlenecks.
-
Use TRUNCATE for faster cleanup when applicable, but handle constraints carefully.
For complete source code, check out the GitHub repository