Click here to Skip to main content
15,898,036 members
Articles / Database Development / SQL Server
Tip/Trick

Automatic Missing Indexes Creation Statements

Rate me:
Please Sign up or sign in to vote.
4.73/5 (9 votes)
30 Mar 2024Public Domain2 min read 23.9K   7   2
Automatic creation of non clustered indexes using system SQL entities

Introduction

A common issue about the indexes is to choose which indexes are necessary to create on your database to guarantee a good performance. On this tip, I want to present my solution about this problem, in this case I base all my code on a SQL Server system view named: dm_db_missing_index_details.

Background

Before we proceed with the implementation, maybe it's useful to know something more about dm_db_missing_index_details.

dm_db_missing_index_details returns detailed information about missing indexes. In this tip, we are mostly interested in these columns:

  • index_handle: It's an unique identifier across the server and identifies a particular missing index.
  • equality_columns: contains all columns used for equality predicates
  • inequality_columns: contains all columns used for other comparison
  • included columns: columns that are necessary to include on the index as covering columns for the query.
  • statement: complete table name where the index is missing.

Implementation

The implementation of this system is based on three entities:
  1. A simple function that calculates index's name that we want create
  2. A user view to simplify dm_db_missing_index_details
  3. A procedure to create a statement for each index

I chose to divide this system on three procedures, but honestly it's possible merge the stored procedure and view. I personally didn't choose this solution because I want check from my business logic about what kind of indexes there are before creating them.

Using the Code

1. Function - fn_Index_CreateIndexName

In this function, there are three input parameters:

  1. @equality_columns
  2. @equality_columns
  3. <a>@index_handlE</a>

The function's target is to create a unique name for each index that we want to create.
So first of all, we concatenate @equality_columns and @equality_columns input parameter, after that if result name is greater than 120 characters, it will cut at 120.

Why 120?

Because the maximum length of a name in SQL Server is 128 characters. After that function adds at the end of the name @index_handlE, that it's only to guarantee an unique index name.

CREATE FUNCTION [dbo].[fn_Index_CreateIndexName] (@equality_columns NVARCHAR(4000),
@Inequality_columns NVARCHAR(4000), @index_handlE INT) RETURNS VARCHAR(128)
AS
BEGIN
	
	DECLARE @IndexName NVARCHAR(255)

	SET @IndexName = ISNULL(@equality_columns,@Inequality_columns)

	SET @IndexName = LTRIM(REPLACE(@IndexName,'[','_'))

	SET @IndexName = RTRIM(REPLACE(@IndexName,']','_'))

	SET @IndexName = REPLACE(@IndexName,',','')

	SET @IndexName = REPLACE(@IndexName,'_ _','_')

	IF LEN(@IndexName) > 120
	BEGIN

		SET @IndexName = SUBSTRING(@IndexName,0,120)

	END  

	SET @IndexName = @IndexName + CAST(@index_handlE AS NVARCHAR(15))
	 
	RETURN @IndexName 
END

2. View - vw_Index_MissingIndex

This view is based on dm_db_missing_index_details joined with the table sys.databases and use the function fn_Index_CreateIndexName to calculate the missing index names.

CREATE VIEW [dbo].[vw_Index_MissingIndex]
AS

SELECT  '[' + d.name + ']' as DBName,
        [dbo].[fn_Index_CreateIndexName]
        (mid.equality_columns,mid.Inequality_columns,mid.index_handlE) AS ID,
        REPLACE(mid.equality_columns,',',' ASC,') AS equality_columns,
        REPLACE(mid.Inequality_columns,',',' ASC,') AS Inequality_columns,
        mid.Included_columns,
        mid.[statement]
FROM sys.dm_db_missing_index_details mid
INNER JOIN sys.databases d
on d.database_id = mid.database_id

3. Stored Procedure - usp_Index_MissingIndexCreationStatements

This procedure is based on vw_Index_MissingIndex and results in indexes creation statement.

CREATE PROCEDURE [dbo].[usp_Index_MissingIndexCreationStatements]
AS

DECLARE @IndexCreationPlaceholder_Start  AS NVARCHAR(MAX)
DECLARE @IndexCreationPlaceholder_End  AS NVARCHAR(MAX)

-- PREPARE PLACEHOLDER

SET @IndexCreationPlaceholder_Start = 'IF NOT EXISTS(SELECT * 
FROM {2}.sys.indexes WHERE [name] = ''IX_{0}'' )
				BEGIN
				CREATE NONCLUSTERED INDEX [IX_{0}] ON {1}'

SET @IndexCreationPlaceholder_End = ' WITH (PAD_INDEX = OFF, 
STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
				END;' + char(13) + char(10)

-- STATEMENT CREATION
 
SELECT
	DBName,
	CASE
	WHEN NOT mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', 
				mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
				   COALESCE(mid.equality_columns,'') +
				   ' ASC,' + 
				   COALESCE(mid.Inequality_columns,'') +
				   ' ASC
				)' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End

		WHEN mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,
				'{0}', mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
				   COALESCE(mid.Inequality_columns,'') +
				   ' ASC
				) ' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End
				
		WHEN NOT mid.equality_columns IS NULL AND mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}',
				mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
			   COALESCE(mid.equality_columns,'') +  ' ASC
					) ' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End
		ELSE NULL
	END AS Index_Creation_Statement,
	' DROP INDEX [IX_' + mid.ID  + '] ON ' + mid.[statement]  
	+  + char(13) + char(10) AS Index_Drop_Statement
FROM [dbo].[vw_Index_MissingIndex] AS mid

Complete Code

-- CREATE FUNCTION fn_Index_CreateIndexName

CREATE FUNCTION [dbo].[fn_Index_CreateIndexName] (@equality_columns NVARCHAR(4000), _
@Inequality_columns NVARCHAR(4000), @index_handlE INT) RETURNS VARCHAR(128)
AS
BEGIN
	
	DECLARE @IndexName NVARCHAR(MAX)

	SET @IndexName = ISNULL(@equality_columns,@Inequality_columns)

	SET @IndexName = LTRIM(REPLACE(@IndexName,'[','_'))

	SET @IndexName = RTRIM(REPLACE(@IndexName,']','_'))

	SET @IndexName = REPLACE(@IndexName,',','')

	SET @IndexName = REPLACE(@IndexName,'_ _','_')

	IF LEN(@IndexName) > 120
	BEGIN

		SET @IndexName = SUBSTRING(@IndexName,0,120)

	END  

	SET @IndexName = @IndexName + CAST(@index_handlE AS NVARCHAR(15))
	 
	RETURN @IndexName 
END

GO

-- CREATE FUNCTION vw_Index_MissingIndex

CREATE VIEW [dbo].[vw_Index_MissingIndex] 
AS

SELECT	'[' + d.name + ']' as DBName,
		[dbo].[fn_Index_CreateIndexName]
		(mid.equality_columns,mid.Inequality_columns,mid.index_handlE) AS ID,
		REPLACE(mid.equality_columns,',',' ASC,') AS equality_columns,
		REPLACE(mid.Inequality_columns,',',' ASC,') AS Inequality_columns,
		mid.Included_columns,
		mid.[statement]
FROM sys.dm_db_missing_index_details mid
INNER JOIN sys.databases d
on d.database_id = mid.database_id

GO

CREATE PROCEDURE [dbo].[usp_Index_MissingIndexCreationStatements]
AS

DECLARE @IndexCreationPlaceholder_Start  AS NVARCHAR(MAX)
DECLARE @IndexCreationPlaceholder_End  AS NVARCHAR(MAX)

-- PREPARE PLACEHOLDER

SET @IndexCreationPlaceholder_Start = 'IF NOT EXISTS_
(SELECT * FROM {2}.sys.indexes WHERE [name] = ''IX_{0}'' )
				BEGIN
				CREATE NONCLUSTERED INDEX [IX_{0}] ON {1}'

SET @IndexCreationPlaceholder_End = ' WITH (PAD_INDEX = OFF, _
STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
				END;' + char(13) + char(10)

-- STATEMENT CREATION
 
SELECT
	DBName,
	CASE
	WHEN NOT mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', 
				mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
				   COALESCE(mid.equality_columns,'') +
				   ' ASC,' + 
				   COALESCE(mid.Inequality_columns,'') +
				   ' ASC
				)' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End

		WHEN mid.equality_columns IS NULL AND NOT mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}', 
				mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
				   COALESCE(mid.Inequality_columns,'') +
				   ' ASC
				) ' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End
				
		WHEN NOT mid.equality_columns IS NULL AND mid.Inequality_columns IS NULL THEN
				REPLACE(REPLACE(REPLACE(@IndexCreationPlaceholder_Start,'{0}',
				mid.ID),'{1}',mid.[statement]),'{2}',mid.DBName)
				+ '
				   ( ' +
			   COALESCE(mid.equality_columns,'') +  ' ASC
					) ' +
				COALESCE('INCLUDE ( ' + mid.Included_columns + ' ) ','')
				+ @IndexCreationPlaceholder_End
		ELSE NULL
	END AS Index_Creation_Statement,
	' DROP INDEX [IX_' + mid.ID  + '] ON ' + mid.[statement] 
	+  + char(13) + char(10) AS Index_Drop_Statement
FROM [dbo].[vw_Index_MissingIndex] AS mid

GO

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer
Italy Italy
I'm a software engineer, specializing in backend development and distributed systems. I have extensive experience in designing and implementing resilient, responsive, scalable, and maintainable systems using C#, .NET on top of cutting-edge technologies.

Comments and Discussions

 
QuestionCaveat Emptor Pin
tmfish22-Sep-16 9:48
tmfish22-Sep-16 9:48 
One of the biggest potential issues is with the column order for multi-column indexes, they may NOT be in the most optimal order. Also, the DMV makes no recommendations on clustering or partitioning.

Blindly adding indexes based on the output of this DMV can have a significant negative impact on your instance's performance. This is not to say that the information is not a good place to start an evaluation, but nothing more.
GeneralRe: Caveat Emptor Pin
PIEBALDconsult30-Mar-24 14:37
mvePIEBALDconsult30-Mar-24 14:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.