This is a pretty simple task but I would like to provide a generic method for doing this. It will help if the requirement is one (or all) of the folowing. I have only considered non-clustered indexes along with the Inclued columns and filtered indexes. No point in moving clustered indexes as moving them would move the data as well. This is because the leaf level of a clustered index is nothing but a data page.
1. Move all the Indexes of a Table/View
2. Move specific Indexes of a Table/View
2. Move all the indexes present in a Database
Here is a SP to achieve this and I haven't added validations as the idea is to present how to do this. Hope this is useful.
I am assuming that you have already created a user-defined FileGroup to which you would like to move the indexes. The required parameters for this SP are Database name, scehma name, FileGroup name and minimum of one table/view name for the @ObjectNameList parameter. If you would like to move indexes from more than one object, specify a comma separated string (Ex:- @ObjectNameList = 'Table1,Table2'). Specify the Index name argument only when you want to move a single index from that table/view.
CREATE PROC [dbo].[MoveIndexToFileGroup] ( @DBName sysname, @SchemaName sysname = 'dbo', @ObjectNameList Varchar(Max), @IndexName sysname = null, @FileGroupName varchar(100) ) WITH RECOMPILE AS BEGIN SET NOCOUNT ON DECLARE @IndexSQL NVarchar(Max) DECLARE @IndexKeySQL NVarchar(Max) DECLARE @IncludeColSQL NVarchar(Max) DECLARE @FinalSQL NVarchar(Max) DECLARE @CurLoopCount Int DECLARE @MaxLoopCount Int DECLARE @StartPos Int DECLARE @EndPos Int DECLARE @ObjectName sysname DECLARE @IndName sysname DECLARE @IsUnique Varchar(10) DECLARE @Type Varchar(25) DECLARE @IsPadded Varchar(5) DECLARE @IgnoreDupKey Varchar(5) DECLARE @AllowRowLocks Varchar(5) DECLARE @AllowPageLocks Varchar(5) DECLARE @FillFactor Int DECLARE @ExistingFGName Varchar(Max) DECLARE @FilterDef NVarchar(Max) DECLARE @ErrorMessage NVARCHAR(4000) DECLARE @SQL nvarchar(4000) DECLARE @RetVal Bit DECLARE @ObjectList Table(Id Int Identity(1,1),ObjectName sysname) DECLARE @WholeIndexData Table ( ObjectName sysname ,IndexName sysname ,Is_Unique Bit ,Type_Desc Varchar(25) ,Is_Padded Bit ,Ignore_Dup_Key Bit ,Allow_Row_Locks Bit ,Allow_Page_Locks Bit ,Fill_Factor Int ,Is_Descending_Key Bit ,ColumnName sysname ,Is_Included_Column Bit ,FileGroupName Varchar(Max),Has_Filter Bit,Filter_Definition NVarchar(Max) ) DECLARE @DistinctIndexData Table ( Id Int IDENTITY(1,1) ,ObjectName sysname ,IndexName sysname ,Is_Unique Bit ,Type_Desc Varchar(25) ,Is_Padded Bit ,Ignore_Dup_Key Bit ,Allow_Row_Locks Bit ,Allow_Page_Locks Bit ,Fill_Factor Int ,FileGroupName Varchar(Max) ,Has_Filter Bit,Filter_Definition NVarchar(Max) ) -------------Validate arguments---------------------- IF(@DBName IS NULL) BEGIN SELECT @ErrorMessage = 'Database Name must be supplied.' GOTO ABEND END IF(@ObjectNameList IS NULL) BEGIN SELECT @ErrorMessage = 'Table or View Name(s) must be supplied.' GOTO ABEND END IF(@FileGroupName IS NULL) BEGIN SELECT @ErrorMessage = 'FileGroup Name must be supplied.' GOTO ABEND END --Check for the existence of the Database IF NOT EXISTS(SELECT Name FROM sys.databases where Name = @DBName) BEGIN SET @ErrorMessage = 'The specified Database does not exist' GOTO ABENDEND --Check for the existence of the Schema IF(upper(@SchemaName) <> 'DBO') BEGIN SET @SQL = 'SELECT @RetVal = COUNT(*) FROM ' + QUOTENAME(@DBName) + '.sys.schemas WHERE name = ''' + @SchemaName + '''' BEGIN TRY EXEC sp_executesql @SQL,N'@RetVal Bit OUTPUT',@RetVal OUTPUT END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH IF(@RetVal = 0) BEGIN SELECT @ErrorMessage = 'No Schema with the name ' + @SchemaName + ' exists in the Database ' + @DBName GOTO ABEND END END --Check for the existence of the FileGroup SET @SQL = 'SELECT @RetVal=COUNT(*) FROM ' + QUOTENAME(@DBName) + '.sys.filegroups WHERE name = ''' + @FileGroupName + '''' BEGIN TRY EXEC sp_executesql @SQL,N'@RetVal Bit OUTPUT',@RetVal OUTPUT END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH IF(@RetVal = 0) BEGIN SELECT @ErrorMessage = 'No FileGroup with the name ' + @FileGroupName + ' exists in the Database ' + @DBName GOTO ABEND END ----------Get the objects from the concatenated list---------------------------------------------------- SET @StartPos = 0 SET @EndPos = 0 WHILE(@EndPos >= 0) BEGIN SELECT @EndPos = CHARINDEX(',',@ObjectNameList,@StartPos) IF(@EndPos = 0) --Means, separator is not found BEGIN INSERT INTO @ObjectList SELECT SUBSTRING(@ObjectNameList,@StartPos,(LEN(@ObjectNameList) - @StartPos)+1) BREAK END INSERT INTO @ObjectList SELECT SUBSTRING(@ObjectNameList,@StartPos,(@EndPos - @StartPos)) SET @StartPos = @EndPos + 1 END -------------Check for the validity of all the Objects---------------------- SET @StartPos = 1 SELECT @EndPos = COUNT(*) FROM @ObjectList WHILE(@StartPos <= @EndPos) BEGIN SELECT @ObjectName = ObjectName FROM @ObjectList WHERE Id = @StartPos --Check for existence of the object SET @SQL = 'SELECT @RetVal=COUNT(*) FROM ' + QUOTENAME(@DBName) + '.sys.Objects WHERE type IN (''U'',''V'') AND name = ''' + @ObjectName + '''' BEGIN TRY EXEC sp_executesql @SQL,N'@RetVal Int OUTPUT',@RetVal OUTPUT END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH IF(@RetVal = 0) BEGIN SELECT @ErrorMessage = 'No Table or View with the name ' + @ObjectName + ' exists in the Database ' + @DBName GOTO ABEND END --Check for existence of Index IF(@IndexName IS NOT NULL) BEGIN SET @SQL = 'SELECT @RetVal=COUNT(*) FROM ' + QUOTENAME(@DBName) + '.sys.Indexes si INNER JOIN ' + QUOTENAME(@DBName) + '.sys.Objects so ' SET @SQL = @SQL + ' ON si.Object_Id = so.Object_Id WHERE so.Schema_id = ' + CAST(Schema_Id(@Schemaname) as varchar(25)) SET @SQL = @SQL + ' AND so.name = ''' + @ObjectName + ''' AND si.name = ''' + @IndexName + '''' BEGIN TRY EXEC sp_executesql @SQL,N'@RetVal Int OUTPUT',@RetVal OUTPUT END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH IF(@RetVal = 0) BEGIN SELECT @ErrorMessage = 'No Index with the name ' + @IndexName + ' exists on the Object ' + @ObjectName GOTO ABEND END END SET @StartPos = @StartPos + 1 END -------------Loop till all the Objects are processed---------------------- SET @StartPos = 1 SELECT @EndPos = COUNT(*) FROM @ObjectList WHILE(@StartPos <= @EndPos) BEGIN SELECT @ObjectName = ObjectName FROM @ObjectList WHERE Id = @StartPos -------------Build the SQL to get the index data based on the inputs provided---------------------- SET @IndexSQL = 'SELECT so.Name as ObjectName, si.Name as IndexName,si.Is_Unique,si.Type_Desc' + ',si.Is_Padded,si.Ignore_Dup_Key,si.Allow_Row_Locks,si.Allow_Page_Locks,si.Fill_Factor,sic.Is_Descending_Key' + ',sc.Name as ColumnName,sic.Is_Included_Column,sf.Name as FileGroupName,si.Has_Filter,si.Filter_Definition FROM ' + QUOTENAME(@DBName) + '.sys.Objects so INNER JOIN ' + QUOTENAME(@DBName) + '.sys.Indexes si ON so.Object_Id = si.Object_id INNER JOIN ' + QUOTENAME(@DBName) + '.sys.FileGroups sf ON sf.Data_Space_Id = si.Data_Space_Id INNER JOIN ' + QUOTENAME(@DBName) + '.sys.Index_columns sic ON si.Object_Id = sic.Object_Id AND si.Index_id = sic.Index_id INNER JOIN ' + QUOTENAME(@DBName) + '.sys.Columns sc ON sic.Column_Id = sc.Column_Id and sc.Object_Id = sic.Object_Id ' + ' WHERE so.Name = ''' + @ObjectName + '''' + ' AND so.Schema_id = ' + CAST(Schema_Id(@Schemaname) as varchar(25)) + ' AND si.Type_Desc = ''NONCLUSTERED'' ' IF(@IndexName IS NOT NULL) BEGIN SET @IndexSQL = @IndexSQL + ' AND si.Name = ''' + @IndexName + '''' END SET @IndexSQL = @IndexSQL + ' ORDER BY ObjectName, IndexName' --PRINT @IndexSQL -------------Insert the Index Data in to a variable---------------------- BEGIN TRY INSERT INTO @WholeIndexData EXEC sp_executesql @IndexSQL END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH --Check if any indexes are there on the object. Otherwise exit IF (SELECT COUNT(*) FROM @WholeIndexData) = 0 BEGIN SELECT 'Object does not have any nonclustered indexes to move' GOTO FINAL END -------------Get the distinct index rows in to a variable---------------------- INSERT INTO @DistinctIndexData SELECT DISTINCT ObjectName,IndexName,Is_Unique,Type_Desc,Is_Padded,Ignore_Dup_Key,Allow_Row_Locks,Allow_Page_Locks,Fill_Factor,FileGroupName,Has_Filter,Filter_Definition FROM @WholeIndexData WHERE ObjectName = @ObjectName SELECT @CurLoopCount = Min(Id), @MaxLoopCount = Max(Id) FROM @DistinctIndexData WHERE ObjectName = @ObjectName --SELECT @CurLoopCount, @MaxLoopCount -------------Loop till all the indexes are processed---------------------- WHILE(@CurLoopCount <= @MaxLoopCount) BEGIN SET @IndexKeySQL = '' SET @IncludeColSQL = '' -------------Get the current index row to be processed---------------------- SELECT @IndName = IndexName ,@Type = Type_Desc ,@ExistingFGName = FileGroupName ,@IsUnique = CASE WHEN Is_Unique = 1 THEN 'UNIQUE ' ELSE '' END ,@IsPadded = CASE WHEN Is_Padded = 0 THEN 'OFF,' ELSE 'ON,' END ,@IgnoreDupKey = CASE WHEN Ignore_Dup_Key = 0 THEN 'OFF,' ELSE 'ON,' END ,@AllowRowLocks = CASE WHEN Allow_Row_Locks = 0 THEN 'OFF,' ELSE 'ON,' END ,@AllowPageLocks = CASE WHEN Allow_Page_Locks = 0 THEN 'OFF,' ELSE 'ON,' END ,@FillFactor = CASE WHEN Fill_Factor = 0 THEN 100 ELSE Fill_Factor END ,@FilterDef = CASE WHEN Has_Filter = 1 THEN (' WHERE ' + Filter_Definition) ELSE '' END FROM @DistinctIndexData WHERE Id = @CurLoopCount -------------Check if the index is already not part of that FileGroup---------------------- IF(@ExistingFGName = @FileGroupName) BEGIN PRINT 'Index ' + @IndName + ' is NOT moved as it is already part of the FileGroup ' + @FileGroupName + '.' SET @CurLoopCount = @CurLoopCount + 1 CONTINUE END ------- Construct the Index key string along with the direction-------------------- SELECT @IndexKeySQL = CASE WHEN @IndexKeySQL = '' THEN (@IndexKeySQL + QUOTENAME(ColumnName) + CASE WHEN Is_Descending_Key = 0 THEN ' ASC' ELSE ' DESC' END) ELSE (@IndexKeySQL + ',' + QUOTENAME(ColumnName) + CASE WHEN Is_Descending_Key = 0 THEN ' ASC' ELSE ' DESC' END) END FROM @WholeIndexData WHERE ObjectName = @ObjectName AND IndexName = @IndName AND Is_Included_Column = 0 --PRINT @IndexKeySQL ------ Construct the Included Column string -------------------------------------- SELECT @IncludeColSQL = CASE WHEN @IncludeColSQL = '' THEN (@IncludeColSQL + QUOTENAME(ColumnName)) ELSE (@IncludeColSQL + ',' + QUOTENAME(ColumnName)) END FROM @WholeIndexData WHERE ObjectName = @ObjectName AND IndexName = @IndName AND Is_Included_Column = 1 --PRINT @IncludeColSQL -------------Construct the final Create Index statement---------------------- SELECT @FinalSQL = 'CREATE ' + @IsUnique + @Type + ' INDEX ' + QUOTENAME(@IndName) + ' ON ' + QUOTENAME(@DBName) + '.' + QUOTENAME(@SchemaName) + '.' + QUOTENAME(@ObjectName) + '(' + @IndexKeySQL + ') ' + CASE WHEN LEN(@IncludeColSQL) <> 0 THEN 'INCLUDE(' + @IncludeColSQL + ') ' ELSE '' END + @FilterDef + ' WITH (' + 'PAD_INDEX = ' + @IsPadded + 'IGNORE_DUP_KEY = ' + @IgnoreDupKey + 'ALLOW_ROW_LOCKS = ' + @AllowRowLocks + 'ALLOW_PAGE_LOCKS = ' + @AllowPageLocks + 'SORT_IN_TEMPDB = OFF,' + 'DROP_EXISTING = ON,' + 'ONLINE = OFF,' + 'FILLFACTOR = ' + CAST(@FillFactor AS Varchar(3)) + ') ON ' + QUOTENAME(@FileGroupName) --PRINT @FinalSQL -------------Execute the Create Index statement to move to the specified filegroup---------------------- BEGIN TRY EXEC sp_executesql @FinalSQL END TRY BEGIN CATCH SELECT @ErrorMessage = ERROR_MESSAGE() GOTO ABEND END CATCH PRINT 'Index ' + @IndName + ' on Object ' + @ObjectName + ' is moved successfully.' SET @CurLoopCount = @CurLoopCount + 1 END SET @StartPos = @StartPos + 1 END SELECT 'The procedure completed successfully.' RETURN ABEND: RAISERROR 500001 @ErrorMessage FINAL: RETURN END