SQL Server Archive Process Using Filegroups and Table Partitioning

SQL Server Archive Process Using Filegroups and Table Partitioning – SQLYARD

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

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.

MyMainTable (partitioned by year) Partition 1 = 2022 data · Partition 2 = 2023 · … · Partition 5 = 2026 ↓ ALTER TABLE MyMainTable SWITCH PARTITION 1 TO MyArchiveTable2022 MyArchiveTable2022 (on ArchiveFileGroup2022) Entire partition reassigned instantly — no data movement — no log growth ↓ Archive filegroup can be backed up, made read-only, or moved to cheaper storage

Key Concepts

ConceptWhat It IsRole in This Pattern
FilegroupA 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 FunctionDefines how rows are distributed across partitions based on a column valueSplits the main table by year using DateColumn
Partition SchemeMaps each partition from the function to a filegroupRoutes each year’s partition to the corresponding archive filegroup
SWITCHMetadata operation that reassigns a partition to a different tableInstantly moves a year’s data from the main table to the archive table
RANGE LEFTPartition boundary inclusive on the left sideA 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':

Partition 1 Before
2022-01-01
(≤ 2021-12-31)
Partition 2 2022 data
2022-01-01
to 2022-12-31
Partition 3 2023 data
2023-01-01
to 2023-12-31
Partition 4 2024 data
2024-01-01
to 2024-12-31
Partition 5 2025 data
2025-01-01
to 2025-12-31
Partition 6 2026+
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 sysadmin or db_owner role — or ALTER ANY DATASPACE permission

The Complete Implementation

1

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.

2

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
);
3

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
);
4

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.

5

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.

6

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;
7

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:

RequirementDetail
Same schemaAll columns must match exactly — name, data type, nullability, and collation
Same indexesThe archive table must have matching clustered and nonclustered indexes
CHECK constraintThe archive table must have a CHECK constraint covering the exact partition range
Same filegroupThe partition being switched must reside on the same filegroup as the archive table
No foreign keysNeither table can have active foreign key constraints referencing the switched partition
Empty destinationThe archive table (destination) must be empty before the switch
Enterprise EditionTable 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.

ApproachPerformanceLog GrowthBlocking
INSERT...SELECT...DELETEHours on large tablesMassiveSignificant
Batched DELETE with loopsHours to daysControlled but still largeModerate
ALTER TABLE...SWITCHMilliseconds alwaysMinimal (metadata only)Minimal (schema lock)

References


Discover more from SQLYARD

Subscribe to get the latest posts sent to your email.

Leave a Comment