0

I have the following SQL script to use for our Microsoft Dynamics NAV Database (MS SQL Server). It looks for missing indexes in the database. We have the problem that the script also finds some tables which already have this index just missing a long list of "included columns" (most of the time 70%+ of the columns of the table). But we do not want to add those with like 10+ included columns. So I tried to filter out already existing indexes by using db name, table name and equality columns. Sadly I couldn't get it working and also not with the help of Chat GPT. So maybe someone can help me here :)

USE [master]
GO

SET NOCOUNT ON
GO

-- >> Missing Indexes <<
 
-- Filter-Values
declare @threshold_occurrence int, @threshold_impact int, @threshold_age int, @threshold_include int

set @threshold_occurrence = 10 -- Avg. Proposals per Day online; change on demand
set @threshold_impact = 90 -- Percent; change on demand
set @threshold_age = 14 -- last proposal date; change on demand
set @threshold_include = 10 -- maximum number of included columns used in CREATE; change on demand

--

declare @daysonline int
select @daysonline = datediff(dd, create_date, getdate()) + 1 from sys.databases where name = 'tempdb'
declare @dbname sysname, @tsql nvarchar(max)
declare db_cur cursor fast_forward for
  select [name] from sys.databases 
  where [state] = 0 
    --and [name] not in ('master', 'model', 'msdb', 'tempdb', 'SSI')  -- exclude databases
    and [name] in ('XXXX') -- include databases 
  order by [name]
open db_cur
fetch next from db_cur into @dbname
while @@FETCH_STATUS = 0
begin
    set @tsql =
    '
    USE [' + @dbname + ']
    SELECT mig.index_handle, migs.user_seeks, migs.last_user_seek, migs.user_scans, migs.last_user_scan, migs.avg_user_impact,
                                CONVERT(decimal(18,2), migs.user_seeks * migs.avg_total_user_cost * (migs.avg_user_impact * 0.01)) AS [index_advantage],
                                db_name(mid.database_id) as db, object_name(mid.object_id) as object, mid.equality_columns, mid.inequality_columns, mid.included_columns,
                                ''CREATE INDEX [ssi_'' + convert(varchar, mig.index_handle) + ''_'' + replace(replace(replace(convert(varchar(30), getdate(), 120), ''-'', ''''), '':'', ''''), '' '', ''_'') + ''] ON ['' + OBJECT_SCHEMA_NAME(mid.[object_id]) + ''].['' +  object_name(mid.object_id) + ''] '' + ''('' +
                                  CASE WHEN mid.equality_columns is not null THEN mid.equality_columns ELSE '''' END +
                                  CASE WHEN mid.equality_columns is null AND mid.inequality_columns is not null THEN mid.inequality_columns ELSE '''' END +
                                  CASE WHEN mid.equality_columns is not null AND mid.inequality_columns is not null THEN '', '' + mid.inequality_columns ELSE '''' END + '')'' +
                                  CASE WHEN (mid.included_columns is not null) AND ( (SELECT LEN(mid.included_columns) - LEN(REPLACE(mid.included_columns, '','', ''''))) < ' + convert(varchar(5), @threshold_include) + ' ) 
                                  THEN '' INCLUDE ('' + mid.included_columns + '') '' 
                                  ELSE '''' 
                                  END + '' WITH (MAXDOP = 64, ONLINE = OFF, DROP_EXISTING = OFF, ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON);''  COLLATE DATABASE_DEFAULT as tsql
    FROM sys.dm_db_missing_index_group_stats AS migs
    INNER JOIN sys.dm_db_missing_index_groups AS mig
                   ON (migs.group_handle = mig.index_group_handle)
    INNER JOIN sys.dm_db_missing_index_details AS mid
                   ON (mig.index_handle = mid.index_handle)
    WHERE (mid.database_id = db_id())
    AND ((migs.user_seeks / ' + convert(varchar(5), @daysonline) + ') >= ' + convert(varchar(5), @threshold_occurrence) + ')  
    AND (migs.avg_user_impact >= ' + convert(varchar(5), @threshold_impact) + ') 
    AND (datediff(dd, migs.last_user_seek, getdate()) <= ' + convert(varchar(5), @threshold_age) + ') -- propsed within period 
    ORDER BY object_name(mid.object_id), replace(replace((mid.equality_columns + mid.inequality_columns), '', '', ''''), '','', ''''), mid.included_columns
    COLLATE DATABASE_DEFAULT;
    '

    --print @tsql
    exec sp_executesql @tsql

    fetch next from db_cur into @dbname
end
close db_cur
deallocate db_cur
GO

I tried merging it with this script which finds existing indexes of a table:

DECLARE @TableName NVARCHAR(50) = 'Cronus$Sales Price'

;WITH CTE_INDEX_DATA AS (
       SELECT
              SCHEMA_DATA.name AS schema_name,
              TABLE_DATA.name AS table_name,
              INDEX_DATA.name AS index_name,
              STUFF((SELECT  ', ' + COLUMN_DATA_KEY_COLS.name + ' ' + CASE WHEN INDEX_COLUMN_DATA_KEY_COLS.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END -- Include column order (ASC / DESC)
                                  FROM    sys.tables AS T
                                                INNER JOIN sys.indexes INDEX_DATA_KEY_COLS
                                                ON T.object_id = INDEX_DATA_KEY_COLS.object_id
                                                INNER JOIN sys.index_columns INDEX_COLUMN_DATA_KEY_COLS
                                                ON INDEX_DATA_KEY_COLS.object_id = INDEX_COLUMN_DATA_KEY_COLS.object_id
                                                AND INDEX_DATA_KEY_COLS.index_id = INDEX_COLUMN_DATA_KEY_COLS.index_id
                                                INNER JOIN sys.columns COLUMN_DATA_KEY_COLS
                                                ON T.object_id = COLUMN_DATA_KEY_COLS.object_id
                                                AND INDEX_COLUMN_DATA_KEY_COLS.column_id = COLUMN_DATA_KEY_COLS.column_id
                                  WHERE   INDEX_DATA.object_id = INDEX_DATA_KEY_COLS.object_id
                                                AND INDEX_DATA.index_id = INDEX_DATA_KEY_COLS.index_id
                                                AND INDEX_COLUMN_DATA_KEY_COLS.is_included_column = 0
                                  ORDER BY INDEX_COLUMN_DATA_KEY_COLS.key_ordinal
                                  FOR XML PATH('')), 1, 2, '') AS key_column_list ,
          STUFF(( SELECT  ', ' + COLUMN_DATA_INC_COLS.name
                                  FROM    sys.tables AS T
                                                INNER JOIN sys.indexes INDEX_DATA_INC_COLS
                                                ON T.object_id = INDEX_DATA_INC_COLS.object_id
                                                INNER JOIN sys.index_columns INDEX_COLUMN_DATA_INC_COLS
                                                ON INDEX_DATA_INC_COLS.object_id = INDEX_COLUMN_DATA_INC_COLS.object_id
                                                AND INDEX_DATA_INC_COLS.index_id = INDEX_COLUMN_DATA_INC_COLS.index_id
                                                INNER JOIN sys.columns COLUMN_DATA_INC_COLS
                                                ON T.object_id = COLUMN_DATA_INC_COLS.object_id
                                                AND INDEX_COLUMN_DATA_INC_COLS.column_id = COLUMN_DATA_INC_COLS.column_id
                                  WHERE   INDEX_DATA.object_id = INDEX_DATA_INC_COLS.object_id
                                                AND INDEX_DATA.index_id = INDEX_DATA_INC_COLS.index_id
                                                AND INDEX_COLUMN_DATA_INC_COLS.is_included_column = 1
                                  ORDER BY INDEX_COLUMN_DATA_INC_COLS.key_ordinal
                                  FOR XML PATH('')), 1, 2, '') AS include_column_list,
       INDEX_DATA.is_disabled -- Check if index is disabled before determining which dupe to drop (if applicable)
       FROM sys.indexes INDEX_DATA
       INNER JOIN sys.tables TABLE_DATA
       ON TABLE_DATA.object_id = INDEX_DATA.object_id
       INNER JOIN sys.schemas SCHEMA_DATA
       ON SCHEMA_DATA.schema_id = TABLE_DATA.schema_id
       WHERE TABLE_DATA.is_ms_shipped = 0
       AND INDEX_DATA.type_desc IN ('NONCLUSTERED', 'CLUSTERED')
       --AND INDEX_DATA.name NOT LIKE '%$%'
       AND [TABLE_DATA].[object_id] = (select object_id from sys.objects where name = @TableName)
)
SELECT
       [DUPE1].*
       ,(SELECT COUNT(*) FROM CTE_INDEX_DATA DUPE2
         WHERE DUPE1.schema_name = DUPE2.schema_name
         AND DUPE1.table_name = DUPE2.table_name
         AND (DUPE1.key_column_list LIKE LEFT(DUPE2.key_column_list, LEN(DUPE1.key_column_list)) OR DUPE2.key_column_list LIKE LEFT(DUPE1.key_column_list, LEN(DUPE2.key_column_list)))
         AND DUPE1.index_name <> DUPE2.index_name) AS [CountOfSimilarIndexes]
FROM CTE_INDEX_DATA DUPE1
--WHERE EXISTS      /*Einschränkung auf Indexe, die in ähnlicher Form noch mal vorliegen*/
--(SELECT * FROM CTE_INDEX_DATA DUPE2
-- WHERE DUPE1.schema_name = DUPE2.schema_name
-- AND DUPE1.table_name = DUPE2.table_name
-- AND (DUPE1.key_column_list LIKE LEFT(DUPE2.key_column_list, LEN(DUPE1.key_column_list)) OR DUPE2.key_column_list LIKE LEFT(DUPE1.key_column_list, LEN(DUPE2.key_column_list)))
-- AND DUPE1.index_name <> DUPE2.index_name)
ORDER BY DUPE1.table_name, DUPE1.key_column_list
  • ChatGpt: Not working scripts
  • Merging with existing script: resulting in errors/not working
  • trying to change the script myself: resulting in errors/not working
  • google/stackoverflow = index creation is not working good, no solution found...
3
  • 2
    Honestly I wouldn't bother with the Missing Indexes feature. It's just a general indication that you should rethink your indexes, not to be relied on to actually create indexes. Commented Jun 26, 2023 at 13:38
  • 1
    I agree with @Charlieface but if you are after automated scripts then why not use existing tools e.g. BlitzIndex and other similar tools? Commented Jun 27, 2023 at 7:10
  • It is not practical to create indexes on the fly. Instead, let's see sever of the selects and help you formulate an optimal set of indexes for them. Commented Jun 28, 2023 at 0:59

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.