IF OBJECT_ID('upd.alignIndexes') IS NOT NULL
    DROP PROCEDURE [upd].alignIndexes;
GO

/*============================================================================= 

OCTPDBA-414
This procedure is used to align indexes in a pharmacy against a template, stored in the table [arizona].[upd].[IndexesAlignementActions]

Note that:
    * Matching is always made from the columns of the index, never on the name of the index.
        The reason is that we might have difference in the name of the index between several servers.
    * If an index exists with the name A, but your list contains a similar index with the name B, index A will be renamed to B with sp_rename

Parameters
----------------------
@in_debug   
Debug level. Default 0
    0 = no debug, execute commands
    1 = print commands and output a matrix showing the found state
    2 = only print commands and output a matrix showing the found state

@in_dropUnreferencedIndexes 
Drop indexes not referenced. Default 0
    This flag govern behavior when we have a index on a table that exists in [IndexesAlignementActions], but that this specific index is not present in [IndexesAlignementActions]
    0 = Leave the unknown index in place
    1 = Drop the unknown index

Context
----------------------
This proc can be called at anytime to align indexes against those referenced in [arizona].[upd].[IndexesAlignementActions]

Creation : 03.02.2023 / TSC
Modifications:

=============================================================================*/
CREATE PROCEDURE [upd].[alignIndexes]
    @in_debug INT = 0,
    @in_dropUnreferencedIndexes BIT = 0
AS
BEGIN
    SET NOCOUNT ON;

    /* variables */
    DECLARE @query VARCHAR(MAX); /* for dynamic sql */
    DECLARE @outputStatement BIT = CASE /* Do we want to have the commands printed out ? */
                                       WHEN @in_debug IN ( 1, 2 ) THEN
                                           1
                                       ELSE
                                           0
                                   END;
    DECLARE @executeStatement BIT = CASE /* Do we want to execute the commands ? */
                                        WHEN @in_debug IN ( 0, 1 ) THEN
                                            1
                                        ELSE
                                            0
                                    END;
    DECLARE @schemaName VARCHAR(100); /* used in cursor */
    DECLARE @tableName VARCHAR(100); /* used in cursor */
    DECLARE @indexFK INT; /* used in cursor */
    DECLARE @msg VARCHAR(MAX) = '';
    DECLARE @indexesToMaintains TABLE /* working representation of upd.IA_IndexesAlignementActions */
    (
        [IA_indexesAlignementActions] INT NULL,           /* FK to table upd.IA_IndexesAlignementActions */
        [SchemaName] VARCHAR(100) NOT NULL,               /* the schema the table is part of */
        [TableName] VARCHAR(100) NOT NULL,                /* On which table is the index, whithout the schema */
        columnsName_inp VARCHAR(MAX) NOT NULL,            /* list all the columns of the index, in the correct orders */
        IA_index_options VARCHAR(MAX) NULL,               /* index options */
        isClustered_inp BIT NOT NULL
            DEFAULT 0,                                    /* Is the indexe a clustered index ? */
        isUnique_inp BIT NOT NULL
            DEFAULT 0,                                    /* Is the index unique ? */
        includedColumns_inp VARCHAR(MAX) NULL,            /* list all the included columns the index should have. only relevant for idx not to be dropped */
        isToBeDeleted_inp BIT NOT NULL
            DEFAULT 0,                                    /* must the script drop this index ? */
        isToBeRecreated_inp BIT NOT NULL
            DEFAULT 0,                                    /* must the script recreate this index ? 
                                                             If an existing index differs from the definition, it will be dropped and the correct one created. 
                                                             Use this only to force the re-creation even if the structure is identical  */
        expectedIndexName_inp VARCHAR(MAX) NULL,          /* the name the index should have, used to determine if a rename of the existing index should be done */
        indexName VARCHAR(MAX) NULL,                      /* computed by the script. The index name on this system, as indexes name can be different accross systems*/
        isIndexFound_comp BIT NOT NULL
            DEFAULT 0,                                    /* computed by the script. set to 1 if the index is present on the server */
        isToBeCreated_comp BIT NULL
            DEFAULT 0,                                    /* computed by the script. does this index will be created on the server ? */
        isToBeRenamed_comp BIT NULL
            DEFAULT 0,                                    /* computed by the script. does this index will be renamed ? */
        isToBeDeleted_comp BIT NULL
            DEFAULT 0,                                    /* computed by the script. does this index will be deleted ? */
        isToBeRecreated_comp BIT NULL
            DEFAULT 0,                                    /* computed by the script. does this index will be re-created ? */
        includedColumnsMatch_internal BIT NULL,           /* computed ba the script. Are the expected and actual included columns identical ? */
        actualColumns_internal VARCHAR(MAX) NULL,         /* the actual columns in the existing index */
        actualIncludedColumns_internal VARCHAR(MAX) NULL, /* the actual columns in the INCLUDE part of the index */
        statusMsg_internal VARCHAR(MAX) NULL              /* Message, used for debug */
    );


    /* fetch index to align from upd.IA_IndexesAlignementActions */
    INSERT INTO @indexesToMaintains
    (
        [IA_indexesAlignementActions],
        [SchemaName],
        [TableName],
        [expectedIndexName_inp],
        [columnsName_inp],
        [includedColumns_inp],
        [isClustered_inp],
        [isUnique_inp],
        [isToBeDeleted_inp],
        [isToBeRecreated_inp],
        [IA_index_options]
    )
    SELECT ia.[IA_indexesAlignementActionsID],
           ia.[IA_schemaName],
           ia.[IA_tableName],
           ia.[IA_expectedIndexName],
           ia.[IA_columnsName],
           ia.[IA_includedColumns],
           ia.[IA_isClustered],
           ia.[IA_isUnique],
           ia.[IA_isToBeDeleted],
           ia.[IA_isToBeRecreated],
           ia.[IA_indexOptions]
    FROM [upd].[IA_IndexesAlignementActions] ia
    WHERE ia.[IA_executionDate] IS NULL;


    /* logic checks on index definition table */
    SELECT @msg
        = @msg + 'Duplicate index (on expected index name) found in indexes definition. Table ['
          + [itm].[IA_schemaName] + '].[' + [itm].[IA_tableName] + '], expected index name ['
          + [itm].[IA_expectedIndexName] + ']' + CHAR(13) + CHAR(10)
    FROM [upd].[IA_IndexesAlignementActions] [itm]
        JOIN
        (
            SELECT [ii].[IA_expectedIndexName] AS [key]
            FROM [upd].[IA_IndexesAlignementActions] [ii]
            GROUP BY [ii].[IA_expectedIndexName]
            HAVING COUNT(1) > 1
        ) d
            ON d.[key] = [itm].[IA_expectedIndexName]
    GROUP BY [itm].[IA_schemaName],
             [itm].[IA_tableName],
             [itm].[IA_expectedIndexName];

    --duplicate index key
    SELECT @msg
        = @msg + 'Duplicate index (on column definition) found in indexes definition. Table [' + [itm].[IA_schemaName]
          + '].[' + [itm].[IA_tableName] + '], columns in index: ' + [itm].[IA_columnsName]
          + CASE
                WHEN [itm].[IA_includedColumns] IS NOT NULL THEN
                    ', included colum(s): ' + [itm].[IA_includedColumns]
                ELSE
                    ''
            END + CHAR(13) + CHAR(10)
    FROM [upd].[IA_IndexesAlignementActions] [itm]
        JOIN
        (
            SELECT [ii].[IA_columnsName] + '_' + ISNULL([ii].[IA_includedColumns], '') AS [key]
            FROM [upd].[IA_IndexesAlignementActions] [ii]
            GROUP BY [ii].[IA_columnsName] + '_' + ISNULL([ii].[IA_includedColumns], '')
            HAVING COUNT(1) > 1
        ) d
            ON d.[key] = [itm].[IA_columnsName] + '_' + ISNULL([itm].[IA_includedColumns], '')
    GROUP BY [itm].[IA_schemaName],
             [itm].[IA_tableName],
             [itm].[IA_columnsName],
             [itm].[IA_includedColumns];

    IF @msg <> ''
    BEGIN
        RAISERROR(@msg, 16, 4);
        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRANSACTION;
        END;
        RETURN 1;
    END;


    /* fetch the indexes name and non listed indexes on the current instance */
    IF CURSOR_STATUS('global','curIdxName')>=-1
    BEGIN
     DEALLOCATE curIdxName
    END
    DECLARE curIdxName CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
    SELECT DISTINCT
           [itm].[IA_schemaName],
           [itm].[IA_tableName],
           itm.[IA_indexesAlignementActionsID]
    FROM [upd].[IA_IndexesAlignementActions] [itm];

    OPEN curIdxName;

    FETCH NEXT FROM curIdxName
    INTO @schemaName,
         @tableName,
         @indexFK;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        /* map the list to the actual indexes */
        WITH ColInfo
        AS (SELECT o.[name] AS TblName,
                   s.[name] + '.' + o.[name] AS SchemaTbl,
                   i.[name] AS IndexName,
                   i.[is_primary_key] AS IsPrimaryKey,
                   i.[is_unique_constraint] AS IsUniqueConstraint,
                   i.[is_unique] AS IsUnique,
                   c.[name] AS ColName,
                   c.[is_computed] AS IsComputedCol,
                   ic.[is_included_column] AS IsIncludedCol,
                   ic.[key_ordinal],
                   i.[filter_definition] AS FilterDefinition,
                   i.type AS indexType /* 1=clustered, 2=non clustered */
            FROM [sys].[objects] o
                INNER JOIN [sys].[schemas] s
                    ON o.schema_id = s.schema_id
                INNER JOIN [sys].[columns] c
                    ON o.object_id = c.object_id
                INNER JOIN [sys].[indexes] i
                    ON c.object_id = i.object_id
                INNER JOIN [sys].[index_columns] ic
                    ON i.[index_id] = ic.[index_id]
                       AND o.object_id = ic.object_id
                       AND c.[column_id] = ic.[column_id])
        UPDATE [itm]
        SET [itm].[indexName] = i.[name],
            [itm].[includedColumnsMatch_internal] = CASE
                                                        WHEN ISNULL([itm].[includedColumns_inp], '') = ISNULL(
                                                                                                                 [idx_cols].[includedColumns],
                                                                                                                 ''
                                                                                                             ) THEN
                                                            1
                                                        ELSE
                                                            0
                                                    END,
            [itm].[actualIncludedColumns_internal] = [idx_cols].[includedColumns],
            [itm].[actualColumns_internal] = [idx_cols].[IndexColumns],
            [itm].[isIndexFound_comp] = 1,
            [itm].[isToBeCreated_comp] = 0,
            [itm].[isToBeRecreated_comp] = CASE
                                               WHEN idx_cols.indexType <> 1
                                                    AND [itm].isClustered_inp = 1
                                                    AND idx_cols.IsPrimaryKey = 0 THEN
                                                   1 /* existing index is not clustered and not the PK, recreate */
                                               ELSE
                                                   0
                                           END
        FROM [sys].[indexes] AS i WITH (NOLOCK)
            INNER JOIN [sys].[objects] o WITH (NOLOCK)
                ON i.object_id = o.object_id
            OUTER APPLY
        (
            SELECT DISTINCT
                   x.TblName,
                   x.IndexName,
                   x.IsPrimaryKey,
                   x.IsUniqueConstraint,
                   x.IsUnique,
                   c.IndexColumns,
                   ci.includedColumns,
                   cc.ComputedColumns,
                   x.FilterDefinition,
                   x.indexType
            FROM ColInfo x
                CROSS APPLY
            (
                SELECT STUFF(sq.strXML, 1, 2, '') AS IndexColumns
                FROM
                (
                    SELECT ', ' + ISNULL(x2.ColName, '')
                    FROM ColInfo x2
                    WHERE x.TblName = x2.TblName
                          AND x.IndexName = x2.IndexName
                          AND x2.IsIncludedCol = 0
                    ORDER BY x2.[key_ordinal]
                    FOR XML PATH('')
                ) sq(strXML)
            ) c
                OUTER APPLY
            (
                SELECT STUFF(sq.strXML, 1, 2, '') AS includedColumns
                FROM
                (
                    SELECT ', ' + ISNULL(x2.ColName, '')
                    FROM ColInfo x2
                    WHERE x.TblName = x2.TblName
                          AND x.IndexName = x2.IndexName
                          AND x2.IsIncludedCol = 1
                    ORDER BY x2.[key_ordinal]
                    FOR XML PATH('')
                ) sq(strXML)
            ) ci
                OUTER APPLY
            (
                SELECT STUFF(sq.strXML, 1, 2, '') AS ComputedColumns
                FROM
                (
                    SELECT ', ' + ISNULL(x2.ColName, '')
                    FROM ColInfo x2
                    WHERE x.TblName = x2.TblName
                          AND x.IndexName = x2.IndexName
                          AND x2.IsComputedCol = 1
                    ORDER BY x2.[key_ordinal]
                    FOR XML PATH('')
                ) sq(strXML)
            ) cc
            WHERE x.TblName = o.[name]
                  AND x.IndexName = i.[name]
        ) idx_cols
            LEFT OUTER JOIN
            (
                SELECT SCHEMA_NAME(tab.schema_id) + '.' + tab.[name] AS [table],
                       col.[column_id],
                       col.[name] AS [COLUMN_NAME],
                       CASE
                           WHEN fk.object_id IS NOT NULL THEN
                               '>-'
                           ELSE
                               NULL
                       END AS rel,
                       SCHEMA_NAME(pk_tab.schema_id) + '.' + pk_tab.[name] AS primary_table,
                       pk_col.[name] AS pk_column_name,
                       fk_cols.[constraint_column_id] AS NO,
                       fk.[name] AS fk_constraint_name,
                       tab.[name] AS tbl_name
                FROM [sys].[tables] tab
                    INNER JOIN [sys].[columns] col
                        ON col.object_id = tab.object_id
                    LEFT OUTER JOIN [sys].[foreign_key_columns] fk_cols
                        ON fk_cols.[parent_object_id] = tab.object_id
                           AND fk_cols.[parent_column_id] = col.[column_id]
                    LEFT OUTER JOIN [sys].[foreign_keys] fk
                        ON fk.object_id = fk_cols.[constraint_object_id]
                    LEFT OUTER JOIN [sys].[tables] pk_tab
                        ON pk_tab.object_id = fk_cols.[referenced_object_id]
                    LEFT OUTER JOIN [sys].[columns] pk_col
                        ON pk_col.[column_id] = fk_cols.[referenced_column_id]
                           AND pk_col.object_id = fk_cols.[referenced_object_id]
                WHERE fk.object_id IS NOT NULL
            ) fk
                ON fk.tbl_name = o.[name]
                   AND idx_cols.IndexColumns LIKE '%' + fk.[COLUMN_NAME] + '%'
            INNER JOIN @indexesToMaintains [itm]
                ON itm.[SchemaName] = @schemaName
                   AND itm.[TableName] = @tableName
                   AND itm.[columnsName_inp] COLLATE Latin1_General_CI_AI = [idx_cols].[IndexColumns] COLLATE Latin1_General_CI_AI
                   AND ISNULL([itm].[includedColumns_inp], '')COLLATE Latin1_General_CI_AI = ISNULL(
                                                                                                       [idx_cols].[includedColumns],
                                                                                                       ''
                                                                                                   )COLLATE Latin1_General_CI_AI
        WHERE o.type = 'U' -- user table
              --AND i.[index_id]               = s.[index_id]
              --AND s.[database_id]            = DB_ID()
              AND o.[name] = @tableName
              AND SCHEMA_NAME(o.schema_id) = @schemaName
        OPTION (RECOMPILE);


        /* Add the indexes not in the list but present on the server to the list */
        WITH ColInfo
        AS (SELECT o.[name] AS TblName,
                   SCHEMA_NAME(o.[schema_id]) AS [SchemaName],
                   s.[name] + '.' + o.[name] AS SchemaTbl,
                   i.[name] AS IndexName,
                   i.[is_primary_key] AS IsPrimaryKey,
                   i.[is_unique_constraint] AS IsUniqueConstraint,
                   i.[is_unique] AS IsUnique,
                   c.[name] AS ColName,
                   c.[is_computed] AS IsComputedCol,
                   ic.[is_included_column] AS IsIncludedCol,
                   ic.[key_ordinal],
                   i.[filter_definition] AS FilterDefinition
            FROM [sys].[objects] o
                INNER JOIN [sys].[schemas] s
                    ON o.schema_id = s.schema_id
                INNER JOIN [sys].[columns] c
                    ON o.object_id = c.object_id
                INNER JOIN [sys].[indexes] i
                    ON c.object_id = i.object_id
                INNER JOIN [sys].[index_columns] ic
                    ON i.[index_id] = ic.[index_id]
                       AND o.object_id = ic.object_id
                       AND c.[column_id] = ic.[column_id])
        INSERT INTO @indexesToMaintains
        (
            [SchemaName],
            [TableName],
            [columnsName_inp],
            [indexName],
            [isToBeDeleted_inp],
            [isToBeDeleted_comp],
            [isToBeRecreated_inp],
            [statusMsg_internal],
            [actualIncludedColumns_internal],
            [actualColumns_internal],
            [isIndexFound_comp],
            [isToBeCreated_comp],
            [expectedIndexName_inp]
        )
        SELECT DISTINCT
               @schemaName AS [SchemaName],
               @tableName AS [TableName],
               c.[IndexColumns] AS [columnsName],
               x.[IndexName] AS indexName,
               0 AS [isToBeDeleted_inp],
               CASE
                   WHEN EXISTS
                        (
                            SELECT 1
                            FROM @indexesToMaintains [itm]
                            WHERE itm.[expectedIndexName_inp] = x.[IndexName]
                                  OR ISNULL(itm.[columnsName_inp], '') <> ISNULL(itm.[actualColumns_internal], '')
                                  OR ISNULL(itm.[includedColumns_inp], '') <> ISNULL(
                                                                                        itm.[actualIncludedColumns_internal],
                                                                                        ''
                                                                                    )
                        ) THEN
                       1
                   WHEN @in_dropUnreferencedIndexes = 1 THEN
                       1
                   ELSE
                       0
               END AS [isToBeDeleted_comp],
               0 AS [isToBeRecreated_inp],
               CASE
                   WHEN EXISTS
                        (
                            SELECT 1
                            FROM @indexesToMaintains [itm]
                            WHERE itm.[expectedIndexName_inp] = x.[IndexName]
                                  OR ISNULL(itm.[columnsName_inp], '') <> ISNULL(itm.[actualColumns_internal], '')
                                  OR ISNULL(itm.[includedColumns_inp], '') <> ISNULL(
                                                                                        itm.[actualIncludedColumns_internal],
                                                                                        ''
                                                                                    )
                        ) THEN
                       'Index present locally not as expected, drop before creation'
                   WHEN @in_dropUnreferencedIndexes = 1 THEN
                       'Index present locally only'
                   ELSE
                       NULL
               END AS [statusMsg_internal],
               ci.[IncludedColumns] AS [actualIncludedColumns_internal],
               c.[IndexColumns] AS [actualColumns_internal],
               1 AS [isIndexFound_comp],
               0 AS [isToBeCreated_comp],
               x.[IndexName] AS [expectedIndexName_inp]
        FROM ColInfo x
            CROSS APPLY
        (
            SELECT IndexColumns = STUFF(sq.strXML, 1, 2, '')
            FROM
            (
                SELECT ', ' + ISNULL(x2.ColName, '')
                FROM ColInfo x2
                WHERE x.TblName = x2.TblName
                      AND x.IndexName = x2.IndexName
                      AND x2.IsIncludedCol = 0
                ORDER BY x2.[key_ordinal]
                FOR XML PATH('')
            ) sq(strXML)
        ) c
            OUTER APPLY
        (
            SELECT IncludedColumns = STUFF(sq.strXML, 1, 2, '')
            FROM
            (
                SELECT ', ' + ISNULL(x2.ColName, '')
                FROM ColInfo x2
                WHERE x.TblName = x2.TblName
                      AND x.IndexName = x2.IndexName
                      AND x2.IsIncludedCol = 1
                ORDER BY x2.[key_ordinal]
                FOR XML PATH('')
            ) sq(strXML)
        ) ci
            OUTER APPLY
        (
            SELECT ComputedColumns = STUFF(sq.strXML, 1, 2, '')
            FROM
            (
                SELECT ', ' + ISNULL(x2.ColName, '')
                FROM ColInfo x2
                WHERE x.TblName = x2.TblName
                      AND x.IndexName = x2.IndexName
                      AND x2.IsComputedCol = 1
                ORDER BY x2.[key_ordinal]
                FOR XML PATH('')
            ) sq(strXML)
        ) cc
            LEFT OUTER JOIN
            (
                SELECT SCHEMA_NAME(tab.schema_id) + '.' + tab.[name] AS [table],
                       col.[column_id],
                       col.[name] AS [COLUMN_NAME],
                       CASE
                           WHEN fk.object_id IS NOT NULL THEN
                               '>-'
                           ELSE
                               NULL
                       END AS rel,
                       SCHEMA_NAME(pk_tab.schema_id) + '.' + pk_tab.[name] AS primary_table,
                       pk_col.[name] AS pk_column_name,
                       fk_cols.[constraint_column_id] AS NO,
                       fk.[name] AS fk_constraint_name,
                       tab.[name] AS tbl_name
                FROM [sys].[tables] tab
                    INNER JOIN [sys].[columns] col
                        ON col.object_id = tab.object_id
                    LEFT OUTER JOIN [sys].[foreign_key_columns] fk_cols
                        ON fk_cols.[parent_object_id] = tab.object_id
                           AND fk_cols.[parent_column_id] = col.[column_id]
                    LEFT OUTER JOIN [sys].[foreign_keys] fk
                        ON fk.object_id = fk_cols.[constraint_object_id]
                    LEFT OUTER JOIN [sys].[tables] pk_tab
                        ON pk_tab.object_id = fk_cols.[referenced_object_id]
                    LEFT OUTER JOIN [sys].[columns] pk_col
                        ON pk_col.[column_id] = fk_cols.[referenced_column_id]
                           AND pk_col.object_id = fk_cols.[referenced_object_id]
                WHERE fk.object_id IS NOT NULL
            ) fk
                ON fk.tbl_name = x.[TblName]
                   AND [c].[IndexColumns] LIKE '%' + fk.[COLUMN_NAME] + '%'
        WHERE x.[SchemaName] = @schemaName
              AND x.[TblName] = @tableName
              AND NOT EXISTS
        (
            SELECT 1
            FROM @indexesToMaintains [itm2]
            WHERE 1 = 1
                  AND ISNULL(itm2.[actualColumns_internal], '') = ISNULL(c.[IndexColumns], '')
                  AND ISNULL(itm2.[actualIncludedColumns_internal], '') = ISNULL(ci.[IncludedColumns], '')
        )
        OPTION (RECOMPILE);


        FETCH NEXT FROM curIdxName
        INTO @schemaName,
             @tableName,
             @indexFK;
    END;

    CLOSE curIdxName;
    DEALLOCATE curIdxName;


    /* Adjust the "_comp" flags to create a matrix of actions */
    /* to be created */
    UPDATE [itm]
    SET [itm].[isToBeCreated_comp] = CASE
                                         WHEN [itm].[isToBeDeleted_inp] = 0 THEN
                                             1
                                         ELSE
                                             0
                                     END
    FROM @indexesToMaintains [itm]
    WHERE [itm].[indexName] IS NULL /* index was not found on the server */
          AND [itm].[expectedIndexName_inp] IS NOT NULL /* we did gave an index name, ie: we want to create it if missing */
    ;

    /* to be renamed */
    UPDATE [itm]
    SET [itm].[isToBeRenamed_comp] = 1
    FROM @indexesToMaintains [itm]
    WHERE [itm].[indexName] IS NOT NULL /* index was not found on the server */
          AND [itm].[expectedIndexName_inp] IS NOT NULL /* we did gave an index name, ie: we expect that index to be present */
          AND [indexName] <> [itm].[expectedIndexName_inp]
          AND [isToBeDeleted_inp] = 0
          AND [isToBeDeleted_comp] = 0
          AND [isToBeRecreated_inp] = 0
          AND [isToBeRecreated_comp] = 0;

    /* to be re-created */
    UPDATE [itm]
    SET [itm].[isToBeRecreated_comp] = 1
    FROM @indexesToMaintains [itm]
    WHERE [isToBeRecreated_inp] = 1;

    /* to be deleted, because specified in table */
    UPDATE itm
    SET [itm].[isToBeDeleted_comp] = CASE
                                         WHEN [itm].[isIndexFound_comp] = 0 THEN
                                             0
                                         ELSE
                                             1
                                     END,
        [itm].[isToBeRecreated_comp] = 0, /* deletion override re-create */
        [itm].[isToBeRenamed_comp] = 0    /* deletion override rename */
    FROM @indexesToMaintains [itm]
    WHERE [itm].[isToBeDeleted_inp] = 1;

    /* to be deleted, because different to specs. link on index name */
    UPDATE [exp]
    SET [exp].[isToBeCreated_comp] = 1 /* flag the expected index "to be created" */
    FROM @indexesToMaintains [exp] --expected
        JOIN @indexesToMaintains [act] --actual
            ON [act].[indexName] = [exp].[expectedIndexName_inp]
    WHERE [exp].[isToBeDeleted_comp] = 0
          AND [exp].[isToBeCreated_comp] = 0
          AND [exp].[isToBeRenamed_comp] = 0
          AND [exp].[isToBeRecreated_inp] = 0
          AND
          (
              [exp].[columnsName_inp] <> [act].[actualColumns_internal]
              OR [exp].[includedColumns_inp] <> [act].[actualIncludedColumns_internal]
          );

    UPDATE [act]
    SET [act].[isToBeDeleted_comp] = 1 /* flag the actual index "to be deleted" */
    FROM @indexesToMaintains [exp] --expected
        JOIN @indexesToMaintains [act] --actual
            ON [act].[indexName] = [exp].[expectedIndexName_inp]
    WHERE (
              [exp].[columnsName_inp] <> [act].[actualColumns_internal]
              OR [exp].[includedColumns_inp] <> [act].[actualIncludedColumns_internal]
          );

    /* to be deleted, because different to specs. link on index columns */
    UPDATE [exp]
    SET [exp].[isToBeCreated_comp] = 1 /* flag the expected index "to be created" */
    FROM @indexesToMaintains [exp] --expected
        JOIN @indexesToMaintains [act] --actual
            ON [act].[actualColumns_internal] = [exp].[columnsName_inp]
    WHERE (
              [exp].[columnsName_inp] <> [act].[actualColumns_internal]
              OR [exp].[includedColumns_inp] <> [act].[actualIncludedColumns_internal]
          );

    UPDATE [act]
    SET [act].[isToBeDeleted_comp] = 1 /* flag the actual index "to be deleted" */
    FROM @indexesToMaintains [exp] --expected
        JOIN @indexesToMaintains [act] --actual
            ON [act].[actualColumns_internal] = [exp].[columnsName_inp]
    WHERE (
              [exp].[columnsName_inp] <> [act].[actualColumns_internal]
              OR [exp].[includedColumns_inp] <> [act].[actualIncludedColumns_internal]
          );

    /* if we have both "rename" and "delete" actions, remove the "rename" action */
    UPDATE @indexesToMaintains
    SET [isToBeRenamed_comp] = 0
    WHERE [isToBeRenamed_comp] = 1
          AND [isToBeDeleted_comp] = 1;

    /* ensure that we don't have clash between indexes to create and indexes to rename */
    UPDATE ir
    SET [ir].[isToBeRenamed_comp] = 0, /* do not rename this index */
        [ir].[isToBeDeleted_inp] = 1   /* drop this index instead, as we have one with the correct structure about to be created */
    FROM @indexesToMaintains [ic] /* to create */
        JOIN @indexesToMaintains [ir] /* to rename */
            ON ic.[expectedIndexName_inp] = ir.[expectedIndexName_inp]
    WHERE [ic].[isToBeCreated_comp] = 1
          AND [ir].[isToBeRenamed_comp] = 1;


    /* Show the action matrix */
    IF @in_debug > 0
    BEGIN
        SELECT [itm].[isIndexFound_comp],
               [itm].[isToBeCreated_comp],
               [itm].[isToBeRenamed_comp],
               [itm].[isToBeDeleted_comp],
               [itm].[isToBeRecreated_comp],
               [itm].[statusMsg_internal],
               [itm].[IA_index_options],
               '....' AS separator,
               [itm].[isToBeDeleted_inp],
               [itm].[isToBeRecreated_inp],
               [itm].[indexName],
               [itm].[expectedIndexName_inp],
               [itm].[columnsName_inp],
               [itm].[actualColumns_internal],
               [itm].[includedColumns_inp],
               [itm].[actualIncludedColumns_internal]
        FROM @indexesToMaintains [itm]
        ORDER BY [itm].[isIndexFound_comp],
                 [itm].[isToBeDeleted_inp],
                 [itm].[columnsName_inp];
    END;


    /* Drop the indexe(s) that are to be deleted or recreated */
    SET @query = '';
    SELECT @query
        = @query + 'DROP INDEX ' + [itm].[indexName] + ' ON ' + [itm].[SchemaName] + '.' + [itm].[TableName] + ';'
          + CHAR(13) + CHAR(10)
    FROM @indexesToMaintains [itm]
    WHERE [itm].[isToBeDeleted_comp] = 1
          OR [itm].[isToBeRecreated_comp] = 1;

    IF @outputStatement = 1
    BEGIN
        PRINT REPLICATE('-', 40);
        PRINT '-- execute DROP INDEX';
        PRINT REPLICATE('-', 40);
        PRINT @query;
        PRINT REPLICATE('-', 40);
        PRINT CHAR(13) + CHAR(10);
    END;

    IF @executeStatement = 1
    BEGIN
        EXECUTE (@query);
    END;


    /* rename existing indexe(s) */
    SET @query = '';
    SELECT @query
        = @query + 'EXEC sp_rename @objname =''' + [itm].[TableName] + '.' + [itm].[indexName] + ''', @newname = '''
          + [itm].[expectedIndexName_inp] + ''';' + CHAR(13) + CHAR(10)
    FROM @indexesToMaintains [itm]
    WHERE [itm].[isToBeRenamed_comp] = 1;

    IF @outputStatement = 1
    BEGIN
        PRINT REPLICATE('-', 40);
        PRINT '-- execute sp_rename';
        PRINT REPLICATE('-', 40);
        PRINT @query;
        PRINT REPLICATE('-', 40);
        PRINT CHAR(13) + CHAR(10);
    END;

    IF @executeStatement = 1
    BEGIN
        EXECUTE (@query);
    END;


    /* create indexe(s) */
    SET @query = '';
    SELECT @query
        = @query + 'CREATE ' + CASE
                                   WHEN [itm].[isUnique_inp] = 1 THEN
                                       'UNIQUE '
                                   ELSE
                                       ''
                               END + CASE
                                         WHEN [itm].[isClustered_inp] = 1 THEN
                                             'CLUSTERED'
                                         ELSE
                                             'NONCLUSTERED'
                                     END + ' INDEX ' + [itm].[expectedIndexName_inp] + ' ON ' + [itm].[SchemaName]
          + '.' + [itm].[TableName] + '(' + [itm].[columnsName_inp] + ')'
          + CASE
                WHEN [itm].[includedColumns_inp] IS NOT NULL THEN
                    ' INCLUDE (' + [itm].[includedColumns_inp] + ')'
                ELSE
                    ''
            END + CHAR(13) + CHAR(10) + COALESCE('WITH(' + [itm].[IA_index_options] + ')' + CHAR(13) + CHAR(10), '')
          + ';' + CHAR(13) + CHAR(10)
    FROM @indexesToMaintains [itm]
    WHERE (
              [itm].[isToBeCreated_comp] = 1
              OR [itm].[isToBeRecreated_comp] = 1
          );

    IF @outputStatement = 1
    BEGIN
        PRINT REPLICATE('-', 40);
        PRINT '-- execute CREATE INDEX';
        PRINT REPLICATE('-', 40);
        PRINT @query;
        PRINT REPLICATE('-', 40);
        PRINT CHAR(13) + CHAR(10);
    END;

    IF @executeStatement = 1
    BEGIN
        EXECUTE (@query);
    END;

    /* record results in upd.IA_IndexesAlignementActions */
    UPDATE ia
    SET ia.[IA_executionDate] = CURRENT_TIMESTAMP,
        ia.[IA_foundIndexName] = ii.indexName,
        ia.[IA_wasIndexFound] = ii.isIndexFound_comp,
        ia.[IA_wasCreated] = ii.isToBeCreated_comp,
        ia.[IA_wasRenamed] = ii.isToBeRenamed_comp,
        ia.[IA_wasDeleted] = ii.isToBeDeleted_comp,
        ia.[IA_wasRecreated] = ii.isToBeRecreated_comp,
        ia.[IA_includedColumnsMatch] = ii.includedColumnsMatch_internal,
        ia.[IA_actualColumns] = ii.actualColumns_internal,
        ia.[IA_actualIncludedColumns] = ii.actualIncludedColumns_internal,
        ia.[IA_statusMsg] = ii.statusMsg_internal
    FROM [upd].[IA_IndexesAlignementActions] ia
        JOIN @indexesToMaintains ii
            ON ii.[IA_indexesAlignementActions] = ia.[IA_indexesAlignementActionsID]
    WHERE ia.[IA_executionDate] IS NULL;
END;
GO
