Technical Article

Index Usage Stats

,

This script is great for determing index usage and whether they need to be tuned or dropped.  It also provides you with the drop index DDL as well as the create index DDL, incase you need to recreate it for whatever reason.

IF OBJECT_ID('tempdb..#TMP1') IS NOT NULL
    DROP TABLE #TMP1
GO

IF OBJECT_ID('tempdb..#TMP2') IS NOT NULL
    DROP TABLE #TMP2
GO

USE NGPROD
GO

DECLARE @TblName SYSNAME = NULL;

SET @TblName = '<your table name here>'

IF @TblName IS NULL
	SELECT '***** YOU MUST SPECIFY A TABLE NAME IN THE @TblName VARIABLE BEFORE RUNNING THIS SCRIPT *****!!!!!!!!'

IF @TblName IS NOT NULL
BEGIN
	SELECT 
		OBJECT_NAME(I.Object_id) AS TableName
		,I.NAME AS IndexName
		,' CREATE ' + CASE 
			WHEN I.is_unique = 1
				THEN ' UNIQUE '
			ELSE ''
			END + I.type_desc COLLATE DATABASE_DEFAULT + ' INDEX ' + RTRIM(I.NAME) + ' ON ' + Schema_name(T.Schema_id) + '.' + T.NAME + '(' + KeyColumns + ') ' + ISNULL(' INCLUDE (' + IncludedColumns + ')', '') + ISNULL(' WHERE  ' + I.Filter_definition, '') + ' WITH ( ' + CASE 
			WHEN I.is_padded = 1
				THEN ' PAD_INDEX = ON '
			ELSE ' PAD_INDEX = OFF '
			END + ',' + 'FILLFACTOR = ' + CONVERT(CHAR(5), CASE 
				WHEN I.Fill_factor = 0
					THEN 100
				ELSE I.Fill_factor
				END) + ',' +
		-- default value 
		'SORT_IN_TEMPDB = OFF ' + ',' + CASE 
			WHEN I.ignore_dup_key = 1
				THEN ' IGNORE_DUP_KEY = ON '
			ELSE ' IGNORE_DUP_KEY = OFF '
			END + ',' + CASE 
			WHEN ST.no_recompute = 0
				THEN ' STATISTICS_NORECOMPUTE = OFF '
			ELSE ' STATISTICS_NORECOMPUTE = ON '
			END + ',' +
		-- default value  
		' DROP_EXISTING = ON ' + ',' +
		-- default value  
		' ONLINE = OFF ' + ',' + CASE 
			WHEN I.allow_row_locks = 1
				THEN ' ALLOW_ROW_LOCKS = ON '
			ELSE ' ALLOW_ROW_LOCKS = OFF '
			END + ',' + CASE 
			WHEN I.allow_page_locks = 1
				THEN ' ALLOW_PAGE_LOCKS = ON '
			ELSE ' ALLOW_PAGE_LOCKS = OFF '
			END + ' ) ON [' + DS.NAME + ' ] ' [CreateIndexScript]
	INTO #TMP1
	FROM sys.indexes I
	JOIN sys.tables T ON T.Object_id = I.Object_id
	JOIN sys.sysindexes SI ON I.Object_id = SI.id AND I.index_id = SI.indid
	JOIN sys.dm_db_index_usage_stats dm_ius ON I.index_id = dm_ius.index_id
		AND dm_ius.OBJECT_ID = I.OBJECT_ID
	JOIN (
		SELECT *
		FROM (
			SELECT IC2.object_id
				,IC2.index_id
				,STUFF((
						SELECT ' , ' + C.NAME + CASE 
								WHEN MAX(CONVERT(INT, IC1.is_descending_key)) = 1
									THEN ' DESC '
								ELSE ' ASC '
								END
						FROM sys.index_columns IC1
						JOIN Sys.columns C ON C.object_id = IC1.object_id
							AND C.column_id = IC1.column_id
							AND IC1.is_included_column = 0
						WHERE IC1.object_id = IC2.object_id
							AND IC1.index_id = IC2.index_id
						GROUP BY IC1.object_id
							,C.NAME
							,index_id
						ORDER BY MAX(IC1.key_ordinal)
						FOR XML PATH('')
						), 1, 2, '') KeyColumns
			FROM sys.index_columns IC2
			--WHERE IC2.Object_id = object_id('Person.Address') --Comment for all tables  
			GROUP BY IC2.object_id
				,IC2.index_id
			) tmp3
		) tmp4 ON I.object_id = tmp4.object_id
		AND I.Index_id = tmp4.index_id
	JOIN sys.stats ST ON ST.object_id = I.object_id AND ST.stats_id = I.index_id
	JOIN sys.data_spaces DS ON I.data_space_id = DS.data_space_id
	JOIN sys.filegroups FG ON I.data_space_id = FG.data_space_id
	LEFT JOIN (
		SELECT *
		FROM (
			SELECT IC2.object_id
				,IC2.index_id
				,STUFF((
						SELECT ' , ' + C.NAME
						FROM sys.index_columns IC1
						JOIN Sys.columns C ON C.object_id = IC1.object_id
							AND C.column_id = IC1.column_id
							AND IC1.is_included_column = 1
						WHERE IC1.object_id = IC2.object_id
							AND IC1.index_id = IC2.index_id
						GROUP BY IC1.object_id
							,C.NAME
							,index_id
						FOR XML PATH('')
						), 1, 2, '') IncludedColumns
			FROM sys.index_columns IC2
			GROUP BY IC2.object_id
				,IC2.index_id
			) tmp1
		WHERE IncludedColumns IS NOT NULL
		) tmp2 ON tmp2.object_id = I.object_id
		AND tmp2.index_id = I.index_id
	WHERE I.is_primary_key = 0
		AND I.is_unique_constraint = 0
		AND I.Object_id = object_id(@TblName) --Comment for all tables 


-- Unused indexes with create index DDL
	SELECT
	o.name AS ObjectName
	, i.name AS IndexName	
	, STATS_DATE ( i.object_id , i.index_id ) as IndexCreatedDate
	, i.index_id AS IndexID
	, dm_ius.user_seeks AS UserSeek
	, dm_ius.user_scans AS UserScans
	, dm_ius.user_lookups AS UserLookups
	, dm_ius.user_updates AS UserUpdates
	, p.TableRows
	, dm_ius.last_user_seek AS [Last User Seek]
	, dm_ius.last_user_scan AS [Last User Scan]
	, dm_ius.last_system_lookup AS [Last User Lookup]
	, dm_ius.last_user_update AS [Last User Update]
	, 'DROP INDEX ' + QUOTENAME(RTRIM(i.name))
	+ ' ON ' + QUOTENAME(s.name) + '.'
	+ QUOTENAME(OBJECT_NAME(dm_ius.OBJECT_ID)) AS 'drop statement'
	INTO #TMP2
	FROM sys.dm_db_index_usage_stats dm_ius (NOLOCK)
	INNER JOIN sys.indexes i (NOLOCK) ON i.index_id = dm_ius.index_id AND dm_ius.OBJECT_ID = i.OBJECT_ID
	INNER JOIN sys.objects o (NOLOCK) ON dm_ius.OBJECT_ID = o.OBJECT_ID
	INNER JOIN sys.schemas s (NOLOCK) ON o.schema_id = s.schema_id
	INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.OBJECT_ID
	FROM sys.partitions p (NOLOCK) GROUP BY p.index_id, p.OBJECT_ID) p
	ON p.index_id = dm_ius.index_id AND dm_ius.OBJECT_ID = p.OBJECT_ID
	WHERE OBJECTPROPERTY(dm_ius.OBJECT_ID,'IsUserTable') = 1
	AND dm_ius.database_id = DB_ID()
	AND o.name = @tblName
	 AND i.type_desc = 'nonclustered'
	AND i.is_primary_key = 0
	AND i.is_unique_constraint = 0
	--ORDER BY (dm_ius.user_seeks + dm_ius.user_scans + dm_ius.user_lookups) ASC

	SELECT
		ObjectName AS [Table Name],
		b.IndexName AS [Index Name],
		IndexCreatedDate AS [Index Create Date],
		IndexID AS [Index ID],
		UserSeek AS [User Seeks],
		UserScans AS [User Scans],
		UserLookups AS [User Lookups],
		UserUpdates AS [User Updates],
		TableRows AS [Table rows],
		[Last User Seek],
		[Last User Scan],
		[Last User Lookup],
		[Last User Update],
		[drop statement],
		b.CreateIndexScript AS [Create Index DDL]
	FROM 
		#TMP2 a INNER JOIN #TMP1 b ON a.indexname = b.indexname
	ORDER BY
		(UserSeek + UserScans+ UserLookups)
END;

Rate

1 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

1 (2)

You rated this post out of 5. Change rating