Don’t trust the name of an index to tell you what it is. The name is wrong, or at least that’s what you have to assume.
I’m a huge fan of index naming conventions, with my favorite being starting out by saying it’s an index (IDX) followed by the table name, then the key columns, the letters INCL if there are included columns, each included column listed (if reasonable, just do all or nothing for each index), a U or N denoting if it’s unique or not, then a C or N denoting if it’s clustered or not. However, there are too many times I’ve seen an index naming convention get me in trouble where the index IDX_TableName_Key1_Key2_Key3_INCL_Incl1_Incl2_U_N actually not have the column Key1 in it, and it wasn’t unique either.
You can’t always trust the naming convention, even if it does exist. However, the system tables are always right. Here we have all of the key and included fields in an index, how much each index has been used (since restart of services or rebuilds in 2012+), and the size of each index. This typically takes less than a second to run in my experience, and you’ll get a feel for how it runs in your nonprod environment before it runs in prod, right?
DECLARE @TableName VarChar(100) = '%' --Accepts wildcards, % will give you all indexes in the database SELECT TableName = i.SchemaName + '.' + i.TableName , IndexName = ISNULL(i.IndexName, '[' + Lower(i.IndexType) + ']') , i.User_Updates , i.User_Seeks , i.User_Scans , i.User_Lookups , KeyColumnList = substring((SELECT (', ' + c.name) FROM sys.index_columns ic INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = ic.object_id AND i.index_id = ic.index_id AND ic.is_included_column = 0 ORDER BY ic.key_ordinal FOR XML PATH ('') ), 3, 2000) , IncludedColumnList = CASE WHEN i.IndexType IN ('Clustered', 'Heap') THEN '*' ELSE substring((SELECT (', ' + c.name) FROM sys.index_columns ic INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = ic.object_id AND i.index_id = ic.index_id AND ic.is_included_column = 1 ORDER BY c.Name FOR XML PATH ('') ), 3, 2000) END , i.filter_definition , i.is_unique , i.Used_MB , i.Reserved_MB , Free_MB = i.Reserved_MB - i.Used_MB , i.Used_InRow_MB , i.Used_LOB_MB , i.Row_Count , i.index_id -- , DropStatement = 'DROP INDEX [' + i.IndexName + '] ON [' + i.SchemaName + '].[' + i.TableName + ']' FROM ( SELECT SchemaName = s.name , TableName = t.name , IndexName = i.name , IndexType = i.type_desc , i.object_id , i.index_id , i.filter_definition , i.is_unique , User_Updates = SUM(ius.User_Updates) , User_Seeks = SUM(ius.user_seeks) , User_Scans = SUM(ius.user_scans) , User_Lookups = SUM(ius.user_lookups) , i.Used_MB , i.Reserved_MB , i.Used_InRow_MB , i.Used_LOB_MB , i.row_count FROM (SELECT i.name , i.type_desc , i.object_id , i.index_id , i.is_unique , i.filter_definition , Used_MB = SUM(PS.used_page_count) / 128 , Reserved_MB = SUM(PS.reserved_page_count) / 128 , Used_InRow_MB = SUM(PS.in_row_used_page_count) / 128 , Used_LOB_MB = SUM(PS.lob_used_page_count) / 128 , row_count = SUM(PS.row_count) FROM sys.indexes i LEFT JOIN sys.dm_db_partition_stats PS ON i.object_id = PS.object_id AND i.index_id = PS.index_id GROUP BY i.name, i.type_desc, i.object_id, i.index_id, i.is_unique, i.filter_definition) i INNER JOIN sys.all_objects t ON i.object_id = t.object_id INNER JOIN sys.schemas s ON t.schema_id = s.schema_id LEFT JOIN sys.dm_db_Index_Usage_Stats ius ON ius.object_id = i.object_id AND ius.index_id = i.index_id AND ius.database_id = DB_ID() WHERE t.name LIKE @TableName GROUP BY s.name, t.name, i.name, i.object_id, i.index_id, i.is_unique, i.type_desc, i.filter_definition, i.Used_MB, i.Reserved_MB, i.row_count, Used_InRow_MB, Used_LOB_MB ) i ORDER BY 1, KeyColumnList
You may say that’s a temporary one-off inqury the doesn’t fix anything, and it is. However, the permanent fix is very invasive, will void your support contracts, can cause damage, would cause pieces of future upgrades to fail, may not work if it generates a name that’s too long, and other minor details. Assuming you have a home-grown database and absolutely no query hints specifying an index anywhere in your code, have a dev environment recently refreshed from prod, and have looked into every other issue that I never even considered, do I have some code for you!!!
SELECT Command = '--DON''T RUN THIS WITHOUT FIRST LOOKING INTO THE CONSEQUENCES AND UPDATING YOUR RESUME' UNION SELECT Command = 'EXEC sp_rename ''' + TableName + '.' + IndexName + ''', ''' + 'IX_' + TableName + ColumnList + case when len(IncludeList) > 3 then '_INCL' else '' end + ISNULL(IncludeList, '') + '_' + case is_unique when 1 then 'U' else 'N' end + '_' + left(IndexType COLLATE SQL_Latin1_General_CP1_CS_AS, 1) + ''' , ''INDEX''' FROM ( SELECT TableName = t.name , IndexName = i.name , i.is_unique , IndexType = i.type_desc , FileGroupName = d.name , ColumnList = substring((SELECT ('_' + c.name) FROM sys.index_columns ic INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = ic.object_id AND i.index_id = ic.index_id AND ic.is_included_column = 0 ORDER BY ic.key_ordinal FOR XML PATH ('') ), 1, 2000) , IncludeList = substring((SELECT ('_' + c.name) FROM sys.index_columns ic INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = ic.object_id AND i.index_id = ic.index_id AND ic.is_included_column = 1 ORDER BY ic.key_ordinal FOR XML PATH ('') ), 1, 2000) FROM sys.tables t INNER JOIN sys.indexes i ON t.object_id = i.object_id INNER JOIN sys.data_spaces d ON i.data_space_id = d.data_space_id WHERE t.name IN ( 'Table_1' , 'Table_2') )x ORDER BY 1