Add a Filegroup to a SQL Server Database

Adding a filegroup is one of those jobs that looks like two lines of T-SQL and then quietly does not work. The two lines are correct. The problem is that SQL Server lets you get further than you should before it tells you something is missing.

Everything here was run on a SQL Server 2025 instance, including a result that contradicts the advice you will usually see.


Why Bother

Three reasons come up in practice. You want user data off PRIMARY, so that a full user filegroup cannot take out the database’s own metadata. You want a set of tables on different storage, whether that is faster disk or simply a different volume with room on it. Or you want piecemeal restore, where a filegroup can be brought back on its own rather than restoring everything.


The Two Statements

The filegroup first, then a file in it. Both are needed, and the order matters:

-- 1. the container (metadata only, no disk space used yet)
ALTER DATABASE YourDatabase ADD FILEGROUP FG_Data2;

-- 2. a file inside it, which is what actually holds the pages
ALTER DATABASE YourDatabase
ADD FILE (
    NAME       = N'YourDatabase_Data2',
    FILENAME   = N'D:\SQLData\YourDatabase_Data2.ndf',
    SIZE       = 512MB,
    FILEGROWTH = 256MB
)
TO FILEGROUP FG_Data2;
ALTER DATABASE ADD FILEGROUP in SSMS with sys.filegroups showing PRIMARY as the default filegroup and the new filegroup added alongside it
The filegroup exists straight away, and sys.filegroups confirms PRIMARY is still the default. What it does not show you is that the new one has no files yet.

Set SIZE to something realistic rather than accepting the default and letting it grow there in small steps. Use a fixed FILEGROWTH in MB, not a percentage, so growth does not accelerate as the file gets larger.

Microsoft’s reference covers ALTER DATABASE file and filegroup options and Database files and filegroups in full.


The Trap: an Empty Filegroup Fails Late

Most write-ups say you cannot create a table on a filegroup that has no files. That is not what happens. Tested on SQL Server 2025, CREATE TABLE ... ON FG_Empty against a filegroup with no files succeeds. The table exists. Everything looks fine.

The failure is deferred to the first row you try to insert:

ALTER DATABASE YourDatabase ADD FILEGROUP FG_Empty;

CREATE TABLE dbo.Demo (id int) ON FG_Empty;   -- succeeds
INSERT INTO dbo.Demo (id) VALUES (1);         -- fails
Msg 622, Level 16, State 3
The filegroup "FG_Empty" has no files assigned to it. Tables, indexes, text columns,
ntext columns, and image columns cannot be populated on this filegroup until a file
is added.

This error has its own Error Library entry with the full reproduction: The Filegroup Has No Files Assigned to It (Error 622).

This matters because of when it lands. A deployment script that creates the filegroup and the tables will run clean, and the error turns up later, in an application, in front of whoever is using it. Add the file in the same script, then insert a row and roll it back if you want proof:

BEGIN TRAN;
    INSERT INTO dbo.Demo (id) VALUES (-1);
ROLLBACK TRAN;   -- if this ran, the filegroup is genuinely usable

Putting Objects on It

Nothing moves by itself. New tables need an explicit ON clause:

CREATE TABLE dbo.Orders_Archive (
    OrderID   int NOT NULL,
    OrderDate datetime2(0) NOT NULL,
    CONSTRAINT PK_Orders_Archive PRIMARY KEY CLUSTERED (OrderID) ON FG_Data2
) ON FG_Data2;

An existing table moves by rebuilding its clustered index onto the new filegroup:

CREATE CLUSTERED INDEX PK_Orders_Archive
    ON dbo.Orders_Archive (OrderID)
    WITH (DROP_EXISTING = ON, ONLINE = OFF)
    ON FG_Data2;

That is a physical move of every page in the table. Check you have room for both copies while it runs, and treat it as a maintenance window job rather than something to try at four in the afternoon.


Making It the Default

If the goal is keeping user data off PRIMARY altogether, change the default so that anything created without an ON clause lands in the right place:

ALTER DATABASE YourDatabase MODIFY FILEGROUP FG_Data2 DEFAULT;

SELECT name, is_default, is_read_only
FROM   YourDatabase.sys.filegroups;

Verified: the default moved from PRIMARY to the new filegroup, and a table created afterwards with no ON clause landed on it. Worth writing down somewhere, because a year later “why is this table not on PRIMARY” is a confusing question to answer from memory.


Checking Your Work

Two queries. What the filegroups and files look like, and where objects actually live:

SELECT  fg.name                AS filegroup_name,
        fg.is_default,
        f.name                 AS logical_file,
        f.physical_name,
        CAST(f.size / 128.0 AS decimal(18,1))    AS size_mb,
        CASE f.is_percent_growth WHEN 1 THEN CAST(f.growth AS varchar(10)) + ' %'
             ELSE CAST(f.growth / 128 AS varchar(10)) + ' MB' END AS growth
FROM    sys.filegroups fg
LEFT JOIN sys.database_files f ON f.data_space_id = fg.data_space_id
ORDER BY fg.name, f.name;

SELECT  t.name AS table_name, fg.name AS filegroup_name
FROM    sys.tables t
JOIN    sys.indexes i  ON i.object_id = t.object_id AND i.index_id IN (0, 1)
JOIN    sys.filegroups fg ON fg.data_space_id = i.data_space_id
ORDER BY fg.name, t.name;
ALTER DATABASE ADD FILE adding two data files to one filegroup, with sys.database_files showing both files sharing the same data_space_id
Two files added to the same filegroup in one statement. Both share a data_space_id, which is what makes them one container as far as your tables are concerned.

A LEFT JOIN in the first one is deliberate. It is the query that shows you a filegroup with no file, which is the state that causes Msg 622, and an inner join would hide exactly the row you need to see.


Removing One

In reverse order, and only once it is empty:

ALTER DATABASE YourDatabase REMOVE FILE   YourDatabase_Data2;
ALTER DATABASE YourDatabase REMOVE FILEGROUP FG_Data2;

Try it with objects still there and you get Msg 5042, The filegroup "FG_Data2" cannot be removed because it is not empty. If the file has data in it, DBCC SHRINKFILE (..., EMPTYFILE) moves those pages to other files in the same filegroup first.


Frequently Asked Questions

Do I have to add a file, or is the filegroup enough on its own?

You have to add a file, but SQL Server will not tell you that when you expect it to. ADD FILEGROUP creates metadata only. You can then create a table on that empty filegroup and it will succeed. The first INSERT is what fails, with Msg 622. Verified on SQL Server 2025.

What is the difference between a filegroup and a data file?

A filegroup is a named container; the files are what actually hold pages on disk. Objects are placed on a filegroup, never on a specific file, and SQL Server spreads writes across the files inside it using proportional fill. That indirection is the point: you can add capacity by adding a file without touching a single table definition.

Should I use multiple files in one filegroup?

If you want to spread a filegroup across more than one volume, or you are relieving a single hot file, then yes. Make them the same size with the same growth setting, or proportional fill will send most of the writes at whichever file has the most free space.

Can I move an existing table to the new filegroup?

Yes, by rebuilding its clustered index with ON [FG_Name]. That physically moves the table, so plan the space and the window: you need room for both copies during the rebuild. A heap has to get a clustered index first, or be moved by other means.

Why will my filegroup not drop?

Because something is still on it. REMOVE FILEGROUP against a non-empty filegroup fails with Msg 5042, The filegroup cannot be removed because it is not empty. Move or drop the objects, remove the files with REMOVE FILE, then remove the filegroup.

Can I make the new filegroup the default?

Yes: ALTER DATABASE ... MODIFY FILEGROUP [FG_Name] DEFAULT. Tested, the default moved off PRIMARY and a table created with no ON clause landed on the new filegroup. That is a real technique for keeping user data out of PRIMARY, and it is also easy to forget you did it.


Related


Summary

ADD FILEGROUP then ADD FILE ... TO FILEGROUP, and do not stop after the first one. An empty filegroup will happily accept a CREATE TABLE and only fail at the first INSERT with Msg 622, so prove it works with a rolled back insert rather than trusting a clean deployment. Objects do not move on their own: new tables need an ON clause, existing ones need a clustered index rebuild, and MODIFY FILEGROUP ... DEFAULT handles everything created afterwards.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *