MSSQL Server: Find Missing Indexes

Problem Statement: Script to find the missing indexes using 3 different ways.
Solution: 1) Find missing indexes using DMVs

--Find top ten missing indexes using following DMV 
SELECT TOP 10 ( user_seeks ) * avg_total_user_cost * ( avg_user_impact * 0.01 ) 
              AS 
              [index_advantage],--%advantage in the query 
              migs.last_user_seek, 
              --Date and time of last seek caused by user queries that the recommended index in the group could have been used for.
              mid.[statement] 
              AS [Database.Schema.Table], 
              'Create index idx_' 
              + Parsename( mid.[statement], 1) + '_' 
              + Cast(row_number() OVER(ORDER BY (user_seeks) * 
              avg_total_user_cost * ( 
              avg_user_impact * 0.01 ) DESC) AS VARCHAR(150)) 
              + ' on ' + Parsename( mid.[statement], 1) + '(' ++ 
              equality_columns + ')' + CASE WHEN mid.included_columns IS NOT 
              NULL THEN 
              ' include ('+ mid.included_columns+')' ELSE '' END 
              create_index, 
              'drop index idx_' 
              + Parsename( mid.[statement], 1) + '_' 
              + Cast(row_number() OVER(ORDER BY (user_seeks) * 
              avg_total_user_cost * ( 
              avg_user_impact * 0.01 ) DESC) AS VARCHAR(150)) 
              + ' on ' + Parsename( mid.[statement], 1) 
              drop_index, 
              mid.equality_columns, 
              mid.inequality_columns, 
              mid.included_columns, 
              migs.unique_compiles, 
              migs.user_seeks, 
              migs.avg_total_user_cost, 
              --  Average cost of the user queries that could be reduced by the index in the group. 
              migs.avg_user_impact 
--Average percentage benefit that user queries could experience, 
FROM   sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) 
       INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) 
               ON migs.group_handle = mig.index_group_handle 
       INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) 
               ON mig.index_handle = mid.index_handle 
WHERE  mid.database_id = Db_id() -- Remove this to see for entire instance 
ORDER  BY index_advantage DESC 
OPTION (RECOMPILE);

2) Find missing indexes on FK

SELECT fk.NAME    AS CONSTRAINT_NAME,
s.NAME     AS SCHEMA_NAME,
o.NAME     AS TABLE_NAME,
fkc_c.NAME AS CONSTRAINT_COLUMN_NAME
FROM   sys.foreign_keys AS fk
JOIN sys.foreign_key_columns AS fkc
ON fk.object_id = fkc.constraint_object_id
JOIN sys.columns AS fkc_c
ON fkc.parent_object_id = fkc_c.object_id
AND fkc.parent_column_id = fkc_c.column_id
LEFT JOIN sys.index_columns ic
JOIN sys.columns AS c
ON ic.object_id = c.object_id
AND ic.column_id = c.column_id
ON fkc.parent_object_id = ic.object_id
AND fkc.parent_column_id = ic.column_id
JOIN sys.objects AS o
ON o.object_id = fk.parent_object_id
JOIN sys.schemas AS s
ON o.schema_id = s.schema_id
WHERE  c.NAME IS NULL

3) Find missing indexes using stored query plans.

SELECT MissingIndexNode.value('(MissingIndexGroup/@Impact)[1]', 'float')     AS 
       impact, 
       Object_name(sub.objectid, sub.dbid)                                   AS 
       calling_object_name, 
       MissingIndexNode.value ('(MissingIndexGroup/MissingIndex/@Database)[1]', 
       'VARCHAR(128)') 
       + '.' 
       + MissingIndexNode.value ('(MissingIndexGroup/MissingIndex/@Schema)[1]', 
       'VARCHAR(128)') 
       + '.' 
       + MissingIndexNode.value ('(MissingIndexGroup/MissingIndex/@Table)[1]', 
       'VARCHAR(128)')                                                       AS 
       table_name, 
       Stuff((SELECT ',' + c.value('(@Name)[1]', 'VARCHAR(128)') 
              FROM   MissingIndexNode.nodes ( 
'MissingIndexGroup/MissingIndex/                             ColumnGroup[@Usage=''EQUALITY'']/Column'
) AS t ( c ) 
 FOR XML PATH('')), 1, 1, '')                                         AS 
       equality_columns, 
Stuff((SELECT ',' + c.value('(@Name)[1]', 'VARCHAR(128)') 
       FROM   MissingIndexNode.nodes ( 
'MissingIndexGroup/MissingIndex/                             ColumnGroup[@Usage=''INEQUALITY'']/Column'
) AS t ( c ) 
 FOR XML PATH('')), 1, 1, '')                                         AS 
       inequality_columns, 
Stuff((SELECT ',' + c.value('(@Name)[1]', 'VARCHAR(128)') 
       FROM   MissingIndexNode.nodes ( 
'MissingIndexGroup/MissingIndex/                             ColumnGroup[@Usage=''INCLUDE'']/Column'
) AS t ( c ) 
 FOR XML PATH('')), 1, 1, '')                                         AS 
       include_columns, 
sub.usecounts                                                         AS 
       qp_usecounts, 
sub.refcounts                                                         AS 
       qp_refcounts, 
qs.execution_count                                                    AS 
       qs_execution_count, 
qs.last_execution_time                                                AS 
       qs_last_exec_time, 
qs.total_logical_reads                                                AS 
       qs_total_logical_reads, 
qs.total_elapsed_time                                                 AS 
       qs_total_elapsed_time, 
qs.total_physical_reads                                               AS 
       qs_total_physical_reads, 
qs.total_worker_time                                                  AS 
       qs_total_worker_time, 
StmtPlanStub.value('(StmtSimple/@StatementText)[1]', 'varchar(8000)') AS 
statement_text 
FROM   (SELECT ROW_NUMBER() 
                 OVER ( 
                   PARTITION BY qs.plan_handle 
                   ORDER BY qs.statement_start_offset ) AS StatementID, 
               qs.* 
        FROM   sys.dm_exec_query_stats qs) AS qs 
       JOIN (SELECT x.query('../../..')                          AS StmtPlanStub 
                    , 
                    x.query('.') 
                    AS MissingIndexNode, 
                    x.value('(../../../@StatementId)[1]', 'int') AS StatementID, 
                    cp.*, 
                    qp.* 
             FROM   sys.dm_exec_cached_plans AS cp 
                    CROSS APPLY sys.dm_exec_query_plan (cp.plan_handle) qp 
                    CROSS APPLY qp.query_plan.nodes ( 
'/ShowPlanXML/BatchSequence/                                         Batch/Statements/StmtSimple/                                         QueryPlan/MissingIndexes/                                         MissingIndexGroup'
) mi ( x )) AS sub 
  ON qs.plan_handle = sub.plan_handle 
     AND qs.StatementID = sub.StatementID

Leave a Reply

Your email address will not be published. Required fields are marked *