We have on many occasions looked at query execution plans in the Management Studio. No doubt you have noticed that occasionally a green note appears in the heading of the plan describing a “missing index”. What could be better? SQL Server is telling you what indexes you need! Ah, if it were really only that simple.
SQL Server’s missing index recommendations may be helpful, but they are limited and cannot be considered a substitute for actually thinking. Let’s take a look. We’ll start with a query on the Bigwind sample database where we want all the Employees from Delaware. We’ll also run a related query that only retrieves a subset of the columns from the Employees table.

We see that SQL Server does not make a recommendation for the first query, even though it would benefit from a conventional (i.e. non-covering) index on the Region column. An index is recommended for the second query.
If you take a moment to look at index recommendations that might appear, you will quickly see that the SQL Server query engine essentially makes recommendations for covering indexes and focuses on listing columns in an INCLUDE clause. Other potentially useful indexes are ignored. For example, SQL Server’s missing index feature will not recommend a filtered index.
If we run a query very similar to the one abovebut include a couple more columns, we get a recommendation for yet another index.

It’s fair to say that after a while SQL Server will have recommended millions of covering indexes (OK, at least a lot); essentially an index for every query that might benefit from a covering index.
You may be aware that when SQL Server makes missing index recommendations it stashes them in its system tables. The saved recommendations can be examined using dynamic management views (DMVs) provided for that purpose. If we look at the listing of index recommendations when SQL Server starts up we will see nothing. SQL Server monitors many aspects of index usage, but this data is not persisted on the hard drive. If you restart the server, you restart with a blank slate as far as index usage information is concerned.
As is often the case with DMVs, we must join two or more to get the information we want in a format the human beings can reasonably interpret. In this case, we join three DMVs, dm_db_missing_index_groups, dm_db_missing_index_group_stats, and dm_db_missing_index_details.
SELECT mid . database_id ,
OBJECT_NAME ( mid . object_id ),
mig . index_group_handle ,
migs . group_handle ,
migs . unique_compiles ,
migs . user_seeks ,
migs . user_scans ,
migs . last_user_seek ,
migs . last_user_scan ,
migs . avg_user_impact ,
migs . avg_total_user_cost ,
migs . system_seeks ,
migs . system_scans ,
migs . last_system_seek ,
migs . last_system_scan ,
migs . avg_total_system_cost ,
migs . avg_system_impact ,
mid . equality_columns ,
mid . inequality_columns ,
mid . included_columns ,
mid . statement
FROM sys . dm_db_missing_index_groups mig
JOIN sys . dm_db_missing_index_group_stats migs
ON mig . index_group_handle = migs . group_handle
JOIN