views:

341

answers:

4

Hi all!

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!

+3  A: 

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

marc_s
The problem with this is that I have some indexes which are composite indexes. Some of them also have included columns or are unique etc. I can't just re-create them like that. I need to replicate the original schema for each index...
Nai
What's the problem? This script should read *ALL* the columns for the indices - no matter how many there are.
marc_s
Yes I have column names but I do not have the attribute. For example, the index might be nonclustered and have included columns, I would need to specify CREATE NONCLUSTERED INDEX <indexname> ON <tablename> (<columns>) INCLUDE <column name> etc etc. I can't get the included columns and such.
Nai
OK, that could be extended to handle those cases, too - not a big problem, I think.
marc_s
updated to include the index type (nonclustered vs. clustered) and the "included" columns
marc_s
A: 
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

Nai
A: 

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
Tim
A: 

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
Born2BeMild