By:Sergey Gigoyan | Last Updated: 2018-12-14 || Related Tips:More > Database Administration
ProblemSometimes it's necessary to move SQL Server tables between filegroups or create a copy of a table in a different filegroup. Reasons for having a copy of a table in a different filegroup could be for archiving historical data, using a copy of the table for reporting or for testing purposes. In this tip we will look at both scenarios and how it can be done using T-SQL.
SolutionThis article will show methods of copying tables to another filegroup in SQL Server 2016/2017 and in older versions of SQL Server. It will also show how to use SELECT…INTO in SQL 2016 ((13.x) SP2) and 2017 to create tables in a different filegroup.
To illustrate the solution, a test environment is needed, so let's start with the creation of a test database.
Creating the test environmentThe script below creates the TestDB database with two tables UserData and UserLog that stores login information. Both of these tables will be place in the default (primary) filegroup.
USE masterGO
--Database
CREATE DATABASE TestDB
GO
USE TestDB
GO
--Tables
CREATE TABLE UserData
(
UserID INT NOT NULL,
LoginName NVARCHAR(50),
PRIMARY KEY (UserID)
)
GO
CREATE TABLE UserLog
(
UserLogID INT NOT NULL IDENTITY(1,1),
UserID INT NOT NULL,
LoginDate DATETIME DEFAULT GETDATE(),
PRIMARY KEY (UserLogID)
)
GO
--Data
INSERT INTO UserData(UserID,LoginName)
VALUES(1,'<a href="/cdn-cgi/l/email-protection" data-cfemail="91e5fefcd1e5f4e2e5e4fff8e7f4e3e2f8e5e8bff2fefc">[email protected]</a>'),
(2,'<a href="/cdn-cgi/l/email-protection" data-cfemail="354654587541504641405b5c435047465c414c1b565a58">[email protected]</a> '),
(3,'<a href="/cdn-cgi/l/email-protection" data-cfemail="e68c878883a69283959293888f908394958f929fc885898b">[email protected]</a>'),
(4,'<a href="/cdn-cgi/l/email-protection" data-cfemail="c2a3acac82b6a7b1b6b7acabb4a7b0b1abb6bbeca1adaf">[email protected]</a>')
INSERT INTO UserLog(UserID)
VALUES(1),(2)
WAITFOR DELAY '00:00:10'
INSERT INTO UserLog(UserID)
VALUES(1),(3),(4)
WAITFOR DELAY '00:00:10'
INSERT INTO UserLog(UserID)
VALUES(2),(3),(1)
Now, we'll create a new filegroup to store historical data related to user logins. In other words, the UserData table (or some filtered data from that table) should be moved to a new filegroup.
So, let's create a new filegroup.
USE masterGO
ALTER DATABASE TestDB ADD FILEGROUP HISTORY
ALTER DATABASE TestDB
ADD FILE
(
NAME='History_Data',
FILENAME = 'D:\Microsoft SQL Server\mssql14.MSSQLSERVER\MSSQL\DATA\TesDB_2.mdf'
)
TO FILEGROUP HISTORY
GOGO
Running the query below, we can see that we have two filegroups for the database.
USE TestDBGO
SELECT * FROM sys.filegroups

However, both tables in our database are in the PRIMARY filegroup:
USE TestDBGO
SELECT o.[name] AS TableName, i.[name] AS IndexName, fg.[name] AS FileGroupName
FROM sys.indexes i
INNER JOIN sys.filegroups fg ON i.data_space_id = fg.data_space_id
INNER JOIN sys.all_objects o ON i.[object_id] = o.[object_id]
WHERE i.data_space_id = fg.data_space_id AND o.type = 'U'

Now, suppose we have a task to move the UserLog table to the HISTORY filegroup.
Moving a SQL Server table with data to a different filegroup Moving table with a clustered indexOne solution to move a table to another filegroup is by dropping the clustered index and using the MOVE TO option as follows. We can see the IndexName in the above screenshot.
USE TestDBGO
ALTER TABLE UserLog
DROP CONSTRAINT PK__UserLog__7F8B815172CE9EAE WITH (MOVE TO HISTORY)
We can now run this query to see which filegroup is being used.
USE TestDBGO
SELECT o.[name] AS TableName, i.[name] AS IndexName, fg.[name] AS FileGroupName
FROM sys.indexes i
INNER JOIN sys.filegroups fg ON i.data_space_id = fg.data_space_id
INNER JOIN sys.all_objects o ON i.[object_id] = o.[object_id]
WHERE i.data_space_id = fg.data_space_id AND o.type = 'U'
We can see the UserLog table is now in the HISTORY filegroup. However, the table no longer has a clustered index. If you need a clustered index, you would need to create one for the UserLog table.

Moving table without a clustered index
If we do not have a clustered index on the table, we can create a clustered index and specifying which filegroup to use.
For instance, if we want to move the UserLog table that we just moved to the HISTORY filegroup back to the PRIMARY filegroup, we could issue the following command.
USE TestDBGO
CREATE UNIQUE CLUSTERED INDEX UIX_UserLogID
ON UserLog(UserLogID) ON [PRIMARY]
Run the following again.
USE TestDBGO
SELECT o.[name] AS TableName, i.[name] AS IndexName, fg.[name] AS FileGroupName
FROM sys.indexes i
INNER JOIN sys.filegroups fg ON i.data_space_id = fg.data_space_id
INNER JOIN sys.all_objects o ON i.[object_id] = o.[object_id]
WHERE i.data_space_id = fg.data_space_id AND o.type = 'U'
We can see the UserLog table is back in the PRIMARY filegroup and has an index.

If we do not need the clustered index, we would need to run additional code to drop it as follows.
USE TestDBGO
DROP INDEX UserLog.UIX_UserLogID
Hence, if we are moving a heap to another filegroup, we first need to create a clustered index in order to move it to another filegroup and then we could drop the clustered index.
Creating a Copy of a SQL Server Table and Data on Different FilegroupWhat if we don't want to move the table, but just create a copy in another filegroup.
Prior to SQL Server 2016 SP2We could do this as follows where we use SELECT INTO, then create a clustered index and specify the filegroup, then drop the clustered index if we don't want the clustered index.
USE TestDBGO
SELECT * INTO UserLogHistory FROM UserLog
CREATE UNIQUE CLUSTERED INDEX UIX_UserLogID
ON UserLogHistory(UserLogID) ON [HISTORY]
DROP INDEX UserLogHistory.UIX_UserLogID
As a result, we have a new table UserLogHistory in the HISTORY filegroup:
USE TestDBGO
SELECT o.[name] AS TableName, i.[name] AS IndexName, fg.[name] AS FileGroupName
FROM sys.indexes i
INNER JOIN sys.filegroups fg ON i.data_space_id = fg.data_space_id
INNER JOIN sys.all_objects o ON i.[object_id] = o.[object_id]
WHERE i.data_space_id = fg.data_space_id AND o.type = 'U'
SELECT * FROM UserLogHistory

SQL Server 2016 SP2 and later
Starting with SQL Server 2016 SP2, SELECT…INTO allows you tospecify a filegroup when creating the new table.
USE TestDBGO
-- Create copy of the table and all data in different filegroup
SELECT * INTO UserLogHistory1 ON HISTORY
FROM UserLog
We can also use a WHERE clause to minimize the amount of data we want to move to the new table.
USE TestDBGO
-- Create copy of the table and filtered data in different filegroup
SELECT * I