SQL Server Archive Process Using Filegroups and Table Partitioning
Compatibility: Table partitioning and filegroup-based archiving requires SQL Server Enterprise Edition (or Developer Edition for testing). Available on SQL Server 2005 and later through SQL Server 2025. The ALTER TABLE...SWITCH operation has been available since SQL Server 2005.
- How This Archive Pattern Works
- Key Concepts
- Prerequisites
- Step 1 — Add Filegroups to the Database
- Step 2 — Create the Partition Function
- Step 3 — Create the Partition Scheme
- Step 4 — Create the Main Table on the Partition Scheme
- Step 5 — Create Archive Tables for Each Year
- Step 6 — Switch Partitions to Archive Tables
- Step 7 — Optionally Merge Empty Partitions
- Verifying the Archive
- ALTER TABLE SWITCH Requirements
- Ongoing Maintenance and Adding New Years
- Summary
- References
How This Archive Pattern Works
As tables grow over years, query performance degrades, backup windows grow, and maintenance operations slow down. The standard solution is to archive old data — but naive archiving using INSERT...SELECT...DELETE locks large portions of the table, generates massive transaction log growth, and takes hours to complete.
The filegroup-based partition switching approach solves this. Instead of moving rows, SQL Server simply reassigns a partition’s metadata pointer from the main table to an archive table. The data stays where it is on disk — only the ownership changes. The switch operation completes in milliseconds regardless of how many rows the partition contains.
The key insight: ALTER TABLE...SWITCH is a metadata operation — it does not move data on disk. Switching a partition containing 500 million rows takes the same time as switching one containing 500 rows. It acquires and releases a schema modification lock in under a second.
Key Concepts
| Concept | What It Is | Role in This Pattern |
|---|---|---|
| Filegroup | A logical group of database files (.ndf) | Each year’s archive data lives on its own filegroup — can be backed up or made read-only independently |
| Partition Function | Defines how rows are distributed across partitions based on a column value | Splits the main table by year using DateColumn |
| Partition Scheme | Maps each partition from the function to a filegroup | Routes each year’s partition to the corresponding archive filegroup |
| SWITCH | Metadata operation that reassigns a partition to a different table | Instantly moves a year’s data from the main table to the archive table |
| RANGE LEFT | Partition boundary inclusive on the left side | A boundary of '2023-01-01' means dates up to and including 2022-12-31 are in partition 1 |
RANGE LEFT Partition Boundaries Visualised
With RANGE LEFT and boundary values of '2022-01-01', '2023-01-01', '2024-01-01', '2025-01-01', '2026-01-01':
2022-01-01
(≤ 2021-12-31)
2022-01-01
to 2022-12-31
2023-01-01
to 2023-12-31
2024-01-01
to 2024-12-31
2025-01-01
to 2025-12-31
2026-01-01
and beyond
Prerequisites
- SQL Server Enterprise Edition (or Developer Edition for testing)
- Sufficient disk space for new filegroup files (.ndf files)
- The partition column must be part of any clustered index or primary key on the table
- The archive table must have an identical schema to the source partition (same columns, same types, same constraints)
- Both tables must have matching indexes for SWITCH to succeed
- The
sysadminordb_ownerrole — orALTER ANY DATASPACEpermission
The Complete Implementation
Add Filegroups to the Database
This step is missing from many implementations but is critical. Before creating the partition scheme, the filegroups must exist on the database. Each filegroup needs at least one data file.
-- Add a filegroup for each archive year
ALTER DATABASE YourDatabase ADD FILEGROUP [ArchiveFileGroup2022];
ALTER DATABASE YourDatabase ADD FILEGROUP [ArchiveFileGroup2023];
ALTER DATABASE YourDatabase ADD FILEGROUP [ArchiveFileGroup2024];
ALTER DATABASE YourDatabase ADD FILEGROUP [ArchiveFileGroup2025];
ALTER DATABASE YourDatabase ADD FILEGROUP [ArchiveFileGroup2026];
-- Add a data file to each filegroup
-- Update the FILENAME paths to match your data drive
ALTER DATABASE YourDatabase
ADD FILE
(
NAME = N'Archive2022',
FILENAME = N'D:\SQLData\Archive2022.ndf',
SIZE = 256MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 256MB
) TO FILEGROUP [ArchiveFileGroup2022];
ALTER DATABASE YourDatabase
ADD FILE
(
NAME = N'Archive2023',
FILENAME = N'D:\SQLData\Archive2023.ndf',
SIZE = 256MB,
MAXSIZE = UNLIMITED,
FILEGROWTH = 256MB
) TO FILEGROUP [ArchiveFileGroup2023];
-- Repeat for 2024, 2025, 2026
-- Size the files appropriately for how much data each year contains
Size the initial file close to the amount of data you expect to archive into it. Autogrowth events during a SWITCH operation can cause unexpected delays. Pre-sizing eliminates this risk.
Create the Partition Function
The partition function defines the boundary values — the date values that separate one partition from the next. RANGE LEFT means the boundary value belongs to the left (lower) partition.
-- Partition function: splits the table by year
-- RANGE LEFT means the boundary date belongs to the left partition
-- e.g. '2023-01-01' is the upper bound of the 2022 partition
CREATE PARTITION FUNCTION MyPartitionFunction (DATE)
AS RANGE LEFT FOR VALUES
(
'2022-01-01', -- Partition 1: everything before 2022
'2023-01-01', -- Partition 2: 2022 data
'2024-01-01', -- Partition 3: 2023 data
'2025-01-01', -- Partition 4: 2024 data
'2026-01-01' -- Partition 5: 2025 data
-- Partition 6: 2026 and beyond
);
Create the Partition Scheme
The partition scheme maps each partition number to a filegroup. The number of filegroup entries must equal the number of partitions (boundary values + 1).
-- Map each partition to its filegroup
-- 6 boundary values = 6 partitions = 6 filegroup entries needed
CREATE PARTITION SCHEME MyPartitionScheme
AS PARTITION MyPartitionFunction
TO
(
[PRIMARY], -- Partition 1: pre-2022 data
[ArchiveFileGroup2022], -- Partition 2: 2022 data
[ArchiveFileGroup2023], -- Partition 3: 2023 data
[ArchiveFileGroup2024], -- Partition 4: 2024 data
[ArchiveFileGroup2025], -- Partition 5: 2025 data
[ArchiveFileGroup2026] -- Partition 6: 2026+ data
);
Create the Main Table on the Partition Scheme
The main table is created on the partition scheme, specifying DateColumn as the partition column. The partition column must be included in any clustered index.
-- Main table: partitioned by DateColumn
CREATE TABLE MyMainTable
(
ID INT NOT NULL,
DateColumn DATE NOT NULL,
Description NVARCHAR(500) NULL,
Amount DECIMAL(18, 2) NULL,
CreatedBy NVARCHAR(100) NULL,
-- Add all columns your table requires
CONSTRAINT PK_MyMainTable PRIMARY KEY CLUSTERED (ID, DateColumn)
)
ON MyPartitionScheme (DateColumn);
The partition column (DateColumn) must be included in the primary key or clustered index. SQL Server requires this to guarantee that each row can be unambiguously placed in the correct partition.
Create Archive Tables for Each Year
Each archive table must have the exact same schema as the main table — same columns, same data types, same nullability, and matching indexes. The only difference is that each archive table resides on its dedicated filegroup and covers exactly one partition’s range via a CHECK constraint.
-- Archive table for 2022 data
-- Must match main table schema exactly
-- CHECK constraint aligns with the partition boundary
CREATE TABLE MyArchiveTable2022
(
ID INT NOT NULL,
DateColumn DATE NOT NULL,
Description NVARCHAR(500) NULL,
Amount DECIMAL(18, 2) NULL,
CreatedBy NVARCHAR(100) NULL,
CONSTRAINT PK_MyArchiveTable2022 PRIMARY KEY CLUSTERED (ID, DateColumn),
CONSTRAINT CK_MyArchiveTable2022_Date
CHECK (DateColumn >= '2022-01-01' AND DateColumn < '2023-01-01')
)
ON [ArchiveFileGroup2022];
-- Archive table for 2023 data
CREATE TABLE MyArchiveTable2023
(
ID INT NOT NULL,
DateColumn DATE NOT NULL,
Description NVARCHAR(500) NULL,
Amount DECIMAL(18, 2) NULL,
CreatedBy NVARCHAR(100) NULL,
CONSTRAINT PK_MyArchiveTable2023 PRIMARY KEY CLUSTERED (ID, DateColumn),
CONSTRAINT CK_MyArchiveTable2023_Date
CHECK (DateColumn >= '2023-01-01' AND DateColumn < '2024-01-01')
)
ON [ArchiveFileGroup2023];
-- Repeat for 2024, 2025, 2026 with matching date ranges
The CHECK constraint is mandatory for SWITCH to succeed. SQL Server verifies that the archive table's CHECK constraint aligns with the partition boundary before allowing the switch. Without it, the ALTER TABLE...SWITCH will fail with an error.
Switch Partitions to Archive Tables
This is the archiving operation itself. Each SWITCH completes in milliseconds — it is a metadata-only operation with no data movement and minimal locking.
-- Switch 2022 data (partition 2) to the archive table
ALTER TABLE MyMainTable
SWITCH PARTITION 2 TO MyArchiveTable2022;
-- Switch 2023 data (partition 3) to the archive table
ALTER TABLE MyMainTable
SWITCH PARTITION 3 TO MyArchiveTable2023;
-- Switch 2024 data (partition 4) to the archive table
ALTER TABLE MyMainTable
SWITCH PARTITION 4 TO MyArchiveTable2024;
-- Verify the main table partitions are now empty
SELECT
p.partition_number,
p.rows,
prv.value AS boundary_value
FROM sys.partitions p
JOIN sys.tables t ON p.object_id = t.object_id
JOIN sys.partition_schemes ps ON ps.data_space_id =
(SELECT index_id FROM sys.indexes WHERE object_id = t.object_id
AND type = 1)
JOIN sys.partition_functions pf ON pf.function_id = ps.function_id
LEFT JOIN sys.partition_range_values prv
ON prv.function_id = pf.function_id
AND prv.boundary_id = p.partition_number
WHERE t.name = 'MyMainTable'
ORDER BY p.partition_number;
Merge Empty Partitions (Optional)
After switching out the data, you can merge the now-empty partitions back into the main table to simplify its partition structure. Use MERGE RANGE — not DROP PARTITION, which is not valid T-SQL syntax.
-- Merge the boundary to remove empty partition for 2022
-- This removes the '2022-01-01' boundary — partition 1 and 2 become one
ALTER PARTITION FUNCTION MyPartitionFunction()
MERGE RANGE ('2022-01-01');
-- Merge the 2023 boundary
ALTER PARTITION FUNCTION MyPartitionFunction()
MERGE RANGE ('2023-01-01');
-- Note: MERGE removes the boundary value and merges the two adjacent partitions
-- Only do this after confirming the partition is empty via the row count query above
Note that the original script used ALTER TABLE...DROP PARTITION — this is not valid SQL Server syntax. The correct operation is ALTER PARTITION FUNCTION...MERGE RANGE which removes a boundary value and merges the adjacent partitions.
Verifying the Archive
After completing the switch operations, verify the row counts are as expected in both the main table and archive tables:
-- Row counts by partition in the main table
SELECT
p.partition_number,
p.rows AS row_count,
fg.name AS filegroup_name
FROM sys.partitions p
JOIN sys.allocation_units au ON au.container_id = p.hobt_id
JOIN sys.filegroups fg ON fg.data_space_id = au.data_space_id
JOIN sys.tables t ON p.object_id = t.object_id
WHERE t.name = 'MyMainTable'
AND p.index_id IN (0, 1) -- heap or clustered index only
ORDER BY p.partition_number;
-- Confirm archive tables have data
SELECT '2022' AS year, COUNT(*) AS rows FROM MyArchiveTable2022
UNION ALL
SELECT '2023', COUNT(*) FROM MyArchiveTable2023
UNION ALL
SELECT '2024', COUNT(*) FROM MyArchiveTable2024;
ALTER TABLE SWITCH Requirements
The SWITCH operation has strict requirements. Understanding these prevents the most common errors:
| Requirement | Detail |
|---|---|
| Same schema | All columns must match exactly — name, data type, nullability, and collation |
| Same indexes | The archive table must have matching clustered and nonclustered indexes |
| CHECK constraint | The archive table must have a CHECK constraint covering the exact partition range |
| Same filegroup | The partition being switched must reside on the same filegroup as the archive table |
| No foreign keys | Neither table can have active foreign key constraints referencing the switched partition |
| Empty destination | The archive table (destination) must be empty before the switch |
| Enterprise Edition | Table partitioning requires Enterprise or Developer Edition |
Ongoing Maintenance and Adding New Years
Each year, before new data arrives, add a new boundary to the partition function and a new filegroup to accommodate it:
-- Add a new year (2027) to the partition function
-- First, mark the next filegroup as the destination for the split
ALTER PARTITION SCHEME MyPartitionScheme
NEXT USED [ArchiveFileGroup2027];
-- Then split the partition function to add the new boundary
ALTER PARTITION FUNCTION MyPartitionFunction()
SPLIT RANGE ('2027-01-01');
-- Create the archive table for 2027 when you are ready to archive
CREATE TABLE MyArchiveTable2027
(
ID INT NOT NULL,
DateColumn DATE NOT NULL,
Description NVARCHAR(500) NULL,
Amount DECIMAL(18, 2) NULL,
CreatedBy NVARCHAR(100) NULL,
CONSTRAINT PK_MyArchiveTable2027 PRIMARY KEY CLUSTERED (ID, DateColumn),
CONSTRAINT CK_MyArchiveTable2027_Date
CHECK (DateColumn >= '2027-01-01' AND DateColumn < '2028-01-01')
)
ON [ArchiveFileGroup2027];
Making Archive Filegroups Read-Only
Once a year's data is fully archived and no further changes are expected, mark the filegroup as read-only. This protects the data, reduces backup complexity, and can improve query performance:
-- Make the 2022 archive filegroup read-only
ALTER DATABASE YourDatabase
MODIFY FILEGROUP [ArchiveFileGroup2022] READ_ONLY;
-- Verify filegroup status
SELECT name, is_read_only
FROM sys.filegroups
WHERE name LIKE 'Archive%'
ORDER BY name;
Summary
The filegroup-based partition switching pattern is the production-grade approach to SQL Server data archiving. It eliminates the blocking, log growth, and downtime associated with row-by-row archiving while giving you independent control over each year's data for backup, storage tiering, and access policy purposes.
The pattern scales to billions of rows because ALTER TABLE...SWITCH is always a metadata operation. A partition containing ten years of transactional history switches to an archive table in under a second.
| Approach | Performance | Log Growth | Blocking |
|---|---|---|---|
| INSERT...SELECT...DELETE | Hours on large tables | Massive | Significant |
| Batched DELETE with loops | Hours to days | Controlled but still large | Moderate |
| ALTER TABLE...SWITCH | Milliseconds always | Minimal (metadata only) | Minimal (schema lock) |
References
Discover more from SQLYARD
Subscribe to get the latest posts sent to your email.


