DOWNLOAD THE CODE:
Download the Code 97018.zip

Executive Summary:
The script included with this article lets database administrators move tables and indexed views between filegroups in SQL Server 2000 and SQL Server 2005 more efficiently than moving tables and indexed views manually.


Filegroups are useful tools for controlling a database’s location and size because they let DBAs easily move files between different disks or network computers. But using filegroups can present problems when you need to move tables because if you define multiple filegroups for a single database, then some tables can reside in one filegroup while other tables might be stored in a different filegroup. If you then need to move data files, the fact that the data is spread out among filegroups means you’ll likely have to move tables from one filegroup to another. Here’s a sample scenario: A database called [MyDB] has two filegroups, which the sample code in Listing 1 shows. The first filegroup, called [PRIMARY], contains the data file C:\MyDB_Primary.mdf; the second filegroup, [MyFileGroup], has the data file D:\MyDB_ MyFilegroup.ndf. If I want to move all the table data to drive D while maintaining 100 percent uptime (in which case detaching databases or taking files offline is not an acceptable option), then I must move tables from one filegroup to the other. The need to move tables arises in other situations, such as in filegroup consolidation or before deleting database files. To make moving tables between filegroups easier and more efficient, I’ve written a SQL script that moves one table between filegroups. The script doesn’t assume any prior knowledge about the table and can be used to move tables that have a clustered index and also to move heaps. In addition, the script lets users specify whether a table’s nonclustered indexes and unique constraints should be moved as well.

My script is useful for moving tables or indexed views for SQL Server 2005 and SQL Server 2000. If a table you want to move has full-text indexes, the script won’t register those indexes with the full text catalog, nor will the script move user-defined statistics. Note that although I composed the script to move single tables, you can easily modify it to move a collection of tables and indexed views. Before I explain how my script works, let’s take a look at the basics of tables and moving them between filegroups. (The description following applies as well to indexed views because indexes on views have the same underlying architecture that table indexes do.)

Understanding Table Types
A table is stored on the filegroup that holds its data pages. In order to move a table between filegroups and keep the table schema intact, it’s important to distinguish between two types of tables. The first table type refers to tables that have a clustered index. When a table has a clustered index, the data pages are the leaf pages of the clustered index B-tree. To move such tables, you must recreate the clustered index on the target filegroup and also move the nonclustered indexes and constraints. The second table type refers to heaps. With tables that don’t have a clustered index, you can move the data pages by dropping the table, then recreating it on the desired file group. A better solution that omits the need to drop the table creates a clustered index on the target filegroup, then drops the clustered index later.

Although it might seem from the foregoing explanation that tables can be moved easily, keep in mind that changing clustered indexes can negatively affect a server’s performance and thus the availability of data, especially in large tables. When it comes to moving tables, the devil truly is in the details.

Existing Table-Moving Techniques
Assume that the database [MyDB] contains the table [dbo].[MyTable1]. This table has a primary key that is also a clustered index, a nonclustered index, and a unique constraint (the Data Definition Language—DDL—for the table is provided in the sample code in Listing 2). The indexes and constraints for this table all reside on the [PRIMARY] filegroup—we can validate this by running the SQL code in Listing 3 against the [MyDB] database. This SQL statement returns the filegroup information for all indexes and constraints that belong to the table [dbo].[MyTable1].

One technique for moving a table to a different filegroup is to use Enterprise Manager in SQL Server 2005 or SQL Server 2000. In Enterprise Manager you can right-click the table, select Design Table, click the Properties icon, then select the desired filegroup from the Table Filegroup dropdown menu, as Figure 1, page 38, shows. To move the table’s indexes and constraints, you can go to the Indexes/Keys tab on the Properties page, as Figure 2, page 38, shows, change the filegroup for each object in the Index Filegroup dropdown menu, click Close, then click Yes when prompted to apply the changes. Unfortunately, the ability to move a table to a different filegroup from the UI is no longer available in SQL Server Management Studio.

This technique’s main benefit is that it’s straightforward. However, a quick look under the hood reveals that it isn’t particularly efficient: Moving several tables requires a lot of tedious and manual work. Moreover, if you were to run SQL Profiler during the table move, you’d see that the GUI creates a new table called [dbo].[Tmp_utbMyTable1], moves the data from [dbo].[utbMyTable1] to the new table, then makes the new table assume the identity of the original one. These actions are inefficient because the table data is duplicated and stored twice in the database (once in [dbo].[utbMyTable1] and once in [dbo].[Tmp_utbMy- Table1]), which is extremely wasteful, especially in the case of large tables. Another problem with this method is that if the table [dbo].[utbMyTable1] has user-defined statistics, those statistics are dropped without warning and are not recreated. These actions are embedded in Enterprise Manager and in the SQL Distributed Management Objects (DMO)—both for SQL Server 2000 and the SQL Server 2005–compatible DMO—and therefore can’t be controlled by the user.

Another simple and popular way to move tables with a clustered index is to recreate the clustered index using the CREATE INDEX command together with the DROP_EXISTING clause, as the sample code in Listing 4, page 38, illustrates. This method is quick and easy; however, it results in redundant and wasteful operations. The SQL statement in Listing 4 moves only the clustered index and therefore has to be followed by similar statements for all nonclustered indexes. The recreation of the clustered index forces a rebuild of all nonclustered indexes and B-trees because the leaf pages of the nonclustered indexes must be redirected to point to the leaf pages of the newly reconstructed clustered index. In other words, using this technique rebuilds the nonclustered indexes twice—once when the clustered index is moved and again when the nonclustered indexes are recreated on the target filegroup.

A More Efficient Solution
As I’ve mentioned, the script I’ve written can move any type of table, including heaps. Here’s how it operates. The script accepts five user-assigned parameters: The variables @SourceFileGroupID and @TargetFileGroupID are set by the user and hold the original filegroup ID for the table to be moved, as well as the ID of the target filegroup. You can obtain the IDs of the database file groups by running EXEC sp_helpfile or by querying the system object [sysfiles]. Before running the script, you should also specify the name of the table to move as the variable @TableToMove. Moreover, you can indicate whether the primary key and constraints should also be moved by setting the value of the bit variable @MovePKAndAllUniqueConstraints to 1. In the same way, the bit variable @MoveAllNon- ClusteredIndexes indicates whether to move the nonclustered indexes. In order to move the table, the script performs the following steps and actions. First, the script determines whether the source and target filegroups and the requested table indeed exist, and aborts if they do not. Then, the script checks whether the requested table has a clustered index or is a heap. If the table is a heap, the script determines whether the table contains a unique column (i.e., an identity column or a column with a primary key or unique constraint). If a unique column indeed exists, the script creates a nonunique clustered index on that column on the appropriate target filegroup. Applying the clustered index on a unique column is useful because the column data has high cardinality even if duplicate data entries exist (e.g., as in the case of manually populating an identity column with SET IDENTITY INSERT), which guarantees fast and efficient creation of the clustered index. In the event that no unique columns are available, the script adds a new identity column and builds a clustered index on it. All items that are added to the table as part of the move process are later dropped to ensure that the table schema remains unchanged.

In a case where the requested table has a unique clustered index, the script will first drop all foreign keys that reference the unique index. (If the clustered index is not unique, then there’s no need to drop foreign keys.) Next, the clustered index is dropped. If you instruct the script to move the nonclustered indexes and the table constraints, they will be dropped to avoid the duplicate index rebuild I described previously. Finally, the script creates the clustered index on the target filegroup and also recreates all the indexes, constraints, and keys that were dropped earlier while maintaining all properties and attributes for all objects (such as PADINDEX and FILLFACTOR).

Moving IMAGE and TEXT Columns to Other Filegroups in SQL Server 2000
In SQL Server 2000 we have the option to explicitly specify the filegroup for columns that use long data types (i.e., IMAGE, TEXT, NTEXT) by using the TEXTIMAGE_ ON clause. For example, we can change the table creation statement in Listing 1 to write:

CREATE TABLE [dbo].[utbMyTable1]
([Col1] INT IDENTITY(1, 1),
[Col2] NVARCHAR(10),
[Col3] BIGINT,
[Col4] IMAGE) ON [PRIMARY] TEXTIMAGE_ON [MyFilegroup]

in which case the IMAGE data stored in column Col4 would reside on the filegroup [MyFilegroup]. If a table has more than one column that utilizes a large data type, then the data for all such large columns will be stored on the filegroup specified in the TEXTIMAGE_ON section. In SQL Server 2005 the TEXTIMAGE_ON clause is no longer available—the data contained in IMAGE, XML, TEXT, NTEXT, VARBINARY(MAX), VARCHAR(MAX), and NVARCHAR(MAX) columns always resides on the same filegroup as the table’s data pages. It’s important to note that my script doesn’t move data for the column types specified above because the only way to move these data entries is by dropping and recreating the entire table.

Limitations and Possibilities
My script doesn’t handle the case in which two objects in the database have the same name and belong to different schemas. On top, the script handles one table or indexed view at a time. I encourage interested readers to tune the script to move multiple objects and handle richer scenarios to fit their database environments.

End of Article




You must log on before posting a comment.

If you don't have a username & password, please register now.

Reader Comments

The link to download the code is a broken link. When I click it, it tries to go to http://www.winnetmag.com/Files/09/97018/97018.zip , and I get the message:

"Sorry the page you are trying to reach is temporarily unavailable or the page no longer exists."

jdulian

Article Rating 3 out of 5

I use the TEXTIMAGE_ON clause in SQL Server 2005 all the time. Am I missing something here? The article says it is no longer available in SQL 2005. -Ken

kkellman

Article Rating 4 out of 5

Ken- TEXTIMAGE_ON was not supported in SQL 2005 RTM and I'm not sure about SP1 (to the best of my knowledge). I looked again at the docs for the latest release (updated 2/2007) and it looks as if it is now supported. You are absolutely correct- it is currently supported so this is my mistake. I'm not sure when this support was added to SQL 2005. Would be interesting to find out. I'll try to find more info... thank you for the input! Very good comment.

Omri Bahat

Article Rating 5 out of 5

I don't see the actual script you mention to do the moving in the zip file. thanks!

tangojam

Article Rating 3 out of 5

The article doesn't explain about what the novel method adopted in the "A More Efficient Solution" approach, besides the script is missing.

sqlmagbm21

Article Rating 2 out of 5

Yeah... It would be not bad to add script itself. Someone may have practical interest in moving tables between filegroups... Although, article is very useful because summaries information about this problem.

Also, there is a mistake - " Unfortunately, the ability to move a table to a different filegroup from the UI is no longer available in SQL Server Management Studio." I believe that this ability exists.

ColdFusion74

Article Rating 4 out of 5

regarding the performance hit of using DROP_EXISTING when rebuilding the clustered index to move the file group:

if you are going to use CLUSTERED INDEX ... DROP_EXISTING, then drop the non-clustered indexes before issuing the command. then you only have to re-create the non-clustered indexes once.

tangojam

Article Rating 3 out of 5

Thank you for all the replied. I wanted to reply to all the posted comments:

tangojam (first posting) - SQL Mag was notified of the missing script and I hope they fix it promptly. The script can also be downloaded on SQL Farms' (free) forums at: http://education.sqlfarms.com/education/ShowForum.aspx?ForumID=20 (requires registration).

ColdFusion74- Thank you for the comment. I wasn't able to find how to move tables between filegroups in SSMS. If there is such a capability - please share it with everyone and post here.

tangojam (second posting) - you are absolutely correct; you can drop all non-clustered indexes and then recreate the clustered index using the DROP_EXISTIG clause, and then recreate the non-clustered indexes. One note on this- it has to be done carefully to account for unique indexes/constraints/keys as well. There are many cases to be tested. The provided script does take all cases into consideration.

Omri Bahat

Article Rating 5 out of 5

Nice article but useless without the script -- I already do these types of manual moves and having a script would be helpful.

jcasement

Article Rating 1 out of 5

To jcasement, Are you referring to a script in addition to the code in the download files? Thanks, Diana

DianaMay

Article Rating 4 out of 5

download link http://www.winnetmag.com/Files/09/97018/97018.zip is not working...

focasio

Article Rating 3 out of 5

I have to rate this article as a 3 under less useful. First and foremost, object references are SQL Server 2000. Please... I do give Omri credit for emphasizing the significance of dropping nonclustered indexes before rebuilding the clustered. That's about it.

msl9669

Article Rating 3 out of 5

msl9669 - What do you mean by Object References? Not sure I understand the comment...

Omri Bahat

Article Rating 5 out of 5

I couldn't see actual code, only thing I can download is 97018.zip file and it has only sample code to created tables, DBs, filegroups etc.

Where is the actual code to move tables between file groups?

smontabon

Article Rating 4 out of 5

If you headline the article "User this nifty code..." why don't you give us the code???

lesnicholson

Article Rating 2 out of 5

See More Comments  1   2