view_IndexCreation.sql 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. --
  2. -- 2007-10-11 Pedro Lopes (Microsoft) [email protected]
  3. --
  4. -- 2008-01-17 Check for possibly redundant indexes in the output.
  5. -- 2009-05-21 Changed index scoring method; Disregards indexes with [Score] < 100000 and [User_Hits_on_Missing_Index] < 99;
  6. -- 2013-03-21 Changed database loop method;
  7. -- 2013-11-10 Added search for redundant indexes in missing indexes;
  8. SET NOCOUNT ON;
  9. SET QUOTED_IDENTIFIER ON;
  10. DECLARE @IC VARCHAR(4000), @ICWI VARCHAR(4000), @editionCheck bit
  11. /* Refer to http://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql */
  12. IF (SELECT SERVERPROPERTY('EditionID')) IN (1804890536, 1872460670, 610778273, -2117995310)
  13. SET @editionCheck = 1 -- supports enterprise only features
  14. ELSE
  15. SET @editionCheck = 0; -- does not support enterprise only features
  16. -- Create the helper functions
  17. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_allcols'')) DROP FUNCTION dbo.fn_createindex_allcols')
  18. EXEC ('USE tempdb; EXEC(''
  19. CREATE FUNCTION dbo.fn_createindex_allcols (@ix_handle int)
  20. RETURNS NVARCHAR(max)
  21. AS
  22. BEGIN
  23. DECLARE @ReturnCols NVARCHAR(max)
  24. ;WITH ColumnToPivot ([data()]) AS (
  25. SELECT CONVERT(VARCHAR(3),ic.column_id) + N'''',''''
  26. FROM sys.dm_db_missing_index_details id
  27. CROSS APPLY sys.dm_db_missing_index_columns(id.index_handle) ic
  28. WHERE id.index_handle = @ix_handle
  29. ORDER BY ic.column_id ASC
  30. FOR XML PATH(''''''''), TYPE
  31. ),
  32. XmlRawData (CSVString) AS (
  33. SELECT (SELECT [data()] AS InputData
  34. FROM ColumnToPivot AS d FOR XML RAW, TYPE).value(''''/row[1]/InputData[1]'''', ''''NVARCHAR(max)'''') AS CSVCol
  35. )
  36. SELECT @ReturnCols = CASE WHEN LEN(CSVString) <= 1 THEN NULL ELSE LEFT(CSVString, LEN(CSVString)-1) END
  37. FROM XmlRawData
  38. RETURN (@ReturnCols)
  39. END'')
  40. ')
  41. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_keycols'')) DROP FUNCTION dbo.fn_createindex_keycols')
  42. EXEC ('USE tempdb; EXEC(''
  43. CREATE FUNCTION dbo.fn_createindex_keycols (@ix_handle int)
  44. RETURNS NVARCHAR(max)
  45. AS
  46. BEGIN
  47. DECLARE @ReturnCols NVARCHAR(max)
  48. ;WITH ColumnToPivot ([data()]) AS (
  49. SELECT CONVERT(VARCHAR(3),ic.column_id) + N'''',''''
  50. FROM sys.dm_db_missing_index_details id
  51. CROSS APPLY sys.dm_db_missing_index_columns(id.index_handle) ic
  52. WHERE id.index_handle = @ix_handle
  53. AND (ic.column_usage = ''''EQUALITY'''' OR ic.column_usage = ''''INEQUALITY'''')
  54. ORDER BY ic.column_id ASC
  55. FOR XML PATH(''''''''), TYPE
  56. ),
  57. XmlRawData (CSVString) AS (
  58. SELECT (SELECT [data()] AS InputData
  59. FROM ColumnToPivot AS d FOR XML RAW, TYPE).value(''''/row[1]/InputData[1]'''', ''''NVARCHAR(max)'''') AS CSVCol
  60. )
  61. SELECT @ReturnCols = CASE WHEN LEN(CSVString) <= 1 THEN NULL ELSE LEFT(CSVString, LEN(CSVString)-1) END
  62. FROM XmlRawData
  63. RETURN (@ReturnCols)
  64. END'')
  65. ')
  66. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_includedcols'')) DROP FUNCTION dbo.fn_createindex_includedcols')
  67. EXEC ('USE tempdb; EXEC(''
  68. CREATE FUNCTION dbo.fn_createindex_includedcols (@ix_handle int)
  69. RETURNS NVARCHAR(max)
  70. AS
  71. BEGIN
  72. DECLARE @ReturnCols NVARCHAR(max)
  73. ;WITH ColumnToPivot ([data()]) AS (
  74. SELECT CONVERT(VARCHAR(3),ic.column_id) + N'''',''''
  75. FROM sys.dm_db_missing_index_details id
  76. CROSS APPLY sys.dm_db_missing_index_columns(id.index_handle) ic
  77. WHERE id.index_handle = @ix_handle
  78. AND ic.column_usage = ''''INCLUDE''''
  79. ORDER BY ic.column_id ASC
  80. FOR XML PATH(''''''''), TYPE
  81. ),
  82. XmlRawData (CSVString) AS (
  83. SELECT (SELECT [data()] AS InputData
  84. FROM ColumnToPivot AS d FOR XML RAW, TYPE).value(''''/row[1]/InputData[1]'''', ''''NVARCHAR(max)'''') AS CSVCol
  85. )
  86. SELECT @ReturnCols = CASE WHEN LEN(CSVString) <= 1 THEN NULL ELSE LEFT(CSVString, LEN(CSVString)-1) END
  87. FROM XmlRawData
  88. RETURN (@ReturnCols)
  89. END'')
  90. ')
  91. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#IndexCreation'))
  92. DROP TABLE #IndexCreation
  93. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#IndexCreation'))
  94. CREATE TABLE #IndexCreation (
  95. [database_id] int,
  96. DBName VARCHAR(255),
  97. [Table] VARCHAR(255),
  98. [ix_handle] int,
  99. [User_Hits_on_Missing_Index] int,
  100. [Estimated_Improvement_Percent] DECIMAL(5,2),
  101. [Avg_Total_User_Cost] int,
  102. [Unique_Compiles] int,
  103. [Score] NUMERIC(19,3),
  104. [KeyCols] VARCHAR(1000),
  105. [IncludedCols] VARCHAR(4000),
  106. [Ix_Name] VARCHAR(255),
  107. [AllCols] NVARCHAR(max),
  108. [KeyColsOrdered] NVARCHAR(max),
  109. [IncludedColsOrdered] NVARCHAR(max)
  110. )
  111. IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#IndexRedundant'))
  112. DROP TABLE #IndexRedundant
  113. IF NOT EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID('tempdb.dbo.#IndexRedundant'))
  114. CREATE TABLE #IndexRedundant (
  115. DBName VARCHAR(255),
  116. [Table] VARCHAR(255),
  117. [Ix_Name] VARCHAR(255),
  118. [ix_handle] int,
  119. [KeyCols] VARCHAR(1000),
  120. [IncludedCols] VARCHAR(4000),
  121. [Redundant_With] VARCHAR(255)
  122. )
  123. INSERT INTO #IndexCreation
  124. SELECT i.database_id,
  125. m.[name],
  126. RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3)) AS [Table],
  127. i.index_handle AS [ix_handle],
  128. [User_Hits_on_Missing_Index] = (s.user_seeks + s.user_scans),
  129. s.avg_user_impact, -- Query cost would reduce by this amount in percentage, on average.
  130. s.avg_total_user_cost, -- Average cost of the user queries that could be reduced by the index in the group.
  131. s.unique_compiles, -- Number of compilations and recompilations that would benefit from this missing index group.
  132. (CONVERT(NUMERIC(19,3), s.user_seeks) + CONVERT(NUMERIC(19,3), s.user_scans))
  133. * CONVERT(NUMERIC(19,3), s.avg_total_user_cost)
  134. * CONVERT(NUMERIC(19,3), s.avg_user_impact) AS Score, -- The higher the score, higher is the anticipated improvement for user queries.
  135. CASE WHEN (i.equality_columns IS NOT NULL AND i.inequality_columns IS NULL) THEN i.equality_columns
  136. WHEN (i.equality_columns IS NULL AND i.inequality_columns IS NOT NULL) THEN i.inequality_columns
  137. ELSE i.equality_columns + ',' + i.inequality_columns END AS [KeyCols],
  138. i.included_columns AS [IncludedCols],
  139. 'IX_' + LEFT(RIGHT(RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3)), LEN(RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3))) - (CHARINDEX('.', RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3)), 1)) - 1),
  140. LEN(RIGHT(RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3)), LEN(RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3))) - (CHARINDEX('.', RIGHT(i.[statement], LEN(i.[statement]) - (LEN(m.[name]) + 3)), 1)) - 1)) - 1) + '_' + CAST(i.index_handle AS NVARCHAR) AS [Ix_Name],
  141. tempdb.dbo.fn_createindex_allcols(i.index_handle),
  142. tempdb.dbo.fn_createindex_keycols(i.index_handle),
  143. tempdb.dbo.fn_createindex_includedcols(i.index_handle)
  144. FROM sys.dm_db_missing_index_details i
  145. INNER JOIN master.sys.databases m ON i.database_id = m.database_id
  146. INNER JOIN sys.dm_db_missing_index_groups g ON i.index_handle = g.index_handle
  147. INNER JOIN sys.dm_db_missing_index_group_stats s ON s.group_handle = g.index_group_handle
  148. WHERE i.database_id > 4
  149. INSERT INTO #IndexRedundant
  150. SELECT I.DBName, I.[Table], I.[Ix_Name], i.[ix_handle], I.[KeyCols], I.[IncludedCols], I2.[Ix_Name]
  151. FROM #IndexCreation I
  152. INNER JOIN #IndexCreation I2 ON I.[database_id] = I2.[database_id] AND I.[Table] = I2.[Table] AND I.[Ix_Name] <> I2.[Ix_Name]
  153. AND (((I.KeyColsOrdered <> I2.KeyColsOrdered OR I.[IncludedColsOrdered] <> I2.[IncludedColsOrdered])
  154. AND ((CASE WHEN I.[IncludedColsOrdered] IS NULL THEN I.KeyColsOrdered ELSE I.KeyColsOrdered + ',' + I.[IncludedColsOrdered] END) = (CASE WHEN I2.[IncludedColsOrdered] IS NULL THEN I2.KeyColsOrdered ELSE I2.KeyColsOrdered + ',' + I2.[IncludedColsOrdered] END)
  155. OR I.[AllCols] = I2.[AllCols]))
  156. OR (I.KeyColsOrdered <> I2.KeyColsOrdered AND I.[IncludedColsOrdered] = I2.[IncludedColsOrdered])
  157. OR (I.KeyColsOrdered = I2.KeyColsOrdered AND I.[IncludedColsOrdered] <> I2.[IncludedColsOrdered]))
  158. WHERE I.[Score] >= 100000
  159. AND I2.[Score] >= 100000
  160. GROUP BY I.DBName, I.[Table], I.[Ix_Name], I.[ix_handle], I.[KeyCols], I.[IncludedCols], I2.[Ix_Name]
  161. ORDER BY I.DBName, I.[Table], I.[Ix_Name]
  162. IF (SELECT COUNT(*) FROM #IndexCreation WHERE [Score] >= 100000) > 0
  163. BEGIN
  164. SELECT 'Missing_Indexes' AS [Information], IC.DBName AS [Database_Name], IC.[Table] AS [Table_Name], CONVERT(bigint,[Score]) AS [Score], [User_Hits_on_Missing_Index],
  165. [Estimated_Improvement_Percent], [Avg_Total_User_Cost], [Unique_Compiles], IC.[KeyCols], IC.[IncludedCols], IC.[Ix_Name] AS [Index_Name],
  166. SUBSTRING((SELECT ',' + IR.[Redundant_With] FROM #IndexRedundant IR
  167. WHERE IC.DBName = IR.DBName AND IC.[Table] = IR.[Table] AND IC.[ix_handle] = IR.[ix_handle]
  168. ORDER BY IR.[Redundant_With]
  169. FOR XML PATH('')), 2, 8000) AS [Possibly_Redundant_With]
  170. FROM #IndexCreation IC
  171. WHERE [Score] >= 100000
  172. ORDER BY IC.DBName, IC.[Score] DESC, IC.[User_Hits_on_Missing_Index], IC.[Estimated_Improvement_Percent];
  173. SELECT DISTINCT 'Possibly_redundant_IXs_in_list' AS Comments, I.DBName AS [Database_Name], I.[Table] AS [Table_Name],
  174. I.[Ix_Name] AS [Index_Name], I.[KeyCols], I.[IncludedCols]
  175. FROM #IndexRedundant I
  176. ORDER BY I.DBName, I.[Table], I.[Ix_Name]
  177. END
  178. ELSE
  179. BEGIN
  180. SELECT 'Missing_Indexes' AS [Information], 'None' AS [Comment]
  181. END;
  182. IF (SELECT COUNT(*) FROM #IndexCreation IC WHERE IC.[IncludedCols] IS NULL AND IC.[Score] >= 100000) > 0
  183. BEGIN
  184. PRINT CHAR(10) + '/* Generated on ' + CONVERT (VARCHAR, GETDATE()) + ' in ' + @@SERVERNAME + ' */' + CHAR(10)
  185. PRINT '--############# Indexes creation statements #############' + CHAR(10)
  186. DECLARE cIC CURSOR FAST_FORWARD FOR
  187. SELECT '-- User Hits on Missing Index ' + IC.[Ix_Name] + ': ' + CONVERT(VARCHAR(20),IC.[User_Hits_on_Missing_Index]) + CHAR(10) +
  188. '-- Estimated Improvement Percent: ' + CONVERT(VARCHAR(6),IC.[Estimated_Improvement_Percent]) + CHAR(10) +
  189. '-- Average Total User Cost: ' + CONVERT(VARCHAR(50),IC.[Avg_Total_User_Cost]) + CHAR(10) +
  190. '-- Unique Compiles: ' + CONVERT(VARCHAR(50),IC.[Unique_Compiles]) + CHAR(10) +
  191. '-- Score: ' + CONVERT(VARCHAR(20),CONVERT(bigint,IC.[Score])) +
  192. CASE WHEN (SELECT COUNT(IR.[Redundant_With]) FROM #IndexRedundant IR
  193. WHERE IC.DBName = IR.DBName AND IC.[Table] = IR.[Table] AND IC.[ix_handle] = IR.[ix_handle]) > 0
  194. THEN CHAR(10) + '-- Possibly Redundant with Missing Index(es): ' + SUBSTRING((SELECT ',' + IR.[Redundant_With] FROM #IndexRedundant IR
  195. WHERE IC.DBName = IR.DBName AND IC.[Table] = IR.[Table] AND IC.[ix_handle] = IR.[ix_handle]
  196. FOR XML PATH('')), 2, 8000)
  197. ELSE '' END +
  198. CHAR(10) + 'USE ' + QUOTENAME(IC.DBName) + CHAR(10) + 'GO' + CHAR(10) + 'IF EXISTS (SELECT name FROM sysindexes WHERE name = N''' +
  199. IC.[Ix_Name] + ''') DROP INDEX ' + IC.[Table] + '.' +
  200. IC.[Ix_Name] + ';' + CHAR(10) + 'GO' + CHAR(10) + 'CREATE INDEX ' +
  201. IC.[Ix_Name] + ' ON ' + IC.[Table] + ' (' + IC.[KeyCols] + CASE WHEN @editionCheck = 1 THEN ') WITH (ONLINE = ON);' ELSE ');' END + CHAR(10) + 'GO' + CHAR(10)
  202. FROM #IndexCreation IC
  203. WHERE IC.[IncludedCols] IS NULL AND IC.[Score] >= 100000
  204. ORDER BY IC.DBName, IC.[Table], IC.[Ix_Name]
  205. OPEN cIC
  206. FETCH NEXT FROM cIC INTO @IC
  207. WHILE @@FETCH_STATUS = 0
  208. BEGIN
  209. PRINT @IC
  210. FETCH NEXT FROM cIC INTO @IC
  211. END
  212. CLOSE cIC
  213. DEALLOCATE cIC
  214. END;
  215. IF (SELECT COUNT(*) FROM #IndexCreation IC WHERE IC.[IncludedCols] IS NOT NULL AND IC.[Score] >= 100000) > 0
  216. BEGIN
  217. PRINT CHAR(10) + '/* Generated on ' + CONVERT (VARCHAR, GETDATE()) + ' in ' + @@SERVERNAME + ' */' + CHAR(10)
  218. PRINT '--############# Covering indexes creation statements #############' + CHAR(10)
  219. DECLARE cICWI CURSOR FAST_FORWARD FOR
  220. SELECT '-- User Hits on Missing Index ' + IC.[Ix_Name] + ': ' + CONVERT(VARCHAR(20),IC.[User_Hits_on_Missing_Index]) + CHAR(10) +
  221. '-- Estimated Improvement Percent: ' + CONVERT(VARCHAR(6),IC.[Estimated_Improvement_Percent]) + CHAR(10) +
  222. '-- Average Total User Cost: ' + CONVERT(VARCHAR(50),IC.[Avg_Total_User_Cost]) + CHAR(10) +
  223. '-- Unique Compiles: ' + CONVERT(VARCHAR(50),IC.[Unique_Compiles]) + CHAR(10) +
  224. '-- Score: ' + CONVERT(VARCHAR(20),CONVERT(bigint,IC.[Score])) +
  225. CASE WHEN (SELECT COUNT(IR.[Redundant_With]) FROM #IndexRedundant IR
  226. WHERE IC.DBName = IR.DBName AND IC.[Table] = IR.[Table] AND IC.[ix_handle] = IR.[ix_handle]) > 0
  227. THEN CHAR(10) + '-- Possibly Redundant with Missing Index(es): ' + SUBSTRING((SELECT ',' + IR.[Redundant_With] FROM #IndexRedundant IR
  228. WHERE IC.DBName = IR.DBName AND IC.[Table] = IR.[Table] AND IC.[ix_handle] = IR.[ix_handle]
  229. FOR XML PATH('')), 2, 8000)
  230. ELSE '' END +
  231. CHAR(10) + 'USE ' + QUOTENAME(IC.DBName) + CHAR(10) + 'GO' + CHAR(10) + 'IF EXISTS (SELECT name FROM sysindexes WHERE name = N''' +
  232. IC.[Ix_Name] + ''') DROP INDEX ' + IC.[Table] + '.' +
  233. IC.[Ix_Name] + ';' + CHAR(10) + 'GO' + CHAR(10) + 'CREATE INDEX ' +
  234. IC.[Ix_Name] + ' ON ' + IC.[Table] + ' (' + IC.[KeyCols] + CASE WHEN @editionCheck = 1 THEN ') WITH (ONLINE = ON);' ELSE ');' END + CHAR(10) + 'GO' + CHAR(10)
  235. FROM #IndexCreation IC
  236. WHERE IC.[IncludedCols] IS NOT NULL AND IC.[Score] >= 100000
  237. ORDER BY IC.DBName, IC.[Table], IC.[Ix_Name]
  238. OPEN cICWI
  239. FETCH NEXT FROM cICWI INTO @ICWI
  240. WHILE @@FETCH_STATUS = 0
  241. BEGIN
  242. PRINT @ICWI
  243. FETCH NEXT FROM cICWI INTO @ICWI
  244. END
  245. CLOSE cICWI
  246. DEALLOCATE cICWI
  247. END;
  248. DROP TABLE #IndexCreation
  249. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_keycols'')) DROP FUNCTION dbo.fn_createindex_keycols')
  250. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_allcols'')) DROP FUNCTION dbo.fn_createindex_allcols')
  251. EXEC ('USE tempdb; IF EXISTS (SELECT [object_id] FROM tempdb.sys.objects (NOLOCK) WHERE [object_id] = OBJECT_ID(''tempdb.dbo.fn_createindex_includedcols'')) DROP FUNCTION dbo.fn_createindex_includedcols')
  252. GO