Better Index Usage DMV

Slightly altered Aaron Bertrand’s “Better Index Usage DMV” to script out CREATE INDEX-statements.

SELECT
 d.\[object\_id\],
 s = OBJECT\_SCHEMA\_NAME(d.\[object\_id\]),
 o = OBJECT\_NAME(d.\[object\_id\]),
 d.equality\_columns,
 d.inequality\_columns,
 d.included\_columns,
 s.unique\_compiles,
 s.user\_seeks, s.last\_user\_seek,
 s.user\_scans, s.last\_user\_scan,
 'CREATE INDEX \[IX\_' + OBJECT\_NAME(d.OBJECT\_ID,d.database\_id) + '\_'
 + REPLACE(REPLACE(REPLACE(ISNULL(d.equality\_columns,''),', ','\_'),'\[',''),'\]','') +
 CASE
 WHEN d.equality\_columns IS NOT NULL AND d.inequality\_columns IS NOT NULL THEN '\_'
 ELSE ''
 END
 + REPLACE(REPLACE(REPLACE(ISNULL(d.inequality\_columns,''),', ','\_'),'\[',''),'\]','')
 + '\]'
 + ' ON ' + d.statement
 + ' (' + ISNULL (d.equality\_columns,'')
 + CASE WHEN d.equality\_columns IS NOT NULL AND d.inequality\_columns IS NOT NULL THEN ',' ELSE
 '' END
 + ISNULL (d.inequality\_columns, '')
 + ')'
 + ISNULL (' INCLUDE (' + d.included\_columns + ')', '') AS Create\_Statement
 INTO #candidates
 FROM sys.dm\_db\_missing\_index\_details AS d
 INNER JOIN sys.dm\_db\_missing\_index\_groups AS g
 ON d.index\_handle = g.index\_handle
 INNER JOIN sys.dm\_db\_missing\_index\_group\_stats AS s
 ON g.index\_group\_handle = s.group\_handle
 WHERE d.database\_id = DB\_ID()
 AND OBJECTPROPERTY(d.\[object\_id\], 'IsMsShipped') = 0;
 CREATE TABLE #planops
 (
 o INT,
 i INT,
 h VARBINARY(64),
 uc INT,
 Scan\_Ops INT,
 Seek\_Ops INT,
 Update\_Ops INT
 );

DECLARE @sql NVARCHAR(MAX) = N'';
with xmlnamespaces (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
 insert #planops
 select o = coalesce(T1.o, T2.o),
 i = coalesce(T1.i, T2.i),
 h = coalesce(T1.h, T2.h),
 uc = coalesce(T1.uc, T2.uc),
 Scan\_Ops = isnull(T1.Scan\_Ops, 0),
 Seek\_Ops = isnull(T1.Seek\_Ops, 0),
 Update\_Ops = isnull(T2.Update\_Ops, 0)
 from
 (
 select o = i.object\_id,
 i = i.index\_id,
 h = t.plan\_handle,
 uc = t.usecounts,
 Scan\_Ops = sum(case when t.LogicalOp in ('Index Scan', 'Clustered Index Scan') then 1 else 0 end),
 Seek\_Ops = sum(case when t.LogicalOp in ('Index Seek', 'Clustered Index Seek') then 1 else 0 end)
 from (
 select
 r.n.value('@LogicalOp', 'varchar(100)') as LogicalOp,
 o.n.value('@Index', 'sysname') as IndexName,
 pl.plan\_handle,
 pl.usecounts
 from sys.dm\_exec\_cached\_plans as pl
 cross apply sys.dm\_exec\_query\_plan(pl.plan\_handle) AS p
 cross apply p.query\_plan.nodes('//RelOp') as r(n)
 cross apply r.n.nodes('\*/Object') as o(n)
 where p.dbid = db\_id()
 and p.query\_plan is not null
 ) as t
 inner join sys.indexes as i
 on t.IndexName = quotename(i.name)
 where t.LogicalOp in ('Index Scan', 'Clustered Index Scan', 'Index Seek', 'Clustered Index Seek')
 and exists (select 1 from #candidates as c where c.object\_id = i.object\_id)
 group by i.object\_id,
 i.index\_id,
 t.plan\_handle,
 t.usecounts
 ) as T1
 full outer join
 (
 select o = i.object\_id,
 i = i.index\_id,
 h = t.plan\_handle,
 uc = t.usecounts,
 Update\_Ops = count(\*)
 from (
 select
 o.n.value('@Index', 'sysname') as IndexName,
 pl.plan\_handle,
 pl.usecounts
 from sys.dm\_exec\_cached\_plans as pl
 cross apply sys.dm\_exec\_query\_plan(pl.plan\_handle) AS p
 cross apply p.query\_plan.nodes('//Update') as r(n)
 cross apply r.n.nodes('Object') as o(n)
 where p.dbid = db\_id()
 and p.query\_plan is not null
 ) as t
 inner join sys.indexes as i
 on t.IndexName = quotename(i.name)
 where exists
 (
 select 1 from #candidates as c where c.\[object\_id\] = i.\[object\_id\]
 )
 and i.index\_id > 0
 group by i.object\_id,
 i.index\_id,
 t.plan\_handle,
 t.usecounts
 ) as T2
 on T1.o = T2.o and
 T1.i = T2.i and
 T1.h = T2.h and
 T1.uc = T2.uc;
 SELECT \[object\_id\], index\_id, user\_seeks, user\_scans, user\_lookups, user\_updates
 INTO #indexusage
 FROM sys.dm\_db\_index\_usage\_stats AS s
 WHERE database\_id = DB\_ID()
 AND EXISTS (SELECT 1 FROM #candidates WHERE \[object\_id\] = s.\[object\_id\]);
 ;WITH x AS
 (
 SELECT
 c.\[object\_id\],
 potential\_read\_ops = SUM(c.user\_seeks + c.user\_scans),
 \[write\_ops\] = SUM(iu.user\_updates),
 \[read\_ops\] = SUM(iu.user\_scans + iu.user\_seeks + iu.user\_lookups),
 \[write:read ratio\] = CONVERT(DECIMAL(18,2), SUM(iu.user\_updates)\*1.0 /
 SUM(iu.user\_scans + iu.user\_seeks + iu.user\_lookups)),
 current\_plan\_count = po.h,
 current\_plan\_use\_count = po.uc
 FROM
 #candidates AS c
 LEFT OUTER JOIN
 #indexusage AS iu
 ON c.\[object\_id\] = iu.\[object\_id\]
 LEFT OUTER JOIN
 (
 SELECT o, h = COUNT(h), uc = SUM(uc)
 FROM #planops GROUP BY o
 ) AS po
 ON c.\[object\_id\] = po.o
 GROUP BY c.\[object\_id\], po.h, po.uc
 )
 SELECT \[object\] = QUOTENAME(c.s) + '.' + QUOTENAME(c.o),
 c.equality\_columns,
 c.inequality\_columns,
 c.included\_columns,
 x.potential\_read\_ops,
 x.write\_ops,
 x.read\_ops,
 x.\[write:read ratio\],
 x.current\_plan\_count,
 x.current\_plan\_use\_count,
 c.create\_statement
 FROM #candidates AS c
 INNER JOIN x
 ON c.\[object\_id\] = x.\[object\_id\]
 ORDER BY x.\[write:read ratio\];

/\*
 drop table #candidates
 drop table #indexusage
 drop table #planops
 \*/

{{ < / hightlight > }}