super9
super9

Reputation: 30111

Generate CREATE scripts for a list of indexes

As part of a collation changing exercise, I have a list of indexes (122) that needs to be dropped and then re-created. How can I re-create these indexes without having to go through the GUI and scripting it to a query window each time?

My list of indexes is obtained from this script

WITH indexCTE AS
    (   
    SELECT Table_Name, Column_Name, Collation_Name 
    FROM information_schema.columns 
    WHERE Collation_Name IS NOT NULL AND Collation_Name = 'Modern_Spanish_CI_AS'
    ), 
    indexCTE2 AS
    (
    SELECT i.Name [Index Name], OBJECT_NAME(i.object_ID) [Table Name], c.Name [Column Name]
    FROM sys.indexes i 
    INNER JOIN sys.index_columns ic ON i.index_id = ic.index_id AND i.object_id = ic.object_id
    INNER JOIN sys.columns c ON ic.column_id = c.column_id AND ic.object_id = c.OBJECT_ID
    WHERE EXISTS (SELECT 1 FROM indexCTE t1 WHERE t1.Table_Name = OBJECT_NAME(i.object_ID) AND t1.Column_Name = c.Name)
    ) SELECT * FROM indexCTE2

As you can probably tell, I'm still a Jr. DBA so please be patient with me!

Thanks!

Upvotes: 3

Views: 11960

Answers (5)

juwens
juwens

Reputation: 3897

There's a relative complete solution at TechNet.

SQL
SELECT ' CREATE ' +    
    CASE WHEN  I.is_unique = 1 THEN ' UNIQUE '  ELSE ''  END  +    
    I.type_desc COLLATE  DATABASE_DEFAULT +' INDEX '  +     
    I.name  + ' ON '   +    
    Schema_name(T.Schema_id)+'.'+T.name + ' ( '  +   
    KeyColumns + ' )  ' +    
    ISNULL(' INCLUDE ('+IncludedColumns+' ) ','') +   
    ISNULL(' WHERE  '+I.Filter_definition,'') + ' WITH ( '  +   
    CASE WHEN  I.is_padded = 1 THEN ' PAD_INDEX = ON '  ELSE ' PAD_INDEX = OFF '  END + ','  +   
    'FILLFACTOR = '+CONVERT(CHAR(5),CASE WHEN  I.Fill_factor = 0 THEN 100 ELSE I.Fill_factor END) + ','  +    
    -- default value   
    'SORT_IN_TEMPDB = OFF '  + ','   +   
    CASE WHEN  I.ignore_dup_key = 1 THEN ' IGNORE_DUP_KEY = ON '  ELSE ' IGNORE_DUP_KEY = OFF '  END + ','  +   
    CASE WHEN  ST.no_recompute = 0 THEN ' STATISTICS_NORECOMPUTE = OFF '  ELSE ' STATISTICS_NORECOMPUTE = ON '  END + ','  +   
    -- default value    
    ' DROP_EXISTING = ON '  + ','   +   
    -- default value    
    ' ONLINE = OFF '  + ','   +   
   CASE WHEN  I.allow_row_locks = 1 THEN ' ALLOW_ROW_LOCKS = ON '  ELSE ' ALLOW_ROW_LOCKS = OFF '  END + ','  +   
   CASE WHEN  I.allow_page_locks = 1 THEN ' ALLOW_PAGE_LOCKS = ON '  ELSE ' ALLOW_PAGE_LOCKS = OFF '  END  + ' ) ON [' +   
   DS.name + ' ] '   [CreateIndexScript]   
FROM sys.indexes I     
 JOIN sys.tables T ON T.Object_id = I.Object_id      
 JOIN sys.sysindexes SI ON I.Object_id = SI.id AND I.index_id = SI.indid     
 JOIN (SELECT * FROM (    
    SELECT IC2.object_id , IC2.index_id ,    
        STUFF((SELECT ' , ' + C.name + CASE WHEN  MAX(CONVERT(INT,IC1.is_descending_key)) = 1 THEN  ' DESC ' ELSE  ' ASC ' END 
    FROM sys.index_columns IC1    
    JOIN Sys.columns C     
       ON C.object_id = IC1.object_id     
       AND C.column_id = IC1.column_id     
       AND IC1.is_included_column = 0    
    WHERE IC1.object_id = IC2.object_id     
       AND IC1.index_id = IC2.index_id     
    GROUP BY  IC1.object_id,C.name,index_id    
    ORDER BY  MAX(IC1.key_ordinal)    
       FOR XML PATH('')), 1, 2, '') KeyColumns     
    FROM sys.index_columns IC2     
    --WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables    
    GROUP BY  IC2.object_id ,IC2.index_id) tmp3 )tmp4     
  ON I.object_id = tmp4.object_id AND I.Index_id = tmp4.index_id    
 JOIN sys.stats ST ON ST.object_id = I.object_id AND ST.stats_id = I.index_id     
 JOIN sys.data_spaces DS ON I.data_space_id=DS.data_space_id     
 JOIN sys.filegroups FG ON I.data_space_id=FG.data_space_id     
 LEFT JOIN (SELECT * FROM (     
    SELECT IC2.object_id , IC2.index_id ,     
        STUFF((SELECT ' , ' + C.name  
    FROM sys.index_columns IC1     
    JOIN Sys.columns C      
       ON C.object_id = IC1.object_id      
       AND C.column_id = IC1.column_id      
       AND IC1.is_included_column = 1     
    WHERE IC1.object_id = IC2.object_id      
       AND IC1.index_id = IC2.index_id      
    GROUP BY  IC1.object_id,C.name,index_id     
       FOR XML PATH('')), 1, 2, '') IncludedColumns      
   FROM sys.index_columns IC2      
   --WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables     
   GROUP BY  IC2.object_id ,IC2.index_id) tmp1     
   WHERE IncludedColumns IS NOT  NULL ) tmp2      
ON tmp2.object_id = I.object_id AND tmp2.index_id = I.index_id     
WHERE I.is_primary_key = 0 AND I.is_unique_constraint = 0   
--AND I.Object_id = object_id('Person.Address') --Comment for all tables   
--AND I.name = 'IX_Address_PostalCode' --comment for all indexes

Adjust the query to your desire:

  • sys.tables to sys.views
  • remove default values in select
  • remove/add some where conditions

Upvotes: 2

Born2BeMild
Born2BeMild

Reputation: 544

Great script Marc. The only thing I think it is missing is the ascending or descending order indicator on each column. I have amended your script to include a case statement for the indexed columns to add in ASC or DESC depending on the is_descending_key column of the sys.index_columns view.

WITH indexCTE AS
(
    SELECT DISTINCT 
        i.index_id, i.name, i.object_id
    FROM 
        sys.indexes i 
    INNER JOIN
        sys.index_columns ic 
           ON i.index_id = ic.index_id AND i.object_id = ic.object_id
    WHERE 
        EXISTS (SELECT * FROM sys.columns c 
                 WHERE 
                 c.collation_name = 'Modern_Spanish_CI_AS' 
                 AND c.column_id = ic.column_id AND c.object_id = ic.object_id)
), 
indexCTE2 AS
(
    SELECT 
        indexCTE.name 'IndexName', 
        OBJECT_NAME(indexCTE.object_ID) 'TableName',
        CASE indexCTE.index_id 
          WHEN 1 THEN 'CLUSTERED'
          ELSE 'NONCLUSTERED'
        END AS 'IndexType', 
        (SELECT CASE WHEN ic.is_descending_key = 1 THEN c.name + ' DESC ,'
                ELSE c.name + ' ASC ,'
                END 
         FROM 
            sys.columns c 
         INNER JOIN
            sys.index_columns ic 
               ON c.object_id = ic.object_id AND ic.column_id = c.column_id AND ic.Is_Included_Column = 0
         WHERE
            indexCTE.OBJECT_ID = ic.object_id 
            AND indexCTE.index_id = ic.index_id 
         FOR XML PATH('')
        ) ixcols,
        ISNULL(
        (SELECT DISTINCT c.name + ','
         FROM 
            sys.columns c 
         INNER JOIN
            sys.index_columns ic 
               ON c.object_id = ic.object_id AND ic.column_id = c.column_id AND ic.Is_Included_Column = 1
         WHERE
            indexCTE.OBJECT_ID = ic.object_id 
            AND indexCTE.index_id = ic.index_id 
         FOR XML PATH('')
        ), '') includedcols
    FROM 
        indexCTE
) 
SELECT 
    'CREATE ' + IndexType + ' INDEX ' + IndexName + ' ON ' + TableName + 
        '(' + SUBSTRING(ixcols, 1, LEN(ixcols)-1) + 
        CASE LEN(includedcols)
          WHEN 0 THEN ')'
          ELSE ') INCLUDE (' + SUBSTRING(includedcols, 1, LEN(includedcols)-1) + ')'
        END
FROM 
   indexCTE2
ORDER BY 
   TableName, IndexName

Upvotes: 5

Tim
Tim

Reputation: 776

This is a bit off topic, but thought I would suggest this anyways:

If you don't want to keep executing your scripts in sql server management studio you can create a runmyscripts.bat file including something like:

@echo off

echo Execute Scripts...

sqlcmd -i C:\Scripts\myscript1.sql
sqlcmd -i C:\Scripts\myscript2.sql

echo Scripts Complete.
echo Press any button to exit.
pause

Upvotes: 0

super9
super9

Reputation: 30111

DECLARE @T_IndexInfo TABLE
    (
      IndID NVARCHAR(128),
      ObjectID NVARCHAR(128),
      ColID NVARCHAR(128),
      IndexName NVARCHAR(128),
      TableName NVARCHAR(128),
      ColumnName NVARCHAR(128),
      KeyNo NVARCHAR(128),
      ColType NVARCHAR(128)
    )

INSERT  INTO @T_IndexInfo
        SELECT  I.IndID,
                SO.ID AS 'ObjectID',
                SK.ColID,
                I.Name AS 'IndexName',
                SO.Name AS 'TableName',
                SC.Name AS 'ColumnName',
                Sk.KeyNo,
                CASE WHEN Sk.KeyNo = 0 THEN 'Include'
                     ELSE 'Normal'
                END AS 'ColType'
        FROM    sys.sysindexes I
                INNER JOIN sys.sysobjects SO ON SO.ID = I.ID
                                                AND SO.xtype = 'U'
                INNER JOIN sys.sysindexkeys SK ON SK.IndID = I.IndID
                                                  AND SO.ID = SK.ID
                INNER JOIN sys.syscolumns SC ON SC.ID = SO.ID
                                                AND SC.ColID = SK.ColID
        WHERE   I.IndID > 0
                AND I.IndID < 255
                AND ( I.Status & 64 ) = 0
--                AND ( I.status & 2048 ) <> 2048   /******** comment this if PK's also need to be recreated *****/
        ORDER BY SO.Name,
                I.Name

DECLARE @T_Final TABLE
    (
      TableName NVARCHAR(128),
      IndexName NVARCHAR(128),
      NormalColumns NVARCHAR(MAX),
      IncludedColumns NVARCHAR(MAX)
    )

INSERT  INTO @T_Final
        SELECT DISTINCT
                TableName,
                IndexName,
                STUFF(( SELECT  ',[' + ColumnName + ']'
                        FROM    @T_IndexInfo
                        WHERE   IndID = I.IndID
                                AND ObjectID = I.ObjectID
                                AND ColType = 'Normal'
                        ORDER BY KeyNo
                      FOR
                        XML PATH('')
                      ), 1, 1, '') AS 'NormalColumns',
                STUFF(( SELECT  ',[' + ColumnName + ']'
                        FROM    @T_IndexInfo
                        WHERE   IndID = I.IndID
                                AND ObjectID = I.ObjectID
                                AND ColType = 'Include'
                      FOR
                        XML PATH('')
                      ), 1, 1, '') AS 'IncludedColumns'
        FROM    @T_IndexInfo I;

WITH indexCTE AS
    (   
    SELECT Table_Name, Column_Name --, Collation_Name 
    FROM information_schema.columns 
    WHERE Collation_Name IS NOT NULL AND Collation_Name = 'Modern_Spanish_CI_AS'
    ), 
    indexCTE2 AS
    (
    SELECT i.Name [Index Name], OBJECT_NAME(i.object_ID) [Table Name], c.Name [Column Name]
    FROM sys.indexes i 
    INNER JOIN sys.index_columns ic ON i.index_id = ic.index_id AND i.object_id = ic.object_id
    INNER JOIN sys.columns c ON ic.column_id = c.column_id AND ic.object_id = c.OBJECT_ID
    WHERE EXISTS (SELECT 1 FROM indexCTE t1 WHERE t1.Table_Name = OBJECT_NAME(i.object_ID) AND t1.Column_Name = c.Name)
    )   

SELECT IndexName, TableName, NormalColumns, IncludedColumns
INTO #temp1
FROM @T_Final z INNER JOIN indexCTE2 x ON z.IndexName = x.[Index Name]

-- To generate CREATE INDEX SCRIPT
SELECT  'CREATE INDEX [' + IndexName + '] ON [' + TableName + '].('
        + NormalColumns + ')' + CASE WHEN IncludedColumns IS NULL THEN ''
                                     ELSE ' INCLUDE (' + IncludedColumns + ')'
                                END AS 'CreateScript'
FROM    #temp1

-- To generate DROP INDEX SCRIPT
SELECT  'DROP INDEX [' + TableName + '].[' + IndexName + ']' AS 'DropScript'
FROM    #temp1

DROP TABLE #temp1

Upvotes: 1

marc_s
marc_s

Reputation: 754468

You're pretty close, I'd say - I tried this, can you verify if this works for you and shows you the expected 122 indices to be recreated??

UPDATE: added functionality to determine CLUSTERED vs. NONCLUSTERED index type, and to add INCLUDEd columns to the index definition.

WITH indexCTE AS
(
    SELECT DISTINCT 
        i.index_id, i.name, i.object_id
    FROM 
        sys.indexes i 
    INNER JOIN
        sys.index_columns ic 
           ON i.index_id = ic.index_id AND i.object_id = ic.object_id
    WHERE 
        EXISTS (SELECT * FROM sys.columns c 
                 WHERE c.collation_name = 'Modern_Spanish_CI_AS' 
                 AND c.column_id = ic.column_id AND c.object_id = ic.object_id)
), 
indexCTE2 AS
(
    SELECT 
        indexCTE.name 'IndexName', 
        OBJECT_NAME(indexCTE.object_ID) 'TableName',
        CASE indexCTE.index_id 
          WHEN 1 THEN 'CLUSTERED'
          ELSE 'NONCLUSTERED'
        END AS 'IndexType', 
        (SELECT DISTINCT c.name + ','
         FROM 
            sys.columns c 
         INNER JOIN
            sys.index_columns ic 
               ON c.object_id = ic.object_id AND ic.column_id = c.column_id AND ic.Is_Included_Column = 0
         WHERE
            indexCTE.OBJECT_ID = ic.object_id 
            AND indexCTE.index_id = ic.index_id 
         FOR XML PATH('')
        ) ixcols,
        ISNULL(
        (SELECT DISTINCT c.name + ','
         FROM 
            sys.columns c 
         INNER JOIN
            sys.index_columns ic 
               ON c.object_id = ic.object_id AND ic.column_id = c.column_id AND ic.Is_Included_Column = 1
         WHERE
            indexCTE.OBJECT_ID = ic.object_id 
            AND indexCTE.index_id = ic.index_id 
         FOR XML PATH('')
        ), '') includedcols
    FROM 
        indexCTE
) 
SELECT 
    'CREATE ' + IndexType + ' INDEX ' + IndexName + ' ON ' + TableName + 
        '(' + SUBSTRING(ixcols, 1, LEN(ixcols)-1) + 
        CASE LEN(includedcols)
          WHEN 0 THEN ')'
          ELSE ') INCLUDE (' + SUBSTRING(includedcols, 1, LEN(includedcols)-1) + ')'
        END
FROM 
   indexCTE2
ORDER BY 
   TableName, IndexName

Do you get the CREATE INDEX statements you're looking for??

Marc

Upvotes: 11

Related Questions