-- Compile Date: 03/02/2026 18:17:15 UTC
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
██╗  ██╗███████╗ █████╗ ██╗  ████████╗██╗  ██╗
██║  ██║██╔════╝██╔══██╗██║  ╚══██╔══╝██║  ██║
███████║█████╗  ███████║██║     ██║   ███████║
██╔══██║██╔══╝  ██╔══██║██║     ██║   ██╔══██║
██║  ██║███████╗██║  ██║███████╗██║   ██║  ██║
╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚══════╝╚═╝   ╚═╝  ╚═╝

██████╗  █████╗ ██████╗ ███████╗███████╗██████╗
██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗
██████╔╝███████║██████╔╝███████╗█████╗  ██████╔╝
██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝  ██╔══██╗
██║     ██║  ██║██║  ██║███████║███████╗██║  ██║
╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝


Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For support, head over to GitHub:
https://code.erikdarling.com
*/

IF OBJECT_ID(N'dbo.sp_HealthParser', N'P') IS NULL
   BEGIN
       EXECUTE (N'CREATE PROCEDURE dbo.sp_HealthParser AS RETURN 138;');
   END;
GO

ALTER PROCEDURE
    dbo.sp_HealthParser
(
    @what_to_check varchar(10) = 'all', /*Specify which portion of the data to check*/
    @start_date datetimeoffset(7) = NULL, /*Begin date for events*/
    @end_date datetimeoffset(7) = NULL, /*End date for events*/
    @warnings_only bit = 0, /*Only show results from recorded warnings*/
    @database_name sysname = NULL, /*Filter to a specific database for blocking)*/
    @wait_duration_ms bigint = 500, /*Minimum duration to show query waits*/
    @wait_round_interval_minutes bigint = 60, /*Nearest interval to round wait stats to*/
    @skip_locks bit = 0, /*Skip the blocking and deadlocks*/
    @skip_waits bit = 0, /*Skip the wait stats*/
    @use_ring_buffer bit = 0, /*Use ring_buffer target instead of file target for system_health session*/
    @pending_task_threshold integer = 10, /*Minimum number of pending tasks to care about*/
    @log_to_table bit = 0, /*enable logging to permanent tables*/
    @log_database_name sysname = NULL, /*database to store logging tables*/
    @log_schema_name sysname = NULL, /*schema to store logging tables*/
    @log_table_name_prefix sysname = 'HealthParser', /*prefix for all logging tables*/
    @log_retention_days integer = 30, /*Number of days to keep logs, 0 = keep indefinitely*/
    @debug bit = 0, /*Select from temp tables to get event data in raw xml*/
    @help bit = 0, /*Get help*/
    @version varchar(30) = NULL OUTPUT, /*Script version*/
    @version_date datetime = NULL OUTPUT /*Script date*/
)
WITH
RECOMPILE
AS
BEGIN
    SET STATISTICS XML OFF;
    SET NOCOUNT ON;
    SET XACT_ABORT OFF;
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT
        @version = '3.3',
        @version_date = '20260301';

    IF @help = 1
    BEGIN
        SELECT
            introduction =
                'hi, i''m sp_HealthParser!' UNION ALL
        SELECT  'you can use me to examine the contents of the system_health extended event session' UNION ALL
        SELECT  'i apologize if i take a long time, i have to do a lot of XML processing' UNION ALL
        SELECT  'from your loving sql server consultant, erik darling: erikdarling.com';

        /*
        Parameters
        */
        SELECT
            parameter_name =
                ap.name,
            data_type =
                t.name,
            description =
                CASE
                    ap.name
                    WHEN N'@what_to_check' THEN N'areas of system health to check'
                    WHEN N'@start_date' THEN N'earliest date to show data for, will be internally converted to UTC'
                    WHEN N'@end_date' THEN N'latest date to show data for, will be internally converted to UTC'
                    WHEN N'@warnings_only' THEN N'only show rows where a warning was reported'
                    WHEN N'@database_name' THEN N'database name to show blocking events for'
                    WHEN N'@wait_duration_ms' THEN N'minimum wait duration'
                    WHEN N'@wait_round_interval_minutes' THEN N'interval to round minutes to for wait stats'
                    WHEN N'@skip_locks' THEN N'skip the blocking and deadlocking section'
                    WHEN N'@skip_waits' THEN N'skip the wait stats section'
                    WHEN N'@use_ring_buffer' THEN N'use ring_buffer target instead of file target for faster collection'
                    WHEN N'@pending_task_threshold' THEN N'minimum number of pending tasks to display'
                    WHEN N'@log_to_table' THEN N'enable logging to permanent tables instead of returning results'
                    WHEN N'@log_database_name' THEN N'database to store logging tables'
                    WHEN N'@log_schema_name' THEN N'schema to store logging tables'
                    WHEN N'@log_table_name_prefix' THEN N'prefix for all logging tables'
                    WHEN N'@log_retention_days' THEN N'how many days of data to retain'
                    WHEN N'@version' THEN N'OUTPUT; for support'
                    WHEN N'@version_date' THEN N'OUTPUT; for support'
                    WHEN N'@help' THEN N'how you got here'
                    WHEN N'@debug' THEN N'prints dynamic sql, selects from temp tables'
                END,
            valid_inputs =
                CASE
                    ap.name
                    WHEN N'@what_to_check' THEN N'all, waits, disk, cpu, memory, system, locking'
                    WHEN N'@start_date' THEN N'a reasonable date'
                    WHEN N'@end_date' THEN N'a reasonable date'
                    WHEN N'@warnings_only' THEN N'NULL, 0, 1'
                    WHEN N'@database_name' THEN N'the name of a database'
                    WHEN N'@wait_duration_ms' THEN N'the minimum duration of a wait for queries with interesting waits'
                    WHEN N'@wait_round_interval_minutes' THEN N'interval to round minutes to for top wait stats by count and duration'
                    WHEN N'@skip_locks' THEN N'0 or 1'
                    WHEN N'@skip_waits' THEN N'0 or 1'
                    WHEN N'@use_ring_buffer' THEN N'0 or 1'
                    WHEN N'@pending_task_threshold' THEN N'a valid integer'
                    WHEN N'@log_to_table' THEN N'0 or 1'
                    WHEN N'@log_database_name' THEN N'any valid database name'
                    WHEN N'@log_schema_name' THEN N'any valid schema name'
                    WHEN N'@log_table_name_prefix' THEN N'any valid identifier'
                    WHEN N'@log_retention_days' THEN N'a positive integer'
                    WHEN N'@version' THEN N'none'
                    WHEN N'@version_date' THEN N'none'
                    WHEN N'@help' THEN N'0 or 1'
                    WHEN N'@debug' THEN N'0 or 1'
                END,
            defaults =
                CASE
                    ap.name
                    WHEN N'@what_to_check' THEN N'all'
                    WHEN N'@start_date' THEN N'seven days back'
                    WHEN N'@end_date' THEN N'current date'
                    WHEN N'@warnings_only' THEN N'0'
                    WHEN N'@database_name' THEN N'NULL'
                    WHEN N'@wait_duration_ms' THEN N'500'
                    WHEN N'@wait_round_interval_minutes' THEN N'60'
                    WHEN N'@skip_locks' THEN N'0'
                    WHEN N'@skip_waits' THEN N'0'
                    WHEN N'@use_ring_buffer' THEN N'0'
                    WHEN N'@pending_task_threshold' THEN N'10'
                    WHEN N'@log_to_table' THEN N'0'
                    WHEN N'@log_database_name' THEN N'NULL (current database)'
                    WHEN N'@log_schema_name' THEN N'NULL (dbo)'
                    WHEN N'@log_table_name_prefix' THEN N'HealthParser'
                    WHEN N'@log_retention_days' THEN N'30'
                    WHEN N'@version' THEN N'none; OUTPUT'
                    WHEN N'@version_date' THEN N'none; OUTPUT'
                    WHEN N'@help' THEN N'0'
                    WHEN N'@debug' THEN N'0'
                END
        FROM sys.all_parameters AS ap
        JOIN sys.all_objects AS o
          ON ap.object_id = o.object_id
        JOIN sys.types AS t
          ON  ap.system_type_id = t.system_type_id
          AND ap.user_type_id = t.user_type_id
        WHERE o.name = N'sp_HealthParser'
        OPTION(RECOMPILE);

        SELECT
            mit_license_yo = 'i am MIT licensed, so like, do whatever'

        UNION ALL

        SELECT
            mit_license_yo = 'see printed messages for full license';

        RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 0) WITH NOWAIT;

        RETURN;
    END; /*End help section*/

    IF @debug = 1
    BEGIN
        RAISERROR('Declaring variables', 0, 0) WITH NOWAIT;
    END;

    DECLARE
        @sql nvarchar(MAX) =
            N'',
        @params nvarchar(MAX) =
            N'@start_date datetimeoffset(7),
              @end_date datetimeoffset(7)',
        @azure bit  =
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        SERVERPROPERTY('EngineEdition')
                    ) = 5
                THEN 1
                ELSE 0
            END,
        @azure_msg nchar(1),
        @mi bit  =
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        SERVERPROPERTY('EngineEdition')
                    ) = 8
                THEN 1
                ELSE 0
            END,
        @mi_msg nchar(1),
        @dbid integer =
            DB_ID(@database_name),
        @timestamp_utc_mode tinyint,
        @sql_template nvarchar(MAX) = N'',
        @time_filter nvarchar(MAX) = N'',
        @cross_apply nvarchar(MAX) = N'',
        @collection_cursor CURSOR,
        @area_name varchar(20),
        @object_name sysname,
        @temp_table sysname,
        @insert_list sysname,
        @collection_sql nvarchar(MAX),
        /*Log to table stuff*/
        @log_table_significant_waits sysname,
        @log_table_waits_by_count sysname,
        @log_table_waits_by_duration sysname,
        @log_table_io_issues sysname,
        @log_table_cpu_tasks sysname,
        @log_table_memory_conditions sysname,
        @log_table_memory_broker sysname,
        @log_table_memory_node_oom sysname,
        @log_table_system_health sysname,
        @log_table_scheduler_issues sysname,
        @log_table_severe_errors sysname,
        @log_table_pending_tasks sysname,
        @log_table_blocking sysname,
        @log_table_deadlocks sysname,
        @cleanup_date datetime2(7),
        @check_sql nvarchar(MAX) = N'',
        @create_sql nvarchar(MAX) = N'',
        @insert_sql nvarchar(MAX) = N'',
        @log_database_schema nvarchar(1024),
        @max_event_time datetime2(7),
        @dsql nvarchar(MAX) = N'',
        @mdsql_template nvarchar(MAX) = N'',
        @mdsql_execute nvarchar(MAX) = N'',
        @start_date_debug nvarchar(50),
        @end_date_debug nvarchar(50);

    IF @azure = 1
    BEGIN
        RAISERROR('This won''t work in Azure because it''s horrible', 11, 1) WITH NOWAIT;
        RETURN;
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('Fixing parameters and variables', 0, 0) WITH NOWAIT;
    END;

    /*
    Normalize dates to UTC offset for comparison with system_health events
    When dates are NULL, use SYSUTCDATETIME() which is already UTC
    When dates are provided, SWITCHOFFSET converts from any timezone to UTC
    This matches sp_QuickieStore pattern for handling date conversions
    */
    SELECT
        @start_date =
            ISNULL
            (
                SWITCHOFFSET
                (
                    @start_date,
                    '+00:00'
                ),
                DATEADD
                (
                    DAY,
                    -7,
                    SYSUTCDATETIME()
                )
            ),
        @end_date =
            ISNULL
            (
                SWITCHOFFSET
                (
                    @end_date,
                    '+00:00'
                ),
                SYSUTCDATETIME()
            ),
        @wait_round_interval_minutes = /*do this i guess?*/
            CASE
                WHEN @wait_round_interval_minutes < 1
                THEN 1
                ELSE @wait_round_interval_minutes
            END,
        @azure_msg =
            CONVERT(nchar(1), @azure),
        @mi_msg =
            CONVERT(nchar(1), @mi),
        @timestamp_utc_mode =
            CASE
                WHEN EXISTS
                (
                    SELECT
                        1/0
                    FROM sys.all_columns AS ac
                    WHERE ac.object_id = OBJECT_ID(N'sys.fn_xe_file_target_read_file')
                    AND   ac.name = N'timestamp_utc'
                )
                THEN 1 +
                    CASE
                        WHEN
                            PARSENAME
                            (
                                CONVERT
                                (
                                    sysname,
                                    SERVERPROPERTY('PRODUCTVERSION')
                                ),
                                4
                            ) > 16
                        THEN 1
                        ELSE 0
                    END +
                    CASE
                        WHEN @mi = 1
                        THEN 1
                        ELSE 0
                    END
                ELSE 0
            END,
        @sql_template += N'
    INSERT INTO
        {temp_table}
    WITH
        (TABLOCK)
    (
        {insert_list}
    )
    SELECT
        {object_name} =
            ISNULL
            (
                xml.{object_name},
                CONVERT(xml, N''<event>event</event>'')
            )
    FROM
    (
        SELECT
            {object_name} =
                TRY_CAST(fx.event_data AS xml)
        FROM sys.fn_xe_file_target_read_file(N''system_health*.xel'', NULL, NULL, NULL) AS fx
        WHERE fx.object_name = N''{object_name}'' {time_filter}
    ) AS xml
    {cross_apply}
    OPTION(RECOMPILE);
',
        @mdsql_template = N'
    IF OBJECT_ID(''{table_check}'', ''U'') IS NOT NULL
    BEGIN
        SELECT
            @max_event_time =
                ISNULL
                (
                    MAX({date_column}),
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        DATEADD
                        (
                            DAY,
                            -1,
                            SYSDATETIME()
                        )
                    )
                )
        FROM {table_check};
    END;
';

    SELECT
        @start_date_debug = @start_date,
        @end_date_debug = @end_date;

    IF @timestamp_utc_mode = 0
    BEGIN
        /* Pre-2017 handling */
        SET @time_filter = N'';
        SET @cross_apply = N'CROSS APPLY xml.{object_name}.nodes(''/event'') AS e(x)
CROSS APPLY (SELECT x.value( ''(@timestamp)[1]'', ''datetimeoffset'' )) ca ([utc_timestamp])
WHERE ca.utc_timestamp >= @start_date
AND   ca.utc_timestamp < @end_date';
    END;
    ELSE
    BEGIN
        /* 2017+ handling */
        SET @cross_apply = N'CROSS APPLY xml.{object_name}.nodes(''/event'') AS e(x)';

        IF @timestamp_utc_mode = 1
            SET @time_filter = N'
    AND   CONVERT(datetimeoffset(7), fx.timestamp_utc) BETWEEN @start_date AND @end_date';
        ELSE
            SET @time_filter = N'
    AND   fx.timestamp_utc BETWEEN @start_date AND @end_date';
    END;

    SET @sql_template =
        REPLACE
        (
            REPLACE
            (
                @sql_template,
                '{time_filter}',
                @time_filter
            ),
            '{cross_apply}',
            @cross_apply
        );

    /*If any parameters that expect non-NULL default values get passed in with NULLs, fix them*/
    SELECT
        @what_to_check = LOWER(ISNULL(@what_to_check, 'all')),
        @warnings_only = ISNULL(@warnings_only, 0),
        @wait_duration_ms = ISNULL(@wait_duration_ms, 500),
        @wait_round_interval_minutes = ISNULL(@wait_round_interval_minutes, 60),
        @skip_locks = ISNULL(@skip_locks, 0),
        @skip_waits = ISNULL(@skip_waits, 0),
        @use_ring_buffer = ISNULL(@use_ring_buffer, 0),
        @pending_task_threshold = ISNULL(@pending_task_threshold, 10);

    /*Validate what to check*/
    IF @what_to_check NOT IN
        (
            'all',
            'cpu',
            'disk',
            'locking',
            'memory',
            'system',
            'waits'
        )
    BEGIN
        SELECT
            @what_to_check =
                CASE
                    WHEN @what_to_check = 'wait'
                    THEN 'waits'
                    WHEN @what_to_check IN
                         (
                           'blocking', 'blocks',
                           'deadlock', 'deadlocks',
                           'lock', 'locks'
                         )
                    THEN 'locking'
                    ELSE 'all'
                END;
    END;

    /* Validate logging parameters */
    IF @log_to_table = 1
    BEGIN
        SELECT
            /* Default database name to current database if not specified */
            @log_database_name = ISNULL(@log_database_name, DB_NAME()),
            /* Default schema name to dbo if not specified */
            @log_schema_name = ISNULL(@log_schema_name, N'dbo'),
            @log_retention_days =
                CASE
                    WHEN @log_retention_days < 0
                    THEN ABS(@log_retention_days)
                    ELSE @log_retention_days
                END;

        /* Validate database exists */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.databases AS d
            WHERE d.name = @log_database_name
        )
        BEGIN
            RAISERROR('The specified logging database %s does not exist. Logging will be disabled.', 11, 1, @log_database_name) WITH NOWAIT;
            RETURN;
        END;

        SET
            @log_database_schema =
                QUOTENAME(@log_database_name) +
                N'.' +
                QUOTENAME(@log_schema_name) +
                N'.';

        /* Generate fully qualified table names */
        SELECT
            @log_table_significant_waits =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_SignificantWaits'),
            @log_table_waits_by_count =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_WaitsByCount'),
            @log_table_waits_by_duration =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_WaitsByDuration'),
            @log_table_io_issues =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_IOIssues'),
            @log_table_cpu_tasks =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_CPUTasks'),
            @log_table_memory_conditions =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_MemoryConditions'),
            @log_table_memory_broker =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_MemoryBroker'),
            @log_table_memory_node_oom =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_MemoryNodeOOM'),
            @log_table_system_health =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_SystemHealth'),
            @log_table_scheduler_issues =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_SchedulerIssues'),
            @log_table_severe_errors =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_SevereErrors'),
            @log_table_pending_tasks =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_PendingTasks'),
            @log_table_blocking =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_Blocking'),
            @log_table_deadlocks =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_Deadlocks');

        /* Check if schema exists and create it if needed */
        SET @check_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                WHERE s.name = @schema_name
            )
            BEGIN
                DECLARE
                    @create_schema_sql nvarchar(max) = N''CREATE SCHEMA '' + QUOTENAME(@schema_name);

                EXECUTE ' + QUOTENAME(@log_database_name) + N'.sys.sp_executesql @create_schema_sql;
                IF @debug = 1 BEGIN RAISERROR(''Created schema %s in database %s for logging.'', 0, 1, @schema_name, @db_name) WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @check_sql,
          N'@schema_name sysname,
            @db_name sysname,
            @debug bit',
            @log_schema_name,
            @log_database_name,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_SignificantWaits''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_significant_waits + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    wait_type nvarchar(60) NULL,
                    duration_ms nvarchar(30) NULL,
                    signal_duration_ms nvarchar(30) NULL,
                    wait_resource nvarchar(256) NULL,
                    query_text xml NULL,
                    session_id integer NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for significant waits logging.'', 0, 1, ''' + @log_table_significant_waits + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create WaitsByCount table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_WaitsByCount''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_waits_by_count + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time_rounded datetime2(7) NULL,
                    wait_type nvarchar(60) NULL,
                    waits nvarchar(30) NULL,
                    average_wait_time_ms nvarchar(30) NULL,
                    max_wait_time_ms nvarchar(30) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for waits by count logging.'', 0, 1, ''' + @log_table_waits_by_count + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create WaitsByDuration table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_WaitsByDuration''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_waits_by_duration + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time_rounded datetime2(7) NULL,
                    wait_type nvarchar(60) NULL,
                    average_wait_time_ms nvarchar(30) NULL,
                    max_wait_time_ms nvarchar(30) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for waits by duration logging.'', 0, 1, ''' + @log_table_waits_by_duration + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create IOIssues table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_IOIssues''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_io_issues + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    state nvarchar(256) NULL,
                    ioLatchTimeouts bigint NULL,
                    intervalLongIos bigint NULL,
                    totalLongIos bigint NULL,
                    longestPendingRequests_duration_ms nvarchar(30) NULL,
                    longestPendingRequests_filePath nvarchar(500) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for IO issues logging.'', 0, 1, ''' + @log_table_io_issues + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create CPUTasks table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_CPUTasks''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_cpu_tasks + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    state nvarchar(256) NULL,
                    maxWorkers bigint NULL,
                    workersCreated bigint NULL,
                    workersIdle bigint NULL,
                    tasksCompletedWithinInterval bigint NULL,
                    pendingTasks bigint NULL,
                    oldestPendingTaskWaitingTime bigint NULL,
                    hasUnresolvableDeadlockOccurred bit NULL,
                    hasDeadlockedSchedulersOccurred bit NULL,
                    didBlockingOccur bit NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for CPU tasks logging.'', 0, 1, ''' + @log_table_cpu_tasks + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create MemoryConditions table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_MemoryConditions''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory_conditions + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    lastNotification nvarchar(128) NULL,
                    outOfMemoryExceptions bigint NULL,
                    isAnyPoolOutOfMemory bit NULL,
                    processOutOfMemoryPeriod bigint NULL,
                    name nvarchar(128) NULL,
                    available_physical_memory_gb bigint NULL,
                    available_virtual_memory_gb bigint NULL,
                    available_paging_file_gb bigint NULL,
                    working_set_gb bigint NULL,
                    percent_of_committed_memory_in_ws bigint NULL,
                    page_faults bigint NULL,
                    system_physical_memory_high bigint NULL,
                    system_physical_memory_low bigint NULL,
                    process_physical_memory_low bigint NULL,
                    process_virtual_memory_low bigint NULL,
                    vm_reserved_gb bigint NULL,
                    vm_committed_gb bigint NULL,
                    locked_pages_allocated bigint NULL,
                    large_pages_allocated bigint NULL,
                    emergency_memory_gb bigint NULL,
                    emergency_memory_in_use_gb bigint NULL,
                    target_committed_gb bigint NULL,
                    current_committed_gb bigint NULL,
                    pages_allocated bigint NULL,
                    pages_reserved bigint NULL,
                    pages_free bigint NULL,
                    pages_in_use bigint NULL,
                    page_alloc_potential bigint NULL,
                    numa_growth_phase bigint NULL,
                    last_oom_factor bigint NULL,
                    last_os_error bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory conditions logging.'', 0, 1, ''' + @log_table_memory_conditions + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create MemoryBroker table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_MemoryBroker''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory_broker + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    broker_id bigint NULL,
                    pool_metadata_id bigint NULL,
                    delta_time bigint NULL,
                    memory_ratio bigint NULL,
                    new_target bigint NULL,
                    overall bigint NULL,
                    rate bigint NULL,
                    currently_predicated bigint NULL,
                    currently_allocated bigint NULL,
                    previously_allocated bigint NULL,
                    broker nvarchar(256) NULL,
                    notification nvarchar(256) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory broker logging.'', 0, 1, ''' + @log_table_memory_broker + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create MemoryNodeOOM table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_MemoryNodeOOM''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory_node_oom + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    node_id bigint NULL,
                    memory_node_id bigint NULL,
                    memory_utilization_pct bigint NULL,
                    total_physical_memory_kb bigint NULL,
                    available_physical_memory_kb bigint NULL,
                    total_page_file_kb bigint NULL,
                    available_page_file_kb bigint NULL,
                    total_virtual_address_space_kb bigint NULL,
                    available_virtual_address_space_kb bigint NULL,
                    target_kb bigint NULL,
                    reserved_kb bigint NULL,
                    committed_kb bigint NULL,
                    shared_committed_kb numeric(38,0) NULL,
                    awe_kb bigint NULL,
                    pages_kb bigint NULL,
                    failure_type nvarchar(256) NULL,
                    failure_value bigint NULL,
                    resources bigint NULL,
                    factor_text nvarchar(256) NULL,
                    factor_value bigint NULL,
                    last_error bigint NULL,
                    pool_metadata_id bigint NULL,
                    is_process_in_job nvarchar(10) NULL,
                    is_system_physical_memory_high nvarchar(10) NULL,
                    is_system_physical_memory_low nvarchar(10) NULL,
                    is_process_physical_memory_low nvarchar(10) NULL,
                    is_process_virtual_memory_low nvarchar(10) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory node OOM logging.'', 0, 1, ''' + @log_table_memory_node_oom + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create SystemHealth table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_SystemHealth''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_system_health + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    state nvarchar(256) NULL,
                    spinlockBackoffs bigint NULL,
                    sickSpinlockType nvarchar(256) NULL,
                    sickSpinlockTypeAfterAv nvarchar(256) NULL,
                    latchWarnings bigint NULL,
                    isAccessViolationOccurred bigint NULL,
                    writeAccessViolationCount bigint NULL,
                    totalDumpRequests bigint NULL,
                    intervalDumpRequests bigint NULL,
                    nonYieldingTasksReported bigint NULL,
                    pageFaults bigint NULL,
                    systemCpuUtilization bigint NULL,
                    sqlCpuUtilization bigint NULL,
                    BadPagesDetected bigint NULL,
                    BadPagesFixed bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for system health logging.'', 0, 1, ''' + @log_table_system_health + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create SchedulerIssues table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_SchedulerIssues''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_scheduler_issues + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    scheduler_id integer NULL,
                    cpu_id integer NULL,
                    status nvarchar(256) NULL,
                    is_online bit NULL,
                    is_runnable bit NULL,
                    is_running bit NULL,
                    non_yielding_time_ms nvarchar(30) NULL,
                    thread_quantum_ms nvarchar(30) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for scheduler issues logging.'', 0, 1, ''' + @log_table_scheduler_issues + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create SevereErrors table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_SevereErrors''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_severe_errors + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    error_number integer NULL,
                    severity integer NULL,
                    state integer NULL,
                    message nvarchar(max) NULL,
                    database_name sysname NULL,
                    database_id integer NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for severe errors logging.'', 0, 1, ''' + @log_table_severe_errors + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create PendingTasks table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_PendingTasks''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_pending_tasks + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    pending_tasks bigint NULL,
                    entry_point_name sysname NULL,
                    module_name sysname NULL,
                    image_base varbinary(8) NULL,
                    size bigint NULL,
                    address varbinary(8) NULL,
                    entry_point_count bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for pending tasks logging.'', 0, 1, ''' + @log_table_pending_tasks + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create Blocking table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_Blocking''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_blocking + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_time datetime2(7) NULL,
                    currentdbname nvarchar(128) NULL,
                    activity varchar(8) NULL,
                    spid integer NULL,
                    ecid integer NULL,
                    query_text xml NULL,
                    wait_time_ms bigint NULL,
                    status nvarchar(10) NULL,
                    isolation_level nvarchar(50) NULL,
                    transaction_count integer NULL,
                    last_transaction_started datetime2(7) NULL,
                    last_transaction_completed datetime2(7) NULL,
                    client_option_1 varchar(8000) NULL,
                    client_option_2 varchar(8000) NULL,
                    wait_resource nvarchar(1024) NULL,
                    priority integer NULL,
                    log_used bigint NULL,
                    client_app nvarchar(256) NULL,
                    host_name nvarchar(256) NULL,
                    login_name nvarchar(256) NULL,
                    process_id sysname NULL,
                    scheduler_id bigint NULL,
                    kpid bigint NULL,
                    sbid bigint NULL,
                    last_attention datetime2(7) NULL,
                    xactid bigint NULL,
                    currentdb bigint NULL,
                    lock_timeout bigint NULL,
                    host_pid bigint NULL,
                    task_priority bigint NULL,
                    owner_id bigint NULL,
                    transaction_name nvarchar(1024) NULL,
                    last_tran_started datetime2(7) NULL,
                    xdes sysname NULL,
                    lock_mode nvarchar(10) NULL,
                    blocked_process_report xml NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for blocking logging.'', 0, 1, ''' + @log_table_blocking + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Create Deadlocks table if it doesn't exist */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_Deadlocks''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_deadlocks + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    event_date datetime2(7) NULL,
                    is_victim integer NULL,
                    database_name sysname NULL,
                    current_database_name sysname NULL,
                    query_text xml NULL,
                    deadlock_resources xml NULL,
                    isolation_level sysname NULL,
                    lock_mode sysname NULL,
                    status sysname NULL,
                    wait_time bigint NULL,
                    log_used bigint NULL,
                    transaction_name sysname NULL,
                    transaction_count bigint NULL,
                    client_option_1 varchar(500) NULL,
                    client_option_2 varchar(500) NULL,
                    last_tran_started datetime2(7) NULL,
                    last_batch_started datetime2(7) NULL,
                    last_batch_completed datetime2(7) NULL,
                    client_app nvarchar(1024) NULL,
                    host_name sysname NULL,
                    login_name sysname NULL,
                    priority smallint NULL,
                    deadlock_graph xml NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for deadlocks logging.'', 0, 1, ''' + @log_table_deadlocks + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Handle log retention if specified */
        IF @log_to_table = 1 AND @log_retention_days > 0
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Cleaning up log tables older than %i days', 0, 1, @log_retention_days) WITH NOWAIT;
            END;

            SET @cleanup_date =
                DATEADD
                (
                    DAY,
                    -@log_retention_days,
                    SYSDATETIME()
                );

            /* Clean up each log table */
            SET @dsql = N'
    DELETE FROM ' + @log_table_significant_waits + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_waits_by_count + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_waits_by_duration + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_io_issues + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_cpu_tasks + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_memory_conditions + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_memory_broker + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_memory_node_oom + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_system_health + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_scheduler_issues + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_severe_errors + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_pending_tasks + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_blocking + '
    WHERE collection_time < @cleanup_date;

    DELETE FROM ' + @log_table_deadlocks + '
    WHERE collection_time < @cleanup_date;
            ';

            IF @debug = 1
            BEGIN
                PRINT @dsql;
            END;

            EXECUTE sys.sp_executesql
                @dsql,
              N'@cleanup_date datetime2(7)',
                @cleanup_date;

            IF @debug = 1
            BEGIN
                RAISERROR('Log cleanup complete', 0, 0) WITH NOWAIT;
            END;
        END;
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('Creating temp tables', 0, 0) WITH NOWAIT;
    END;

    DECLARE
        @collection_areas table
    (
        id tinyint IDENTITY PRIMARY KEY CLUSTERED,
        area_name varchar(20) NOT NULL,
        object_name sysname NOT NULL,
        temp_table sysname NOT NULL,
        insert_list sysname NOT NULL,
        should_collect bit NOT NULL DEFAULT 0,
        is_processed bit NOT NULL DEFAULT 0
    );

    INSERT INTO
        @collection_areas
    (
        area_name,
        object_name,
        temp_table,
        insert_list,
        should_collect
    )
    SELECT
        v.area_name,
        v.object_name,
        v.temp_table,
        v.insert_list,
        should_collect =
            CASE
                WHEN @what_to_check = 'all'
                THEN
                    CASE
                        WHEN v.area_name = 'locking'
                        AND  @skip_locks = 1
                        THEN 0
                        WHEN v.area_name = 'waits'
                        AND  @skip_waits = 1
                        THEN 0
                        ELSE 1
                    END
                WHEN @what_to_check = v.area_name
                THEN 1
                ELSE 0
            END
    FROM
    (
    VALUES
        ('cpu', 'scheduler_monitor_system_health', '#scheduler_monitor', 'scheduler_monitor'),
        ('disk', 'sp_server_diagnostics_component_result', '#sp_server_diagnostics_component_result', 'sp_server_diagnostics_component_result'),
        ('locking', 'xml_deadlock_report', '#xml_deadlock_report', 'xml_deadlock_report'),
        ('locking', 'sp_server_diagnostics_component_result', '#sp_server_diagnostics_component_result', 'sp_server_diagnostics_component_result'),
        ('waits', 'wait_info', '#wait_info', 'wait_info'),
        ('waits', 'sp_server_diagnostics_component_result', '#sp_server_diagnostics_component_result', 'sp_server_diagnostics_component_result'),
        ('system', 'sp_server_diagnostics_component_result', '#sp_server_diagnostics_component_result', 'sp_server_diagnostics_component_result'),
        ('system', 'error_reported', '#error_reported', 'error_reported'),
        ('memory', 'memory_broker_ring_buffer_recorded', '#memory_broker', 'memory_broker'),
        ('memory', 'memory_node_oom_ring_buffer_recorded', '#memory_node_oom', 'memory_node_oom')
    ) AS v(area_name, object_name, temp_table, insert_list);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '@collection_areas',
            ca.*
        FROM @collection_areas AS ca
        ORDER BY
            ca.id
        OPTION(RECOMPILE);
    END;

    CREATE TABLE
        #ignore_waits
    (
        wait_type nvarchar(60) NOT NULL
    );

    CREATE TABLE
        #ignore_errors
    (
        error_number integer NOT NULL
    );

    CREATE TABLE
        #wait_info
    (
        wait_info xml NOT NULL
    );

    CREATE TABLE
        #sp_server_diagnostics_component_result
    (
        sp_server_diagnostics_component_result xml NOT NULL
    );

    CREATE TABLE
        #xml_deadlock_report
    (
        xml_deadlock_report xml NOT NULL
    );

    CREATE TABLE
        #blocking_xml
    (
        event_time datetime2 NOT NULL,
        human_events_xml xml NOT NULL
    );

    CREATE TABLE
        #x
    (
        x xml NOT NULL
    );

    CREATE TABLE
        #ring_buffer
    (
        ring_buffer xml NOT NULL
    );

    CREATE TABLE
        #scheduler_monitor
    (
        scheduler_monitor xml NOT NULL
    );

    CREATE TABLE
        #error_reported
    (
        error_reported xml NOT NULL
    );

    CREATE TABLE
        #memory_broker
    (
        memory_broker xml NOT NULL
    );

    CREATE TABLE
        #memory_node_oom
    (
        memory_node_oom xml NOT NULL
    );

    /*The more you ignore waits, the worser they get*/
    IF  @what_to_check IN ('all', 'waits')
    AND @skip_waits = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Inserting ignorable waits to #ignore_waits', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #ignore_waits
        WITH
            (TABLOCKX)
        (
            wait_type
        )
        SELECT
            dows.wait_type
        FROM sys.dm_os_wait_stats AS dows
        WHERE dows.wait_type IN
        (
            N'ASYNC_IO_COMPLETION', N'AZURE_IMDS_VERSIONS', N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR',
            N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
            N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
            N'DBMIRROR_DBM_EVENT', N'DBMIRROR_DBM_MUTEX', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_SEND',
            N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
            N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', N'HADR_CLUSAPI_CALL',
            N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK',
            N'HADR_WORK_QUEUE', N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE',
            N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE',
            N'PARALLEL_REDO_TRAN_LIST', N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK', N'PREEMPTIVE_OS_FLUSHFILEBUFFERS',
            N'PREEMPTIVE_XE_GETTARGETSTATE', N'PVS_PREALLOCATE', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
            N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', N'QDS_ASYNC_QUEUE', N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
            N'QDS_SHUTDOWN_QUEUE', N'REDO_THREAD_PENDING_WORK', N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE',
            N'SERVER_IDLE_CHECK', N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY',
            N'SLEEP_MASTERMDREADY', N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK',
            N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', N'SP_SERVER_DIAGNOSTICS_SLEEP',
            N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', N'UCS_SESSION_REGISTRATION',
            N'VDI_CLIENT_OTHER', N'WAIT_FOR_RESULTS', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_HOST_WAIT',
            N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_RECOVERY', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN',
            N'XE_DISPATCHER_JOIN', N'XE_DISPATCHER_WAIT', N'XE_FILE_TARGET_TVF', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT'
        )
        OPTION(RECOMPILE);
    END; /*End waits ignore*/

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#ignore_waits',
            i.*
        FROM #ignore_waits AS i ORDER BY i.wait_type
        OPTION(RECOMPILE);
    END;

    /* First, ensure we're working with the correct collection areas */
    IF @debug = 1
    BEGIN
        RAISERROR('Beginning collection loop for system_health data', 0, 0) WITH NOWAIT;
    END;

    /* Declare a cursor to process each collection area */
    SET @collection_cursor =
        CURSOR
        LOCAL
        SCROLL
        DYNAMIC
        READ_ONLY
    FOR
    SELECT
        ca.area_name,
        ca.object_name,
        ca.temp_table,
        ca.insert_list
    FROM @collection_areas AS ca
    WHERE ca.should_collect = 1
    AND   ca.is_processed = 0
    ORDER BY
        ca.id;

    /*
    File target collection: read from system_health .xel files on disk
    Skip this path when using ring_buffer target or on Managed Instance
    */
    IF  @mi = 0
    AND @use_ring_buffer = 0
    BEGIN
        OPEN @collection_cursor;

        FETCH NEXT
        FROM @collection_cursor
        INTO
            @area_name,
            @object_name,
            @temp_table,
            @insert_list;

        WHILE @@FETCH_STATUS = 0
        BEGIN
            /* Build the SQL statement for this collection area */
            SET
                @collection_sql =
                    REPLACE
                    (
                        REPLACE
                        (
                            REPLACE
                            (
                                @sql_template,
                                '{object_name}',
                                @object_name
                            ),
                            '{temp_table}',
                            @temp_table
                        ),
                        '{insert_list}',
                        @insert_list
                    );

            IF @debug = 1
            BEGIN
                RAISERROR('Collecting data for area: %s, object: %s, target table: %s', 0, 1, @area_name, @object_name, @temp_table) WITH NOWAIT;
                PRINT @collection_sql;
            END;

            IF @debug = 1
            BEGIN
                RAISERROR('Executing collection SQL for dates between %s and %s', 0, 0, @start_date_debug, @end_date_debug) WITH NOWAIT;
                SET STATISTICS XML ON;
            END;

            EXECUTE sys.sp_executesql
                @collection_sql,
                @params,
                @start_date,
                @end_date;

            IF @debug = 1
            BEGIN
                SET STATISTICS XML OFF;
            END;

            UPDATE
                @collection_areas
            SET
                is_processed = 1
            WHERE temp_table = @temp_table
            AND   should_collect = 1;

            FETCH NEXT
            FROM @collection_cursor
            INTO
                @area_name,
                @object_name,
                @temp_table,
                @insert_list;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Data collection complete', 0, 0) WITH NOWAIT;
        END;
    END; /*End file target collection*/

    /*
    Ring buffer collection: read from system_health ring_buffer target
    Used for Managed Instance (always) and on-prem when @use_ring_buffer = 1
    Faster than file target because it avoids disk I/O
    */
    IF @mi = 1
    OR @use_ring_buffer = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Starting ring_buffer analysis', 0, 0) WITH NOWAIT;
            RAISERROR('Inserting #x', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #x
        WITH
            (TABLOCKX)
        (
            x
        )
        SELECT
            x =
                ISNULL
                (
                    TRY_CAST(t.target_data AS xml),
                    CONVERT(xml, N'<event>event</event>')
                )
        FROM sys.dm_xe_session_targets AS t
        JOIN sys.dm_xe_sessions AS s
          ON s.address = t.event_session_address
        WHERE s.name = N'system_health'
        AND   t.target_name = N'ring_buffer'
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#x, top 100 rows',
                x.*
            FROM #x AS x;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting #ring_buffer', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #ring_buffer
        WITH
            (TABLOCKX)
        (
            ring_buffer
        )
        SELECT
            x = e.x.query('.')
        FROM #x AS x
        CROSS APPLY x.x.nodes('//event') AS e(x)
        WHERE 1 = 1
        AND   e.x.exist('@timestamp[.>= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1
        AND   e.x.exist('@name[.= "security_error_ring_buffer_recorded"]') = 0
        AND   e.x.exist('@name[.= "connectivity_ring_buffer_recorded"]') = 0
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#ring_buffer, top 100 rows',
                x.*
            FROM #ring_buffer AS x;
        END;

        IF  @what_to_check IN ('all', 'waits')
        AND @skip_waits = 0
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer waits', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #wait_info', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #wait_info
            WITH
                (TABLOCKX)
            (
                wait_info
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "wait_info"]') = 1
            OPTION(RECOMPILE);
        END;
        IF @what_to_check IN ('all', 'disk', 'locking', 'system', 'memory')
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer sp_server_diagnostics_component_result', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #sp_server_diagnostics_component_result', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #sp_server_diagnostics_component_result
            WITH
                (TABLOCKX)
            (
                sp_server_diagnostics_component_result
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "sp_server_diagnostics_component_result"]') = 1
            OPTION(RECOMPILE);
        END;

        IF
        (
             @what_to_check IN ('all', 'locking')
         AND @skip_locks = 0
        )
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer deadlocks', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #xml_deadlock_report', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #xml_deadlock_report
            WITH
                (TABLOCKX)
            (
                xml_deadlock_report
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "xml_deadlock_report"]') = 1
            OPTION(RECOMPILE);
        END;

        /* Add scheduler_monitor collection for MI */
        IF @what_to_check IN ('all', 'system', 'cpu')
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer scheduler monitor', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #scheduler_monitor', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #scheduler_monitor
            WITH
                (TABLOCKX)
            (
                scheduler_monitor
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "scheduler_monitor_system_health"]') = 1
            OPTION(RECOMPILE);
        END;

        /* Add error_reported collection for MI */
        IF @what_to_check IN ('all', 'system')
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer error reported events', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #error_reported', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #error_reported
            WITH
                (TABLOCKX)
            (
                error_reported
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "error_reported"]') = 1
            OPTION(RECOMPILE);
        END;

        /* Add memory_broker collection for MI */
        IF @what_to_check IN ('all', 'memory')
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer memory broker events', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #memory_broker', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #memory_broker
            WITH
                (TABLOCKX)
            (
                memory_broker
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "memory_broker_ring_buffer_recorded"]') = 1
            OPTION(RECOMPILE);
        END;

        /* Add memory node OOM collection for MI */
        IF @what_to_check IN ('all', 'memory')
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking ring_buffer memory node OOM events', 0, 0) WITH NOWAIT;
                RAISERROR('Inserting #memory_node_oom', 0, 0) WITH NOWAIT;
            END;

            INSERT
                #memory_node_oom
            WITH
                (TABLOCKX)
            (
                memory_node_oom
            )
            SELECT
                e.x.query('.')
            FROM #ring_buffer AS rb
            CROSS APPLY rb.ring_buffer.nodes('/event') AS e(x)
            WHERE e.x.exist('@name[.= "memory_node_oom_ring_buffer_recorded"]') = 1
            OPTION(RECOMPILE);
        END;

    END; /*End Managed Instance collection*/

    IF @debug = 1
    BEGIN
        SELECT TOP (100)
            table_name = '#wait_info, top 100 rows',
            x.*
        FROM #wait_info AS x;

        SELECT TOP (100)
            table_name = '#sp_server_diagnostics_component_result, top 100 rows',
            x.*
        FROM #sp_server_diagnostics_component_result AS x;

        SELECT TOP (100)
            table_name = '#xml_deadlock_report, top 100 rows',
            x.*
        FROM #xml_deadlock_report AS x;

        SELECT TOP (100)
            table_name = '#scheduler_monitor, top 100 rows',
            x.*
        FROM #scheduler_monitor AS x;

        SELECT TOP (100)
            table_name = '#error_reported, top 100 rows',
            x.*
        FROM #error_reported AS x;

        SELECT TOP (100)
            table_name = '#memory_broker, top 100 rows',
            x.*
        FROM #memory_broker AS x;

        SELECT TOP (100)
            table_name = '#memory_node_oom, top 100 rows',
            x.*
        FROM #memory_node_oom AS x;
    END;

    /*Parse out the wait_info data*/
    IF  @what_to_check IN ('all', 'waits')
    AND @skip_waits = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing queries with significant waits', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            wait_type = w.x.value('(data[@name="wait_type"]/text/text())[1]', 'nvarchar(60)'),
            duration_ms = CONVERT(bigint, w.x.value('(data[@name="duration"]/value/text())[1]', 'bigint')),
            signal_duration_ms = CONVERT(bigint, w.x.value('(data[@name="signal_duration"]/value/text())[1]', 'bigint')),
            wait_resource = w.x.value('(data[@name="wait_resource"]/value/text())[1]', 'nvarchar(256)'),
            sql_text_pre = w.x.value('(action[@name="sql_text"]/value/text())[1]', 'nvarchar(max)'),
            session_id = w.x.value('(action[@name="session_id"]/value/text())[1]', 'integer'),
            xml = w.x.query('.')
        INTO #waits_queries
        FROM #wait_info AS wi
        CROSS APPLY wi.wait_info.nodes('//event') AS w(x)
        WHERE w.x.exist('(action[@name="session_id"]/value/text())[.= 0]') = 0
        AND   w.x.exist('(action[@name="sql_text"]/value/text())') = 1
        AND   w.x.exist('(action[@name="sql_text"]/value/text()[contains(upper-case(.), "BACKUP")] )') = 0
        AND   w.x.exist('(data[@name="duration"]/value/text())[.>= sql:variable("@wait_duration_ms")]') = 1
        AND   NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #ignore_waits AS i
                  WHERE w.x.exist('(data[@name="wait_type"]/text/text())[1][.= sql:column("i.wait_type")]') = 1
              )
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('Adding query_text to #waits_queries', 0, 0) WITH NOWAIT;
        END;

        ALTER TABLE #waits_queries
        ADD query_text AS
            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                sql_text_pre COLLATE Latin1_General_BIN2,
            NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
            NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
            NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
        PERSISTED;

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#waits_queries, top 100 rows',
                x.*
            FROM #waits_queries AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* First logging section, queries with significant waits*/
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #waits_queries AS wq
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'waits')
                            THEN 'waits skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @skip_waits = 1
                            THEN 'waits skipped, @skip_waits set to 1'
                            WHEN @what_to_check IN ('all', 'waits')
                            THEN 'no queries with significant waits found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with a minimum duration of ' +
                                 RTRIM(@wait_duration_ms) +
                                 'ms.'
                            ELSE 'no queries with significant waits found!'
                        END;

                RAISERROR('No queries with significant waits found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''queries with significant waits'','
                    END +
              N'
                wq.event_time,
                wq.wait_type,
                duration_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                wq.duration_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                signal_duration_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                wq.signal_duration_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                wq.wait_resource,
                query_text =
                    (
                        SELECT
                            [processing-instruction(query)] =
                                wq.query_text
                        FOR XML
                            PATH(N''''),
                            TYPE
                    ),
                wq.session_id
            FROM #waits_queries AS wq';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_significant_waits
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE wq.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                wq.duration_ms DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_significant_waits + N'
            (
                event_time,
                wait_type,
                duration_ms,
                signal_duration_ms,
                wait_resource,
                query_text,
                session_id
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;

        /*Waits by count*/
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing #waits_by_count', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            wait_type = w2.x2.value('@waitType', 'nvarchar(60)'),
            waits = w2.x2.value('@waits', 'bigint'),
            average_wait_time_ms = CONVERT(bigint, w2.x2.value('@averageWaitTime', 'bigint')),
            max_wait_time_ms = CONVERT(bigint, w2.x2.value('@maxWaitTime', 'bigint')),
            xml = w.x.query('.')
        INTO #topwaits_count
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('/event') AS w(x)
        CROSS APPLY w.x.nodes('/event/data/value/queryProcessing/topWaits/nonPreemptive/byCount/wait') AS w2(x2)
        WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
        AND   NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #ignore_waits AS i
                  WHERE w2.x2.exist('@waitType[.= sql:column("i.wait_type")]') = 1
              )
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#topwaits_count, top 100 rows',
                x.*
            FROM #topwaits_count AS x
            ORDER BY
                x.event_time DESC;
        END;

        SELECT
            finding = 'waits by count',
            event_time_rounded =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        '19000101',
                        tc.event_time
                    ) / @wait_round_interval_minutes *
                        @wait_round_interval_minutes,
                    '19000101'
                ),
            tc.wait_type,
            waits = SUM(CONVERT(bigint, tc.waits)),
            average_wait_time_ms = CONVERT(bigint, AVG(tc.average_wait_time_ms)),
            max_wait_time_ms = CONVERT(bigint, MAX(tc.max_wait_time_ms))
        INTO #tc
        FROM #topwaits_count AS tc
        GROUP BY
            tc.wait_type,
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                    MINUTE,
                    '19000101',
                    tc.event_time
                ) / @wait_round_interval_minutes *
                    @wait_round_interval_minutes,
                '19000101'
            )
        OPTION(RECOMPILE);

        /* Waits by count logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #tc AS t
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'waits')
                            THEN 'waits skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @skip_waits = 1
                            THEN 'waits skipped, @skip_waits set to 1'
                            WHEN @what_to_check IN ('all', 'waits')
                            THEN 'no significant waits found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 '.'
                            ELSE 'no significant waits found!'
                        END;

                RAISERROR('No waits by count found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''waits by count'','
                    END +
              N'
                t.event_time_rounded,
                t.wait_type,
                waits =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                t.waits
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                average_wait_time_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                t.average_wait_time_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                max_wait_time_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                t.max_wait_time_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    )
            FROM #tc AS t';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_waits_by_count
                        ),
                        '{date_column}',
                        'event_time_rounded'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE t.event_time_rounded > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                t.event_time_rounded DESC,
                t.waits DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_waits_by_count + N'
            (
                event_time_rounded,
                wait_type,
                waits,
                average_wait_time_ms,
                max_wait_time_ms
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;

        /*Grab waits by duration*/
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing waits by duration', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            wait_type = w2.x2.value('@waitType', 'nvarchar(60)'),
            waits = w2.x2.value('@waits', 'bigint'),
            average_wait_time_ms = CONVERT(bigint, w2.x2.value('@averageWaitTime', 'bigint')),
            max_wait_time_ms = CONVERT(bigint, w2.x2.value('@maxWaitTime', 'bigint')),
            xml = w.x.query('.')
        INTO #topwaits_duration
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('/event') AS w(x)
        CROSS APPLY w.x.nodes('/event/data/value/queryProcessing/topWaits/nonPreemptive/byDuration/wait') AS w2(x2)
        WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
        AND   w2.x2.exist('@averageWaitTime[.>= sql:variable("@wait_duration_ms")]') = 1
        AND   NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #ignore_waits AS i
                  WHERE w2.x2.exist('@waitType[.= sql:column("i.wait_type")]') = 1
              )
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#topwaits_duration, top 100 rows',
                x.*
            FROM #topwaits_duration AS x
            ORDER BY
                x.event_time DESC;
        END;

        SELECT
            finding = 'waits by duration',
            event_time_rounded =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        '19000101',
                        td.event_time
                    ) / @wait_round_interval_minutes *
                        @wait_round_interval_minutes,
                    '19000101'
                ),
            td.wait_type,
            td.waits,
            td.average_wait_time_ms,
            td.max_wait_time_ms
        INTO #td
        FROM #topwaits_duration AS td
        GROUP BY
            td.wait_type,
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                    MINUTE,
                    '19000101',
                    td.event_time
                ) / @wait_round_interval_minutes *
                    @wait_round_interval_minutes,
                '19000101'
            ),
            td.waits,
            td.average_wait_time_ms,
            td.max_wait_time_ms
        OPTION(RECOMPILE);

        /* Waits by duration logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #td AS t
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'waits')
                            THEN 'waits skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @skip_waits = 1
                            THEN 'waits skipped, @skip_waits set to 1'
                            WHEN @what_to_check IN ('all', 'waits')
                            THEN 'no significant waits found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with a minimum average duration of ' +
                                 RTRIM(@wait_duration_ms) +
                                 'ms.'
                            ELSE 'no significant waits found!'
                        END;

                RAISERROR('No waits by duration', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''waits by duration'','
                    END +
              N'
                x.event_time_rounded,
                x.wait_type,
                x.average_wait_time_ms,
                x.max_wait_time_ms
            FROM
            (
                SELECT
                    t.finding,
                    t.event_time_rounded,
                    t.wait_type,
                    waits =
                        REPLACE
                        (
                            CONVERT
                            (
                                nvarchar(30),
                                CONVERT
                                (
                                    money,
                                    t.waits
                                ),
                                1
                            ),
                        N''.00'',
                        N''''
                        ),
                    average_wait_time_ms =
                        REPLACE
                        (
                            CONVERT
                            (
                                nvarchar(30),
                                CONVERT
                                (
                                    money,
                                    t.average_wait_time_ms
                                ),
                                1
                            ),
                        N''.00'',
                        N''''
                        ),
                    max_wait_time_ms =
                        REPLACE
                        (
                            CONVERT
                            (
                                nvarchar(30),
                                CONVERT
                                (
                                    money,
                                    t.max_wait_time_ms
                                ),
                                1
                            ),
                        N''.00'',
                        N''''
                        ),
                    s =
                        ROW_NUMBER() OVER
                        (
                            ORDER BY
                                t.event_time_rounded DESC,
                                t.waits DESC
                        ),
                    n =
                        ROW_NUMBER() OVER
                        (
                            PARTITION BY
                                t.wait_type,
                                t.waits,
                                t.average_wait_time_ms,
                                t.max_wait_time_ms
                            ORDER BY
                                t.event_time_rounded
                        )
                FROM #td AS t
            ) AS x
            WHERE x.n = 1';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_waits_by_duration
                        ),
                        '{date_column}',
                        'event_time_rounded'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            AND x.event_time_rounded > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                x.s
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_waits_by_duration + N'
            (
                event_time_rounded,
                wait_type,
                average_wait_time_ms,
                max_wait_time_ms
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End wait stats*/

    /*Grab IO stuff*/
    IF @what_to_check IN ('all', 'disk')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing disk stuff', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            state = w.x.value('(data[@name="state"]/text/text())[1]', 'nvarchar(256)'),
            ioLatchTimeouts = w.x.value('(/event/data[@name="data"]/value/ioSubsystem/@ioLatchTimeouts)[1]', 'bigint'),
            intervalLongIos = w.x.value('(/event/data[@name="data"]/value/ioSubsystem/@intervalLongIos)[1]', 'bigint'),
            totalLongIos = w.x.value('(/event/data[@name="data"]/value/ioSubsystem/@totalLongIos)[1]', 'bigint'),
            longestPendingRequests_duration_ms = CONVERT(bigint, w2.x2.value('@duration', 'bigint')),
            longestPendingRequests_filePath = w2.x2.value('@filePath', 'nvarchar(500)'),
            xml = w.x.query('.')
        INTO #io
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x)
        OUTER APPLY w.x.nodes('/event/data/value/ioSubsystem/longestPendingRequests/pendingRequest') AS w2(x2)
        WHERE w.x.exist('(data[@name="component"]/text[.= "IO_SUBSYSTEM"])') = 1
        AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#io, top 100 rows',
                x.*
            FROM #io AS x
            ORDER BY
                x.event_time DESC;
        END;

        SELECT
            finding = 'potential io issues',
            i.event_time,
            i.state,
            i.ioLatchTimeouts,
            i.intervalLongIos,
            i.totalLongIos,
            longestPendingRequests_duration_ms =
                SUM(i.longestPendingRequests_duration_ms),
            longestPendingRequests_filePath =
                ISNULL(i.longestPendingRequests_filePath, 'N/A')
        INTO #i
        FROM #io AS i
        WHERE i.longestPendingRequests_duration_ms IS NOT NULL
        GROUP BY
            i.event_time,
            i.state,
            i.ioLatchTimeouts,
            i.intervalLongIos,
            i.totalLongIos,
            ISNULL(i.longestPendingRequests_filePath, 'N/A')
        OPTION(RECOMPILE);

        /* Potential IO issues logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #i AS i
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'disk')
                            THEN 'disk skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'disk')
                            THEN 'no io issues found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no io issues found!'
                        END;
                RAISERROR('No io data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''potential io issues'','
                    END +
              N'
                i.event_time,
                i.state,
                i.ioLatchTimeouts,
                i.intervalLongIos,
                i.totalLongIos,
                longestPendingRequests_duration_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                i.longestPendingRequests_duration_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                i.longestPendingRequests_filePath
            FROM #i AS i';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for IO issues */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_io_issues
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE i.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                i.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_io_issues + N'
            (
                event_time,
                state,
                ioLatchTimeouts,
                intervalLongIos,
                totalLongIos,
                longestPendingRequests_duration_ms,
                longestPendingRequests_filePath
            )'
                + @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End disk*/

    /*Grab CPU details*/
    IF @what_to_check IN ('all', 'cpu')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing CPU stuff', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            name = w.x.value('@name', 'nvarchar(256)'),
            component = w.x.value('(data[@name="component"]/text/text())[1]', 'nvarchar(256)'),
            state = w.x.value('(data[@name="state"]/text/text())[1]', 'nvarchar(256)'),
            maxWorkers = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@maxWorkers)[1]', 'bigint'),
            workersCreated = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@workersCreated)[1]', 'bigint'),
            workersIdle = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@workersIdle)[1]', 'bigint'),
            tasksCompletedWithinInterval = w.x.value('(//data[@name="data"]/value/queryProcessing/@tasksCompletedWithinInterval)[1]', 'bigint'),
            pendingTasks = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@pendingTasks)[1]', 'bigint'),
            oldestPendingTaskWaitingTime = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@oldestPendingTaskWaitingTime)[1]', 'bigint'),
            hasUnresolvableDeadlockOccurred = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@hasUnresolvableDeadlockOccurred)[1]', 'bit'),
            hasDeadlockedSchedulersOccurred = w.x.value('(/event/data[@name="data"]/value/queryProcessing/@hasDeadlockedSchedulersOccurred)[1]', 'bit'),
            didBlockingOccur = w.x.exist('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report'),
            xml = w.x.query('.')
        INTO #scheduler_details
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('/event') AS w(x)
        WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
        AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        AND  (w.x.exist('(/event/data[@name="data"]/value/queryProcessing/@pendingTasks[.>= sql:variable("@pending_task_threshold")])') = 1 OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#scheduler_details, top 100 rows',
                x.*
            FROM #scheduler_details AS x
            ORDER BY
                x.event_time DESC;
        END;
    END;

    /* CPU task details logging section */
    IF @what_to_check IN ('all', 'cpu')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing scheduler stuff', 0, 0) WITH NOWAIT;
        END;

        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #scheduler_details AS sd
        )
        BEGIN
            IF @log_to_table = 0
                BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'cpu')
                            THEN 'cpu skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'cpu')
                            THEN 'no cpu issues found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no cpu issues found!'
                        END;

                RAISERROR('No scheduler data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
                SELECT
                    ' + CASE
                            WHEN @log_to_table = 1
                            THEN N''
                            ELSE N'finding = ''cpu task details'','
                        END +
                  N'
                    sd.event_time,
                    sd.state,
                    sd.maxWorkers,
                    sd.workersCreated,
                    sd.workersIdle,
                    sd.tasksCompletedWithinInterval,
                    sd.pendingTasks,
                    sd.oldestPendingTaskWaitingTime,
                    sd.hasUnresolvableDeadlockOccurred,
                    sd.hasDeadlockedSchedulersOccurred,
                    sd.didBlockingOccur
                FROM #scheduler_details AS sd';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for CPU task details */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_cpu_tasks
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE sd.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                sd.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
                INSERT INTO
                    ' + @log_table_cpu_tasks + N'
                (
                    event_time,
                    state,
                    maxWorkers,
                    workersCreated,
                    workersIdle,
                    tasksCompletedWithinInterval,
                    pendingTasks,
                    oldestPendingTaskWaitingTime,
                    hasUnresolvableDeadlockOccurred,
                    hasDeadlockedSchedulersOccurred,
                    didBlockingOccur
                )' +
                    @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END;/*End CPU*/

    /*Grab pending task entry point details*/
    IF @what_to_check IN ('all', 'cpu')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing pending task entry point details', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            pending_tasks =
                w.x.value
                (
                    '(/event/data[@name="data"]/value/queryProcessing/@pendingTasks)[1]',
                    'bigint'
                ),
            entry_point_name =
                ep.e.value('@name', 'sysname'),
            module_name =
                ep.e.value('@moduleName', 'sysname'),
            image_base =
                ep.e.value
                (
                    'xs:hexBinary(substring(string(@imageBase), 3))',
                    'varbinary(8)'
                ),
            size =
                CONVERT
                (
                    bigint,
                    ep.e.value
                    (
                        '
                        xs:hexBinary
                        (
                          if (string-length(substring(string(@size), 3)) mod 2 = 1)
                          then concat("0", substring(string(@size), 3))
                          else substring(string(@size), 3)
                        )
                        ',
                        'varbinary(8)'
                    )
                ),
            address =
                ep.e.value
                (
                    'xs:hexBinary(substring(string(@address), 3))',
                    'varbinary(8)'
                ),
            entry_point_count =
                ep.e.value('@count', 'bigint')
        INTO #pending_task_details
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('/event') AS w(x)
        CROSS APPLY w.x.nodes('/event/data[@name="data"]/value/queryProcessing[@pendingTasks > 1]/pendingTasks/entryPoint') AS ep(e)
        WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
        AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#pending_task_details, top 100 rows',
                x.*
            FROM #pending_task_details AS x
            ORDER BY
                x.event_time DESC;
        END;
    END;

    /*Pending task entry point details output*/
    IF @what_to_check IN ('all', 'cpu')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing pending task entry point output', 0, 0) WITH NOWAIT;
        END;

        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #pending_task_details AS ptd
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'cpu')
                            THEN 'cpu skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'cpu')
                            THEN 'no pending task entry point details found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no pending task entry point details found!'
                        END;

                RAISERROR('No pending task entry point data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
                SELECT
                    ' + CASE
                            WHEN @log_to_table = 1
                            THEN N''
                            ELSE N'finding = ''pending task entry point details'','
                        END +
                  N'
                    ptd.event_time,
                    ptd.pending_tasks,
                    ptd.entry_point_name,
                    ptd.module_name,
                    ptd.image_base,
                    ptd.size,
                    ptd.address,
                    ptd.entry_point_count
                FROM #pending_task_details AS ptd';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for pending task details */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_pending_tasks
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE ptd.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                ptd.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
                INSERT INTO
                    ' + @log_table_pending_tasks + N'
                (
                    event_time,
                    pending_tasks,
                    entry_point_name,
                    module_name,
                    image_base,
                    size,
                    address,
                    entry_point_count
                )' +
                    @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END;/*End pending task entry point details*/

    /*Grab memory details*/
    IF @what_to_check IN ('all', 'memory')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing memory stuff', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    s.sp_server_diagnostics_component_result.value('(//@timestamp)[1]', 'datetime2')
                ),
            lastNotification = r.c.value('@lastNotification', 'varchar(128)'),
            outOfMemoryExceptions = r.c.value('@outOfMemoryExceptions', 'bigint'),
            isAnyPoolOutOfMemory = r.c.value('@isAnyPoolOutOfMemory', 'bit'),
            processOutOfMemoryPeriod = r.c.value('@processOutOfMemoryPeriod', 'bigint'),
            name = r.c.value('(//memoryReport/@name)[1]', 'varchar(128)'),
            available_physical_memory_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Available Physical Memory"]]/@value)[1]', 'bigint') / 1024 / 1024 / 1024),
            available_virtual_memory_gb =  CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Available Virtual Memory"]]/@value)[1]', 'bigint') / 1024 / 1024 / 1024),
            available_paging_file_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Available Paging File"]]/@value)[1]', 'bigint') / 1024 / 1024 / 1024),
            working_set_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Working Set"]]/@value)[1]', 'bigint') / 1024 / 1024 / 1024),
            percent_of_committed_memory_in_ws = r.c.value('(//memoryReport/entry[@description[.="Percent of Committed Memory in WS"]]/@value)[1]', 'bigint'),
            page_faults = r.c.value('(//memoryReport/entry[@description[.="Page Faults"]]/@value)[1]', 'bigint'),
            system_physical_memory_high = r.c.value('(//memoryReport/entry[@description[.="System physical memory high"]]/@value)[1]', 'bigint'),
            system_physical_memory_low = r.c.value('(//memoryReport/entry[@description[.="System physical memory low"]]/@value)[1]', 'bigint'),
            process_physical_memory_low = r.c.value('(//memoryReport/entry[@description[.="Process physical memory low"]]/@value)[1]', 'bigint'),
            process_virtual_memory_low = r.c.value('(//memoryReport/entry[@description[.="Process virtual memory low"]]/@value)[1]', 'bigint'),
            vm_reserved_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="VM Reserved"]]/@value)[1]', 'bigint') / 1024 / 1024),
            vm_committed_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="VM Committed"]]/@value)[1]', 'bigint') / 1024 / 1024),
            locked_pages_allocated = r.c.value('(//memoryReport/entry[@description[.="Locked Pages Allocated"]]/@value)[1]', 'bigint'),
            large_pages_allocated = r.c.value('(//memoryReport/entry[@description[.="Large Pages Allocated"]]/@value)[1]', 'bigint'),
            emergency_memory_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Emergency Memory"]]/@value)[1]', 'bigint') / 1024 / 1024),
            emergency_memory_in_use_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Emergency Memory In Use"]]/@value)[1]', 'bigint') / 1024 / 1024),
            target_committed_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Target Committed"]]/@value)[1]', 'bigint') / 1024 / 1024),
            current_committed_gb = CONVERT(bigint, r.c.value('(//memoryReport/entry[@description[.="Current Committed"]]/@value)[1]', 'bigint') / 1024 / 1024),
            pages_allocated = r.c.value('(//memoryReport/entry[@description[.="Pages Allocated"]]/@value)[1]', 'bigint'),
            pages_reserved = r.c.value('(//memoryReport/entry[@description[.="Pages Reserved"]]/@value)[1]', 'bigint'),
            pages_free = r.c.value('(//memoryReport/entry[@description[.="Pages Free"]]/@value)[1]', 'bigint'),
            pages_in_use = r.c.value('(//memoryReport/entry[@description[.="Pages In Use"]]/@value)[1]', 'bigint'),
            page_alloc_potential = r.c.value('(//memoryReport/entry[@description[.="Page Alloc Potential"]]/@value)[1]', 'bigint'),
            numa_growth_phase = r.c.value('(//memoryReport/entry[@description[.="NUMA Growth Phase"]]/@value)[1]', 'bigint'),
            last_oom_factor = r.c.value('(//memoryReport/entry[@description[.="Last OOM Factor"]]/@value)[1]', 'bigint'),
            last_os_error = r.c.value('(//memoryReport/entry[@description[.="Last OS Error"]]/@value)[1]', 'bigint'),
            xml = r.c.query('.')
        INTO #memory
        FROM #sp_server_diagnostics_component_result AS s
        CROSS APPLY s.sp_server_diagnostics_component_result.nodes('/event/data/value/resource') AS r(c)
        WHERE (r.c.exist('@lastNotification[.= "RESOURCE_MEMPHYSICAL_LOW"]') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#memory, top 100 rows',
                x.*
            FROM #memory AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Memory conditions logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #memory AS m
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'memory')
                            THEN 'memory skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'memory')
                            THEN 'no memory issues found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no memory issues found!'
                        END;

                RAISERROR('No memory condition data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''memory conditions'','
                    END +
              N'm.event_time,
                m.lastNotification,
                m.outOfMemoryExceptions,
                m.isAnyPoolOutOfMemory,
                m.processOutOfMemoryPeriod,
                m.name,
                m.available_physical_memory_gb,
                m.available_virtual_memory_gb,
                m.available_paging_file_gb,
                m.working_set_gb,
                m.percent_of_committed_memory_in_ws,
                m.page_faults,
                m.system_physical_memory_high,
                m.system_physical_memory_low,
                m.process_physical_memory_low,
                m.process_virtual_memory_low,
                m.vm_reserved_gb,
                m.vm_committed_gb,
                m.locked_pages_allocated,
                m.large_pages_allocated,
                m.emergency_memory_gb,
                m.emergency_memory_in_use_gb,
                m.target_committed_gb,
                m.current_committed_gb,
                m.pages_allocated,
                m.pages_reserved,
                m.pages_free,
                m.pages_in_use,
                m.page_alloc_potential,
                m.numa_growth_phase,
                m.last_oom_factor,
                m.last_os_error
            FROM #memory AS m';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for memory conditions */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_memory_conditions
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE m.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                m.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_memory_conditions + N'
            (
                event_time,
                lastNotification,
                outOfMemoryExceptions,
                isAnyPoolOutOfMemory,
                processOutOfMemoryPeriod,
                name,
                available_physical_memory_gb,
                available_virtual_memory_gb,
                available_paging_file_gb,
                working_set_gb,
                percent_of_committed_memory_in_ws,
                page_faults,
                system_physical_memory_high,
                system_physical_memory_low,
                process_physical_memory_low,
                process_virtual_memory_low,
                vm_reserved_gb,
                vm_committed_gb,
                locked_pages_allocated,
                large_pages_allocated,
                emergency_memory_gb,
                emergency_memory_in_use_gb,
                target_committed_gb,
                current_committed_gb,
                pages_allocated,
                pages_reserved,
                pages_free,
                pages_in_use,
                page_alloc_potential,
                numa_growth_phase,
                last_oom_factor,
                last_os_error
            )'
            +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End memory*/

    /*Parse memory broker data*/
    IF @what_to_check IN ('all', 'memory')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing memory broker data', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            broker_id = w.x.value('(data[@name="id"]/value)[1]', 'bigint'),
            pool_metadata_id = w.x.value('(data[@name="pool_metadata_id"]/value)[1]', 'bigint'),
            delta_time = w.x.value('(data[@name="delta_time"]/value)[1]', 'bigint'),
            memory_ratio = w.x.value('(data[@name="memory_ratio"]/value)[1]', 'bigint'),
            new_target = w.x.value('(data[@name="new_target"]/value)[1]', 'bigint'),
            overall = w.x.value('(data[@name="overall"]/value)[1]', 'bigint'),
            rate = w.x.value('(data[@name="rate"]/value)[1]', 'bigint'),
            currently_predicated = w.x.value('(data[@name="currently_predicated"]/value)[1]', 'bigint'),
            currently_allocated = w.x.value('(data[@name="currently_allocated"]/value)[1]', 'bigint'),
            previously_allocated = w.x.value('(data[@name="previously_allocated"]/value)[1]', 'bigint'),
            broker = w.x.value('(data[@name="broker"]/value)[1]', 'nvarchar(256)'),
            notification = w.x.value('(data[@name="notification"]/value)[1]', 'nvarchar(256)'),
            xml = w.x.query('.')
        INTO #memory_broker_info
        FROM #memory_broker AS mb
        CROSS APPLY mb.memory_broker.nodes('//event') AS w(x)
        WHERE (w.x.exist('(data[@name="notification"]/value[.= "RESOURCE_MEMPHYSICAL_LOW"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#memory_broker_info, top 100 rows',
                x.*
            FROM #memory_broker_info AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Memory broker notifications logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #memory_broker_info AS mbi
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'memory')
                            THEN 'memory broker skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'memory')
                            THEN 'no memory pressure events found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no memory pressure events found!'
                        END;

                RAISERROR('No memory broker data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query for memory broker notifications */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N'mbi.event_time,
                mbi.broker_id,
                mbi.pool_metadata_id,
                mbi.delta_time,
                mbi.memory_ratio,
                mbi.new_target,
                mbi.overall,
                mbi.rate,
                mbi.currently_predicated,
                mbi.currently_allocated,
                mbi.previously_allocated,
                mbi.broker,
                mbi.notification'
                        ELSE N'finding = ''memory broker notifications'',
                mbi.event_time,
                mbi.broker_id,
                mbi.pool_metadata_id,
                mbi.delta_time,
                mbi.memory_ratio,
                new_target_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mbi.new_target / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                overall_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mbi.overall / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                mbi.rate,
                currently_predicated_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mbi.currently_predicated / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                currently_allocated_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mbi.currently_allocated / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                previously_allocated_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mbi.previously_allocated / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                mbi.broker,
                mbi.notification'
                    END +
              N'
            FROM #memory_broker_info AS mbi';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for memory broker */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_memory_broker
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE mbi.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                mbi.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO '
                + @log_table_memory_broker + N'
            (
                event_time,
                broker_id,
                pool_metadata_id,
                delta_time,
                memory_ratio,
                new_target,
                overall,
                rate,
                currently_predicated,
                currently_allocated,
                previously_allocated,
                broker,
                notification
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End memory broker analysis*/

    /*Parse memory node OOM data*/
    IF @what_to_check IN ('all', 'memory')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing memory node OOM data', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            node_id = w.x.value('(data[@name="id"]/value)[1]', 'bigint'),
            memory_node_id = w.x.value('(data[@name="memory_node_id"]/value)[1]', 'bigint'),
            memory_utilization_pct = w.x.value('(data[@name="memory_utilization_pct"]/value)[1]', 'bigint'),
            total_physical_memory_kb = w.x.value('(data[@name="total_physical_memory_kb"]/value)[1]', 'bigint'),
            available_physical_memory_kb = w.x.value('(data[@name="available_physical_memory_kb"]/value)[1]', 'bigint'),
            total_page_file_kb = w.x.value('(data[@name="total_page_file_kb"]/value)[1]', 'bigint'),
            available_page_file_kb = w.x.value('(data[@name="available_page_file_kb"]/value)[1]', 'bigint'),
            total_virtual_address_space_kb = w.x.value('(data[@name="total_virtual_address_space_kb"]/value)[1]', 'bigint'),
            available_virtual_address_space_kb = w.x.value('(data[@name="available_virtual_address_space_kb"]/value)[1]', 'bigint'),
            target_kb = w.x.value('(data[@name="target_kb"]/value)[1]', 'bigint'),
            reserved_kb = w.x.value('(data[@name="reserved_kb"]/value)[1]', 'bigint'),
            committed_kb = w.x.value('(data[@name="committed_kb"]/value)[1]', 'bigint'),
            shared_committed_kb = w.x.value('(data[@name="shared_committed_kb"]/value)[1]', 'numeric(38,0)'),
            awe_kb = w.x.value('(data[@name="awe_kb"]/value)[1]', 'bigint'),
            pages_kb = w.x.value('(data[@name="pages_kb"]/value)[1]', 'bigint'),
            failure_type = w.x.value('(data[@name="failure"]/text)[1]', 'nvarchar(256)'),
            failure_value = w.x.value('(data[@name="failure"]/value)[1]', 'bigint'),
            resources = w.x.value('(data[@name="resources"]/value)[1]', 'bigint'),
            factor_text = w.x.value('(data[@name="factor"]/text)[1]', 'nvarchar(256)'),
            factor_value = w.x.value('(data[@name="factor"]/value)[1]', 'bigint'),
            last_error = w.x.value('(data[@name="last_error"]/value)[1]', 'bigint'),
            pool_metadata_id = w.x.value('(data[@name="pool_metadata_id"]/value)[1]', 'bigint'),
            is_process_in_job = w.x.value('(data[@name="is_process_in_job"]/value)[1]', 'nvarchar(10)'),
            is_system_physical_memory_high = w.x.value('(data[@name="is_system_physical_memory_high"]/value)[1]', 'nvarchar(10)'),
            is_system_physical_memory_low = w.x.value('(data[@name="is_system_physical_memory_low"]/value)[1]', 'nvarchar(10)'),
            is_process_physical_memory_low = w.x.value('(data[@name="is_process_physical_memory_low"]/value)[1]', 'nvarchar(10)'),
            is_process_virtual_memory_low = w.x.value('(data[@name="is_process_virtual_memory_low"]/value)[1]', 'nvarchar(10)'),
            xml = w.x.query('.')
        INTO #memory_node_oom_info
        FROM #memory_node_oom AS mno
        CROSS APPLY mno.memory_node_oom.nodes('//event') AS w(x)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#memory_node_oom_info, top 100 rows',
                x.*
            FROM #memory_node_oom_info AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Memory node OOM events logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #memory_node_oom_info AS mnoi
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'memory')
                            THEN 'memory node OOM skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'memory')
                            THEN 'no memory node OOM events found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 '.'
                            ELSE 'no memory node OOM events found!'
                        END;

                RAISERROR('No memory oom data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query for memory node OOM events */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N'mnoi.event_time,
                mnoi.node_id,
                mnoi.memory_node_id,
                mnoi.memory_utilization_pct,
                mnoi.total_physical_memory_kb,
                mnoi.available_physical_memory_kb,
                mnoi.total_page_file_kb,
                mnoi.available_page_file_kb,
                mnoi.total_virtual_address_space_kb,
                mnoi.available_virtual_address_space_kb,
                mnoi.target_kb,
                mnoi.reserved_kb,
                mnoi.committed_kb,
                mnoi.shared_committed_kb,
                mnoi.awe_kb,
                mnoi.pages_kb,
                mnoi.failure_type,
                mnoi.failure_value,
                mnoi.resources,
                mnoi.factor_text,
                mnoi.factor_value,
                mnoi.last_error,
                mnoi.pool_metadata_id,
                mnoi.is_process_in_job,
                mnoi.is_system_physical_memory_high,
                mnoi.is_system_physical_memory_low,
                mnoi.is_process_physical_memory_low,
                mnoi.is_process_virtual_memory_low'
                        ELSE N'finding = ''memory node OOM events'',
                mnoi.event_time,
                mnoi.node_id,
                mnoi.memory_node_id,
                mnoi.memory_utilization_pct,
                total_physical_memory_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.total_physical_memory_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                available_physical_memory_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.available_physical_memory_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                total_page_file_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.total_page_file_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                available_page_file_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.available_page_file_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                target_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.target_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                reserved_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.reserved_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                committed_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.committed_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                shared_committed_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.shared_committed_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                awe_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.awe_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                pages_gb =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                mnoi.pages_kb / 1024.0 / 1024.0
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                mnoi.failure_type,
                mnoi.failure_value,
                mnoi.resources,
                mnoi.factor_text,
                mnoi.factor_value,
                mnoi.last_error,
                mnoi.pool_metadata_id,
                mnoi.is_process_in_job,
                mnoi.is_system_physical_memory_high,
                mnoi.is_system_physical_memory_low,
                mnoi.is_process_physical_memory_low,
                mnoi.is_process_virtual_memory_low'
                    END +
              N'
            FROM #memory_node_oom_info AS mnoi';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for memory node OOM */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_memory_node_oom
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
            WHERE mnoi.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
            ORDER BY
                mnoi.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_memory_node_oom + N'
            (
                event_time,
                node_id,
                memory_node_id,
                memory_utilization_pct,
                total_physical_memory_kb,
                available_physical_memory_kb,
                total_page_file_kb,
                available_page_file_kb,
                total_virtual_address_space_kb,
                available_virtual_address_space_kb,
                target_kb,
                reserved_kb,
                committed_kb,
                shared_committed_kb,
                awe_kb,
                pages_kb,
                failure_type,
                failure_value,
                resources,
                factor_text,
                factor_value,
                last_error,
                pool_metadata_id,
                is_process_in_job,
                is_system_physical_memory_high,
                is_system_physical_memory_low,
                is_process_physical_memory_low,
                is_process_virtual_memory_low
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End memory node OOM analysis*/

    /*Grab health stuff*/
    IF @what_to_check IN ('all', 'system')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing system stuff', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            state = w.x.value('(data[@name="state"]/text/text())[1]', 'nvarchar(256)'),
            spinlockBackoffs = w.x.value('(/event/data[@name="data"]/value/system/@spinlockBackoffs)[1]', 'bigint'),
            sickSpinlockType = w.x.value('(/event/data[@name="data"]/value/system/@sickSpinlockType)[1]', 'nvarchar(256)'),
            sickSpinlockTypeAfterAv = w.x.value('(/event/data[@name="data"]/value/system/@sickSpinlockTypeAfterAv)[1]', 'nvarchar(256)'),
            latchWarnings = w.x.value('(/event/data[@name="data"]/value/system/@latchWarnings)[1]', 'bigint'),
            isAccessViolationOccurred = w.x.value('(/event/data[@name="data"]/value/system/@isAccessViolationOccurred)[1]', 'bigint'),
            writeAccessViolationCount = w.x.value('(/event/data[@name="data"]/value/system/@writeAccessViolationCount)[1]', 'bigint'),
            totalDumpRequests = w.x.value('(/event/data[@name="data"]/value/system/@totalDumpRequests)[1]', 'bigint'),
            intervalDumpRequests = w.x.value('(/event/data[@name="data"]/value/system/@intervalDumpRequests)[1]', 'bigint'),
            nonYieldingTasksReported = w.x.value('(/event/data[@name="data"]/value/system/@nonYieldingTasksReported)[1]', 'bigint'),
            pageFaults = w.x.value('(/event/data[@name="data"]/value/system/@pageFaults)[1]', 'bigint'),
            systemCpuUtilization = w.x.value('(/event/data[@name="data"]/value/system/@systemCpuUtilization)[1]', 'bigint'),
            sqlCpuUtilization = w.x.value('(/event/data[@name="data"]/value/system/@sqlCpuUtilization)[1]', 'bigint'),
            BadPagesDetected = w.x.value('(/event/data[@name="data"]/value/system/@BadPagesDetected)[1]', 'bigint'),
            BadPagesFixed = w.x.value('(/event/data[@name="data"]/value/system/@BadPagesFixed)[1]', 'bigint'),
            xml = w.x.query('.')
        INTO #health
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x)
        WHERE w.x.exist('(data[@name="component"]/text[.= "SYSTEM"])') = 1
        AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#health, top 100 rows',
                x.*
            FROM #health AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Overall system health logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #health AS h
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'system')
                            THEN 'system health skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'system')
                            THEN 'no system health issues found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no system health issues found!'
                        END;

                RAISERROR('No system health data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''overall system health'','
                    END +
              N'
                h.event_time,
                h.state,
                h.spinlockBackoffs,
                h.sickSpinlockType,
                h.sickSpinlockTypeAfterAv,
                h.latchWarnings,
                h.isAccessViolationOccurred,
                h.writeAccessViolationCount,
                h.totalDumpRequests,
                h.intervalDumpRequests,
                h.nonYieldingTasksReported,
                h.pageFaults,
                h.systemCpuUtilization,
                h.sqlCpuUtilization,
                h.BadPagesDetected,
                h.BadPagesFixed
            FROM #health AS h';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for system health */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_system_health
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql = @dsql + N'
            WHERE h.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql = @dsql + N'
            ORDER BY
                h.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_system_health + N'
            (
                event_time,
                state,
                spinlockBackoffs,
                sickSpinlockType,
                sickSpinlockTypeAfterAv,
                latchWarnings,
                isAccessViolationOccurred,
                writeAccessViolationCount,
                totalDumpRequests,
                intervalDumpRequests,
                nonYieldingTasksReported,
                pageFaults,
                systemCpuUtilization,
                sqlCpuUtilization,
                BadPagesDetected,
                BadPagesFixed
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
    END; /*End system*/

    /*Parse scheduler monitor data*/
    IF @what_to_check IN ('all', 'system', 'cpu')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing scheduler monitor data', 0, 0) WITH NOWAIT;
        END;

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            scheduler_id = w.x.value('(data[@name="scheduler_id"]/value)[1]', 'integer'),
            cpu_id = w.x.value('(data[@name="cpu_id"]/value)[1]', 'integer'),
            status = w.x.value('(data[@name="status"]/text)[1]', 'nvarchar(256)'),
            is_online = w.x.value('(data[@name="is_online"]/value)[1]', 'bit'),
            is_runnable = w.x.value('(data[@name="is_runnable"]/value)[1]', 'bit'),
            is_running = w.x.value('(data[@name="is_running"]/value)[1]', 'bit'),
            non_yielding_time_ms = w.x.value('(data[@name="non_yielding_time"]/value)[1]', 'bigint'),
            thread_quantum_ms = w.x.value('(data[@name="thread_quantum"]/value)[1]', 'bigint'),
            xml = w.x.query('.')
        INTO #scheduler_issues
        FROM #scheduler_monitor AS sm
        CROSS APPLY sm.scheduler_monitor.nodes('//event') AS w(x)
        WHERE (w.x.exist('(data[@name="status"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#scheduler_issues, top 100 rows',
                x.*
            FROM #scheduler_issues AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Scheduler monitor issues logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #scheduler_issues AS si
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'system', 'cpu')
                            THEN 'scheduler monitoring skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'system', 'cpu')
                            THEN 'no scheduler issues found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no scheduler issues found!'
                        END;

                RAISERROR('No scheduler issues data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''scheduler monitor issues'','
                    END +
              N'
                si.event_time,
                si.scheduler_id,
                si.cpu_id,
                si.status,
                si.is_online,
                si.is_runnable,
                si.is_running,
                non_yielding_time_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                si.non_yielding_time_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    ),
                thread_quantum_ms =
                    REPLACE
                    (
                        CONVERT
                        (
                            nvarchar(30),
                            CONVERT
                            (
                                money,
                                si.thread_quantum_ms
                            ),
                            1
                        ),
                    N''.00'',
                    N''''
                    )
            FROM #scheduler_issues AS si';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for scheduler issues */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_scheduler_issues
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql = @dsql + N'
            WHERE si.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql = @dsql + N'
            ORDER BY
                si.event_time DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_scheduler_issues + N'
            (
                event_time,
                scheduler_id,
                cpu_id,
                status,
                is_online,
                is_runnable,
                is_running,
                non_yielding_time_ms,
                thread_quantum_ms
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;

    END; /*End scheduler monitor analysis*/

    /*Parse error_reported data*/
    IF @what_to_check IN ('all', 'system')
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing error_reported data', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #ignore_errors
        (
            error_number
        )
        VALUES
            (17830),
            (18056);

        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('@timestamp', 'datetime2')
                ),
            error_number = w.x.value('(data[@name="error_number"]/value)[1]', 'integer'),
            severity = w.x.value('(data[@name="severity"]/value)[1]', 'integer'),
            state = w.x.value('(data[@name="state"]/value)[1]', 'integer'),
            message = w.x.value('(data[@name="message"]/value)[1]', 'nvarchar(max)'),
            database_name = DB_NAME(w.x.value('(data[@name="database_id"]/value)[1]', 'integer')),
            database_id = w.x.value('(data[@name="database_id"]/value)[1]', 'integer'),
            xml = w.x.query('.')
        INTO #error_info
        FROM #error_reported AS er
        CROSS APPLY er.error_reported.nodes('//event') AS w(x)
        WHERE w.x.exist('(data[@name="severity"]/value)[. >= 16]') = 1
        AND (@warnings_only = 0 OR w.x.exist('(data[@name="severity"]/value)[. >= 19]') = 1)
        AND NOT EXISTS
        (
            SELECT
                1/0
            FROM #ignore_errors AS ie
            WHERE w.x.value('(data[@name="error_number"]/value)[1]', 'integer') = ie.error_number
        )
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#error_info, top 100 rows',
                x.*
            FROM #error_info AS x
            ORDER BY
                x.event_time DESC;
        END;

        /* Severe errors reported logging section */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM #error_info AS ei
        )
        BEGIN
            IF @log_to_table = 0
            BEGIN
                /* No results logic, only return if not logging */
                SELECT
                    finding =
                        CASE
                            WHEN @what_to_check NOT IN ('all', 'system')
                            THEN 'error reporting skipped, @what_to_check set to ' +
                                 @what_to_check
                            WHEN @what_to_check IN ('all', 'system')
                            THEN 'no severe errors found between ' +
                                 RTRIM(CONVERT(date, @start_date)) +
                                 ' and ' +
                                 RTRIM(CONVERT(date, @end_date)) +
                                 ' with @warnings_only set to ' +
                                 RTRIM(@warnings_only) +
                                 '.'
                            ELSE 'no severe errors found!'
                        END;

                RAISERROR('No error data found', 0, 0) WITH NOWAIT;
            END;
        END;
        ELSE
        BEGIN
            /* Build the query */
            SET @dsql = N'
            SELECT
                ' + CASE
                        WHEN @log_to_table = 1
                        THEN N''
                        ELSE N'finding = ''severe errors reported'','
                    END +
              N'
                ei.event_time,
                ei.error_number,
                ei.severity,
                ei.state,
                ei.message,
                ei.database_name,
                ei.database_id
            FROM #error_info AS ei';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for severe errors */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_severe_errors
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql = @dsql + N'
            WHERE ei.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql = @dsql + N'
            ORDER BY
                ei.event_time DESC,
                ei.severity DESC
            OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
            INSERT INTO
                ' + @log_table_severe_errors + N'
            (
                event_time,
                error_number,
                severity,
                state,
                message,
                database_name,
                database_id
            )' +
                @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;

            /* For ignored errors, only display to client */
            IF  @log_to_table = 0
            AND @debug = 1
            BEGIN
                SELECT
                    error_numbers_ignored =
                        N'Error Number Ignored: ' +
                        CONVERT(nvarchar(100), ie.error_number)
                FROM #ignore_errors AS ie;
            END;
        END;
    END; /*End error_reported analysis*/

    /*Grab useless stuff*/

    /*
    I'm pulling this out for now, until I find a good use for it.
    SELECT
        event_time =
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                    MINUTE,
                    GETUTCDATE(),
                    SYSDATETIME()
                ),
                w.x.value('(//@timestamp)[1]', 'datetime2')
            ),
        sessionId =
            w2.x2.value('@sessionId', 'bigint'),
        requestId =
            w2.x2.value('@requestId', 'bigint'),
        command =
            w2.x2.value('@command', 'nvarchar(256)'),
        taskAddress =
            CONVERT
            (
                binary(8),
                RIGHT
                (
                    '0000000000000000' +
                    SUBSTRING
                    (
                        w2.x2.value('@taskAddress', 'varchar(18)'),
                        3,
                        18
                    ),
                    16
                ),
                2
            ),
        cpuUtilization =
            w2.x2.value('@cpuUtilization', 'bigint'),
        cpuTimeMs =
            w2.x2.value('@cpuTimeMs', 'bigint'),
        xml = w2.x2.query('.')
    INTO #useless
    FROM #sp_server_diagnostics_component_result AS wi
    CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x)
    CROSS APPLY w.x.nodes('//data[@name="data"]/value/queryProcessing/cpuIntensiveRequests/request') AS w2(x2)
    WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
    AND   w.x.exist('//data[@name="data"]/value/queryProcessing/cpuIntensiveRequests/request') = 1
    AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT TOP (100) table_name = '#useless, top 100 rows', x.* FROM #useless AS x ORDER BY x.event_time DESC;
    END;

    SELECT
        finding = 'cpu intensive requests',
        u.event_time,
        u.sessionId,
        u.requestId,
        u.command,
        u.taskAddress,
        u.cpuUtilization,
        u.cpuTimeMs
    FROM #useless AS u
    ORDER BY
        u.cpuTimeMs DESC
    OPTION(RECOMPILE);
    */

    /*Grab blocking stuff*/
    IF
    (
        @what_to_check IN ('all', 'locking')
    AND @skip_locks = 0
    )
    BEGIN
        /*Validate database name if provided*/
        IF  @database_name IS NOT NULL
        AND @dbid IS NULL
        BEGIN
            RAISERROR('The specified database %s does not exist.', 11, 1, @database_name) WITH NOWAIT;
            RETURN;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Parsing locking stuff', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #blocking_xml
        WITH
            (TABLOCK)
        (
            event_time,
            human_events_xml
        )
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('(//@timestamp)[1]', 'datetime2')
                ),
            human_events_xml = w.x.query('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report')
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x)
        WHERE w.x.exist('(data[@name="component"]/text[.= "QUERY_PROCESSING"])') = 1
        AND   w.x.exist('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report') = 1
        AND  (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#blocking_xml, top 100 rows',
                x.*
            FROM #blocking_xml AS x
            ORDER BY
                x.event_time DESC;
        END;

        /*Blocked queries*/
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing blocked queries', 0, 0) WITH NOWAIT;
        END;

        SELECT
            bx.event_time,
            currentdbname = bd.value('(process/@currentdbname)[1]', 'sysname'),
            spid = bd.value('(process/@spid)[1]', 'integer'),
            ecid = bd.value('(process/@ecid)[1]', 'integer'),
            query_text_pre = bd.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
            wait_time = bd.value('(process/@waittime)[1]', 'bigint'),
            lastbatchstarted = bd.value('(process/@lastbatchstarted)[1]', 'datetime2'),
            lastbatchcompleted = bd.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
            wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
            status = bd.value('(process/@status)[1]', 'nvarchar(10)'),
            priority = bd.value('(process/@priority)[1]', 'integer'),
            transaction_count = bd.value('(process/@trancount)[1]', 'integer'),
            client_app = bd.value('(process/@clientapp)[1]', 'nvarchar(256)'),
            host_name = bd.value('(process/@hostname)[1]', 'nvarchar(256)'),
            login_name = bd.value('(process/@loginname)[1]', 'nvarchar(256)'),
            isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
            log_used = bd.value('(process/@logused)[1]', 'bigint'),
            clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'),
            clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'),
            process_id = bd.value('(process/@id)[1]', 'sysname'),
            scheduler_id = bd.value('(process/@schedulerid)[1]', 'bigint'),
            kpid = bd.value('(process/@kpid)[1]', 'bigint'),
            sbid = bd.value('(process/@sbid)[1]', 'bigint'),
            last_attention = bd.value('(process/@lastattention)[1]', 'datetime2'),
            xactid = bd.value('(process/@xactid)[1]', 'bigint'),
            currentdb = bd.value('(process/@currentdb)[1]', 'bigint'),
            lock_timeout = bd.value('(process/@lockTimeout)[1]', 'bigint'),
            host_pid = bd.value('(process/@hostpid)[1]', 'bigint'),
            task_priority = bd.value('(process/@taskpriority)[1]', 'bigint'),
            owner_id = bd.value('(process/@ownerId)[1]', 'bigint'),
            transaction_name = bd.value('(process/@transactionname)[1]', 'nvarchar(1024)'),
            last_tran_started = bd.value('(process/@lasttranstarted)[1]', 'datetime2'),
            xdes = bd.value('(process/@XDES)[1]', 'sysname'),
            lock_mode = bd.value('(process/@lockMode)[1]', 'nvarchar(10)'),
            activity = CASE WHEN bd.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END,
            blocked_process_report = bd.query('.')
        INTO #blocked
        FROM #blocking_xml AS bx
        OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
        OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
        WHERE bd.exist('process/@spid') = 1
        AND   (bd.exist('process[@currentdbname = sql:variable("@database_name")]') = 1 OR @database_name IS NULL)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('Adding query_text to #blocked', 0, 0) WITH NOWAIT;
        END;

        ALTER TABLE #blocked
        ADD query_text AS
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
               query_text_pre COLLATE Latin1_General_BIN2,
           NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
           NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
           NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
        PERSISTED;

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#blocked, top 100 rows',
                x.*
            FROM #blocked AS x
            ORDER BY
                x.event_time DESC;
        END;

        /*Blocking queries*/
        IF @debug = 1
        BEGIN
            RAISERROR('Parsing blocking queries', 0, 0) WITH NOWAIT;
        END;

        SELECT
            bx.event_time,
            currentdbname = bg.value('(process/@currentdbname)[1]', 'sysname'),
            spid = bg.value('(process/@spid)[1]', 'integer'),
            ecid = bg.value('(process/@ecid)[1]', 'integer'),
            query_text_pre = bg.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
            wait_time = bg.value('(process/@waittime)[1]', 'bigint'),
            last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'),
            last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
            wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
            status = bg.value('(process/@status)[1]', 'nvarchar(10)'),
            priority = bg.value('(process/@priority)[1]', 'integer'),
            transaction_count = bg.value('(process/@trancount)[1]', 'integer'),
            client_app = bg.value('(process/@clientapp)[1]', 'nvarchar(256)'),
            host_name = bg.value('(process/@hostname)[1]', 'nvarchar(256)'),
            login_name = bg.value('(process/@loginname)[1]', 'nvarchar(256)'),
            isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
            log_used = bg.value('(process/@logused)[1]', 'bigint'),
            clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'),
            clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'),
            process_id = bg.value('(process/@id)[1]', 'sysname'),
            scheduler_id = bg.value('(process/@schedulerid)[1]', 'bigint'),
            kpid = bg.value('(process/@kpid)[1]', 'bigint'),
            sbid = bg.value('(process/@sbid)[1]', 'bigint'),
            last_attention = bg.value('(process/@lastattention)[1]', 'datetime2'),
            xactid = bg.value('(process/@xactid)[1]', 'bigint'),
            currentdb = bg.value('(process/@currentdb)[1]', 'bigint'),
            lock_timeout = bg.value('(process/@lockTimeout)[1]', 'bigint'),
            host_pid = bg.value('(process/@hostpid)[1]', 'bigint'),
            task_priority = bg.value('(process/@taskpriority)[1]', 'bigint'),
            owner_id = bg.value('(process/@ownerId)[1]', 'bigint'),
            transaction_name = bg.value('(process/@transactionname)[1]', 'nvarchar(1024)'),
            last_tran_started = bg.value('(process/@lasttranstarted)[1]', 'datetime2'),
            xdes = bg.value('(process/@XDES)[1]', 'sysname'),
            lock_mode = bg.value('(process/@lockMode)[1]', 'nvarchar(10)'),
            activity = CASE WHEN bg.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END,
            blocked_process_report = bg.query('.')
        INTO #blocking
        FROM #blocking_xml AS bx
        OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
        OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
        WHERE bg.exist('process/@spid') = 1
        AND   (bg.exist('process[@currentdbname = sql:variable("@database_name")]') = 1 OR @database_name IS NULL)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('Adding query_text to #blocking', 0, 0) WITH NOWAIT;
        END;

        ALTER TABLE #blocking
        ADD query_text AS
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
               query_text_pre COLLATE Latin1_General_BIN2,
           NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
           NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
           NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
        PERSISTED;

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#blocking, top 100 rows',
                x.*
            FROM #blocking AS x
            ORDER BY
                x.event_time DESC;
        END;

        /*Put it together*/
        IF @debug = 1
        BEGIN
            RAISERROR('Inserting to #blocks', 0, 0) WITH NOWAIT;
        END;

        SELECT
            kheb.event_time,
            kheb.currentdbname,
            kheb.activity,
            kheb.spid,
            kheb.ecid,
            query_text =
                CASE
                    WHEN kheb.query_text
                         LIKE CONVERT(nvarchar(1), 0x0a00, 0) + N'Proc |[Database Id = %' ESCAPE N'|'
                    THEN
                        (
                            SELECT
                                [processing-instruction(query)] =
                                    OBJECT_SCHEMA_NAME
                                    (
                                            SUBSTRING
                                            (
                                                kheb.query_text,
                                                CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                                LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                            )
                                            ,
                                            SUBSTRING
                                            (
                                                kheb.query_text,
                                                CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                                CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                            )
                                    ) +
                                    N'.' +
                                    OBJECT_NAME
                                    (
                                         SUBSTRING
                                         (
                                             kheb.query_text,
                                             CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                             LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                         )
                                         ,
                                         SUBSTRING
                                         (
                                             kheb.query_text,
                                             CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                             CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                         )
                                    )
                            FOR XML
                                PATH(N''),
                                TYPE
                        )
                    ELSE
                        (
                            SELECT
                                [processing-instruction(query)] =
                                    kheb.query_text
                            FOR XML
                                PATH(N''),
                                TYPE
                        )
                END,
            wait_time_ms =
                kheb.wait_time,
            kheb.status,
            kheb.isolation_level,
            kheb.transaction_count,
            kheb.last_transaction_started,
            kheb.last_transaction_completed,
            client_option_1 =
                SUBSTRING
                (
                    CASE WHEN kheb.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
                    CASE WHEN kheb.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
                    3,
                    8000
                ),
            client_option_2 =
                SUBSTRING
                (
                    CASE WHEN kheb.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
                    CASE WHEN kheb.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
                    3,
                    8000
                ),
            kheb.wait_resource,
            kheb.priority,
            kheb.log_used,
            kheb.client_app,
            kheb.host_name,
            kheb.login_name,
            kheb.process_id,
            kheb.scheduler_id,
            kheb.kpid,
            kheb.sbid,
            kheb.last_attention,
            kheb.xactid,
            kheb.currentdb,
            kheb.lock_timeout,
            kheb.host_pid,
            kheb.task_priority,
            kheb.owner_id,
            kheb.transaction_name,
            kheb.last_tran_started,
            kheb.xdes,
            kheb.lock_mode,
            kheb.blocked_process_report
        INTO #blocks
        FROM
        (
            SELECT
                bg.*
            FROM #blocking AS bg
            WHERE (bg.currentdbname = @database_name
                   OR @database_name IS NULL)

            UNION ALL

            SELECT
                bd.*
            FROM #blocked AS bd
            WHERE (bd.currentdbname = @database_name
                   OR @database_name IS NULL)
        ) AS kheb
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#blocks, top 100 rows',
                x.*
            FROM #blocks AS x
            ORDER BY
                x.event_time DESC;
        END;

        IF EXISTS
        (
            SELECT
                1/0
            FROM #blocks AS b
        )
        BEGIN
            /* Build the query */
            SET @dsql = N'
                SELECT
                    ' + CASE
                            WHEN @log_to_table = 1
                            THEN N''
                            ELSE N'finding = ''blocked process report'','
                        END +
                  N'
                    b.event_time,
                    b.currentdbname,
                    b.activity,
                    b.spid,
                    b.ecid,
                    b.query_text,
                    b.wait_time_ms,
                    b.status,
                    b.isolation_level,
                    b.transaction_count,
                    b.last_transaction_started,
                    b.last_transaction_completed,
                    b.client_option_1,
                    b.client_option_2,
                    b.wait_resource,
                    b.priority,
                    b.log_used,
                    b.client_app,
                    b.host_name,
                    b.login_name,
                    b.process_id,
                    b.scheduler_id,
                    b.kpid,
                    b.sbid,
                    b.last_attention,
                    b.xactid,
                    b.currentdb,
                    b.lock_timeout,
                    b.host_pid,
                    b.task_priority,
                    b.owner_id,
                    b.transaction_name,
                    b.last_tran_started,
                    b.xdes,
                    b.lock_mode,
                    b.blocked_process_report
                FROM #blocks AS b';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_time for blocking */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_blocking
                        ),
                        '{date_column}',
                        'event_time'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
                WHERE b.event_time > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
                ORDER BY
                    b.event_time DESC,
                    CASE
                        WHEN b.activity = ''blocking''
                        THEN -1
                        ELSE +1
                    END
                OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
                INSERT INTO
                    ' + @log_table_blocking + N'
                (
                    event_time,
                    currentdbname,
                    activity,
                    spid,
                    ecid,
                    query_text,
                    wait_time_ms,
                    status,
                    isolation_level,
                    transaction_count,
                    last_transaction_started,
                    last_transaction_completed,
                    client_option_1,
                    client_option_2,
                    wait_resource,
                    priority,
                    log_used,
                    client_app,
                    host_name,
                    login_name,
                    process_id,
                    scheduler_id,
                    kpid,
                    sbid,
                    last_attention,
                    xactid,
                    currentdb,
                    lock_timeout,
                    host_pid,
                    task_priority,
                    owner_id,
                    transaction_name,
                    last_tran_started,
                    xdes,
                    lock_mode,
                    blocked_process_report
                )' +
                    @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
        ELSE
        BEGIN
            SELECT
                finding = CASE
                    WHEN @what_to_check NOT IN ('all', 'locking')
                    THEN 'blocking skipped, @what_to_check set to ' + @what_to_check
                    WHEN @skip_locks = 1
                    THEN 'blocking skipped, @skip_locks set to 1'
                    WHEN @what_to_check IN ('all', 'locking')
                    THEN 'no blocking found between ' +
                         RTRIM(CONVERT(date, @start_date)) +
                         ' and ' +
                         RTRIM(CONVERT(date, @end_date)) +
                         ' with @warnings_only set to ' +
                         RTRIM(@warnings_only)
                    ELSE 'no blocking found!'
                END;
        END;

        /*Grab available plans from the cache*/
        IF @debug = 1
        BEGIN
            RAISERROR('Inserting to #available_plans (blocking)', 0, 0) WITH NOWAIT;
        END;

        SELECT DISTINCT
            b.*
        INTO #available_plans
        FROM
        (
            SELECT
                finding =
                    'available plans for blocking',
                b.currentdbname,
                query_text =
                    TRY_CAST(b.query_text AS nvarchar(MAX)),
                sql_handle =
                    CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
                stmtstart =
                    ISNULL(n.c.value('@stmtstart', 'integer'), 0),
                stmtend =
                    ISNULL(n.c.value('@stmtend', 'integer'), -1)
            FROM #blocks AS b
            CROSS APPLY b.blocked_process_report.nodes('/blocked-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
            WHERE (b.currentdbname = @database_name
                    OR @database_name IS NULL)

            UNION ALL

            SELECT
                finding =
                    CONVERT(varchar(30), 'available plans for blocking'),
                b.currentdbname,
                query_text =
                    TRY_CAST(b.query_text AS nvarchar(MAX)),
                sql_handle =
                    CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
                stmtstart =
                    ISNULL(n.c.value('@stmtstart', 'integer'), 0),
                stmtend =
                    ISNULL(n.c.value('@stmtend', 'integer'), -1)
            FROM #blocks AS b
            CROSS APPLY b.blocked_process_report.nodes('/blocking-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
            WHERE (b.currentdbname = @database_name
                    OR @database_name IS NULL)
        ) AS b
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting to #deadlocks', 0, 0) WITH NOWAIT;
        END;

        SELECT
            x.xml_deadlock_report,
            event_date = x.xml_deadlock_report.value('(event/@timestamp)[1]', 'datetime2'),
            victim_id = x.xml_deadlock_report.value('(//deadlock/victim-list/victimProcess/@id)[1]', 'nvarchar(256)'),
            deadlock_graph = x.xml_deadlock_report.query('/event/data/value/deadlock')
        INTO #deadlocks
        FROM #xml_deadlock_report AS x
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#deadlocks, top 100 rows',
                x.*
            FROM #deadlocks AS x;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting to #deadlocks_parsed', 0, 0) WITH NOWAIT;
        END;

        SELECT
            x.event_date,
            x.id,
            x.victim_id,
            database_name =
                ISNULL
                (
                    DB_NAME(x.database_id),
                    N'UNKNOWN'
                ),
            x.current_database_name,
            x.query_text_pre,
            x.priority,
            x.log_used,
            x.wait_time,
            x.transaction_name,
            x.last_tran_started,
            x.last_batch_started,
            x.last_batch_completed,
            x.lock_mode,
            x.status,
            x.transaction_count,
            x.client_app,
            x.host_name,
            x.login_name,
            x.isolation_level,
            client_option_1 =
                SUBSTRING
                (
                    CASE WHEN x.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
                    CASE WHEN x.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
                    CASE WHEN x.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
                    CASE WHEN x.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
                    CASE WHEN x.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
                    CASE WHEN x.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
                    CASE WHEN x.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN x.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
                    CASE WHEN x.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
                    CASE WHEN x.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
                    CASE WHEN x.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
                    CASE WHEN x.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
                    CASE WHEN x.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
                    CASE WHEN x.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
                    CASE WHEN x.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
                    3,
                    500
                ),
            client_option_2 =
                SUBSTRING
                (
                    CASE WHEN x.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
                    CASE WHEN x.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
                    CASE WHEN x.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
                    CASE WHEN x.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
                    CASE WHEN x.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
                    CASE WHEN x.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
                    CASE WHEN x.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
                    CASE WHEN x.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
                    CASE WHEN x.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
                    CASE WHEN x.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
                    CASE WHEN x.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
                    CASE WHEN x.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
                    CASE WHEN x.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
                    CASE WHEN x.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
                    CASE WHEN x.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
                    CASE WHEN x.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
                    3,
                    500
                ),
            x.deadlock_resources,
            x.deadlock_graph,
            x.process_xml
        INTO #deadlocks_parsed
        FROM
        (
            SELECT
                event_date =
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            GETUTCDATE(),
                            SYSDATETIME()
                        ),
                        d.event_date
                    ),
                d.victim_id,
                d.deadlock_graph,
                id = e.x.value('@id', 'sysname'),
                database_id = e.x.value('@currentdb', 'bigint'),
                current_database_name = e.x.value('@currentdbname', 'sysname'),
                priority = e.x.value('@priority', 'smallint'),
                log_used = e.x.value('@logused', 'bigint'),
                wait_time = e.x.value('@waittime', 'bigint'),
                transaction_name = e.x.value('@transactionname', 'sysname'),
                last_tran_started = e.x.value('@lasttranstarted', 'datetime2'),
                last_batch_started = e.x.value('@lastbatchstarted', 'datetime2'),
                last_batch_completed = e.x.value('@lastbatchcompleted', 'datetime2'),
                lock_mode = e.x.value('@lockMode', 'sysname'),
                status = e.x.value('@status', 'sysname'),
                transaction_count = e.x.value('@trancount', 'bigint'),
                client_app = e.x.value('@clientapp', 'nvarchar(1024)'),
                host_name = e.x.value('@hostname', 'sysname'),
                login_name = e.x.value('@loginname', 'sysname'),
                isolation_level = e.x.value('@isolationlevel', 'sysname'),
                clientoption1 = e.x.value('@clientoption1', 'bigint'),
                clientoption2 = e.x.value('@clientoption2', 'bigint'),
                query_text_pre = e.x.value('(inputbuf/text())[1]', 'nvarchar(max)'),
                process_xml = e.x.query(N'.'),
                deadlock_resources = d.xml_deadlock_report.query('//deadlock/resource-list')
            FROM #deadlocks AS d
            CROSS APPLY d.xml_deadlock_report.nodes('//deadlock/process-list/process') AS e(x)
        ) AS x
        WHERE (x.database_id = @dbid
               OR @dbid IS NULL)
        OR    (x.current_database_name = @database_name
               OR @database_name IS NULL)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('Adding query_text to #deadlocks_parsed', 0, 0) WITH NOWAIT;
        END;

        ALTER TABLE #deadlocks_parsed
        ADD query_text AS
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
               query_text_pre COLLATE Latin1_General_BIN2,
           NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
           NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
           NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
        PERSISTED;

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#deadlocks_parsed, top 100 rows',
                x.*
            FROM #deadlocks_parsed AS x;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Returning deadlocks', 0, 0) WITH NOWAIT;
        END;

        IF EXISTS
        (
            SELECT
                1/0
            FROM #deadlocks_parsed AS dp
        )
        BEGIN
            /* Build the query */
            SET @dsql = N'
                SELECT
                    ' + CASE
                            WHEN @log_to_table = 1
                            THEN N''
                            ELSE N'finding = ''xml deadlock report'','
                        END +
                  N'
                    dp.event_date,
                    is_victim =
                        CASE
                            WHEN dp.id = dp.victim_id
                            THEN 1
                            ELSE 0
                        END,
                    dp.database_name,
                    dp.current_database_name,
                    query_text =
                        CASE
                            WHEN dp.query_text
                                 LIKE CONVERT(nvarchar(1), 0x0a00, 0) + N''Proc |[Database Id = %'' ESCAPE N''|''
                            THEN
                                (
                                    SELECT
                                        [processing-instruction(query)] =
                                            OBJECT_SCHEMA_NAME
                                            (
                                                    SUBSTRING
                                                    (
                                                        dp.query_text,
                                                        CHARINDEX(N''Object Id = '', dp.query_text) + 12,
                                                        LEN(dp.query_text) - (CHARINDEX(N''Object Id = '', dp.query_text) + 12)
                                                    )
                                                    ,
                                                    SUBSTRING
                                                    (
                                                        dp.query_text,
                                                        CHARINDEX(N''Database Id = '', dp.query_text) + 14,
                                                        CHARINDEX(N''Object Id'', dp.query_text) - (CHARINDEX(N''Database Id = '', dp.query_text) + 14)
                                                    )
                                            ) +
                                            N''.'' +
                                            OBJECT_NAME
                                            (
                                                 SUBSTRING
                                                 (
                                                     dp.query_text,
                                                     CHARINDEX(N''Object Id = '', dp.query_text) + 12,
                                                     LEN(dp.query_text) - (CHARINDEX(N''Object Id = '', dp.query_text) + 12)
                                                 )
                                                 ,
                                                 SUBSTRING
                                                 (
                                                     dp.query_text,
                                                     CHARINDEX(N''Database Id = '', dp.query_text) + 14,
                                                     CHARINDEX(N''Object Id'', dp.query_text) - (CHARINDEX(N''Database Id = '', dp.query_text) + 14)
                                                 )
                                            )
                                    FOR XML
                                        PATH(N''''),
                                        TYPE
                                )
                            ELSE
                                (
                                    SELECT
                                        [processing-instruction(query)] =
                                            dp.query_text
                                    FOR XML
                                        PATH(N''''),
                                        TYPE
                                )
                        END,
                    dp.deadlock_resources,
                    dp.isolation_level,
                    dp.lock_mode,
                    dp.status,
                    dp.wait_time,
                    dp.log_used,
                    dp.transaction_name,
                    dp.transaction_count,
                    dp.client_option_1,
                    dp.client_option_2,
                    dp.last_tran_started,
                    dp.last_batch_started,
                    dp.last_batch_completed,
                    dp.client_app,
                    dp.host_name,
                    dp.login_name,
                    dp.priority,
                    dp.deadlock_graph
                FROM #deadlocks_parsed AS dp';

            /* Add the WHERE clause only for table logging */
            IF @log_to_table = 1
            BEGIN
                /* Get max event_date for deadlocks */
                SET @mdsql_execute =
                    REPLACE
                    (
                        REPLACE
                        (
                            @mdsql_template,
                            '{table_check}',
                            @log_table_deadlocks
                        ),
                        '{date_column}',
                        'event_date'
                    );

                IF @debug = 1
                BEGIN
                    PRINT @mdsql_execute;
                END;

                EXECUTE sys.sp_executesql
                    @mdsql_execute,
                  N'@max_event_time datetime2(7) OUTPUT',
                    @max_event_time OUTPUT;

                SET @dsql += N'
                WHERE dp.event_date > @max_event_time';
            END;

            /* Add the ORDER BY clause */
            SET @dsql += N'
                ORDER BY
                    dp.event_date,
                    is_victim
                OPTION(RECOMPILE);
            ';

            /* Handle table logging */
            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
                INSERT INTO
                    ' + @log_table_deadlocks + N'
                (
                    event_date,
                    is_victim,
                    database_name,
                    current_database_name,
                    query_text,
                    deadlock_resources,
                    isolation_level,
                    lock_mode,
                    status,
                    wait_time,
                    log_used,
                    transaction_name,
                    transaction_count,
                    client_option_1,
                    client_option_2,
                    last_tran_started,
                    last_batch_started,
                    last_batch_completed,
                    client_app,
                    host_name,
                    login_name,
                    priority,
                    deadlock_graph
                )' +
                    @dsql;

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql,
                  N'@max_event_time datetime2(7)',
                    @max_event_time;
            END;

            /* Execute the query for client results */
            IF @log_to_table = 0
            BEGIN
                IF @debug = 1
                BEGIN
                    PRINT @dsql;
                END;

                EXECUTE sys.sp_executesql
                    @dsql;
            END;
        END;
        ELSE
        BEGIN
            SELECT
                finding = CASE
                    WHEN @what_to_check NOT IN ('all', 'locking')
                    THEN 'deadlocks skipped, @what_to_check set to ' + @what_to_check
                    WHEN @skip_locks = 1
                    THEN 'deadlocks skipped, @skip_locks set to 1'
                    WHEN @what_to_check IN ('all', 'locking')
                    THEN 'no deadlocks found between ' +
                         RTRIM(CONVERT(date, @start_date)) +
                         ' and ' +
                         RTRIM(CONVERT(date, @end_date))
                    ELSE 'no deadlocks found!'
                END;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting #available_plans (deadlocks)', 0, 0) WITH NOWAIT;
        END;

        INSERT
            #available_plans WITH (TABLOCKX)
        (
            finding,
            currentdbname,
            query_text,
            sql_handle,
            stmtstart,
            stmtend
        )
        SELECT
            finding =
                'available plans for deadlocks',
            dp.database_name,
            dp.query_text,
            sql_handle =
                CONVERT(varbinary(64), e.x.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                0,
            stmtend =
                0
        FROM #deadlocks_parsed AS dp
        CROSS APPLY dp.process_xml.nodes('//executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS e(x)
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT TOP (100)
                table_name = '#available_plans, top 100 rows',
                x.*
            FROM #available_plans AS x;
        END;

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting #dm_exec_query_stats_sh', 0, 0) WITH NOWAIT;
        END;

        SELECT
            deqs.sql_handle,
            deqs.plan_handle,
            deqs.statement_start_offset,
            deqs.statement_end_offset,
            deqs.creation_time,
            deqs.last_execution_time,
            deqs.execution_count,
            total_worker_time_ms =
                deqs.total_worker_time / 1000.,
            avg_worker_time_ms =
                CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / NULLIF(deqs.execution_count, 0)),
            total_elapsed_time_ms =
                deqs.total_elapsed_time / 1000.,
            avg_elapsed_time =
                CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
            executions_per_second =
                ISNULL
                (
                    deqs.execution_count /
                        NULLIF
                        (
                            DATEDIFF
                            (
                                SECOND,
                                deqs.creation_time,
                                NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
                            ),
                            0
                        ),
                        0
                ),
            total_physical_reads_mb =
                deqs.total_physical_reads * 8. / 1024.,
            total_logical_writes_mb =
                deqs.total_logical_writes * 8. / 1024.,
            total_logical_reads_mb =
                deqs.total_logical_reads * 8. / 1024.,
            min_grant_mb =
                deqs.min_grant_kb / 1024.,
            max_grant_mb =
                deqs.max_grant_kb / 1024.,
            min_used_grant_mb =
                deqs.min_used_grant_kb / 1024.,
            max_used_grant_mb =
                deqs.max_used_grant_kb / 1024.,
            deqs.min_reserved_threads,
            deqs.max_reserved_threads,
            deqs.min_used_threads,
            deqs.max_used_threads,
            deqs.total_rows
        INTO #dm_exec_query_stats_sh
        FROM sys.dm_exec_query_stats AS deqs
        WHERE EXISTS
        (
            SELECT
                1/0
            FROM #available_plans AS ap
            WHERE ap.sql_handle = deqs.sql_handle
        )
        AND deqs.query_hash IS NOT NULL;

        IF @debug = 1
        BEGIN
            RAISERROR('Indexing #dm_exec_query_stats_sh', 0, 0) WITH NOWAIT;
        END;

        CREATE CLUSTERED INDEX
            deqs_sh
        ON #dm_exec_query_stats_sh
        (
            sql_handle,
            plan_handle
        );

        IF @debug = 1
        BEGIN
            RAISERROR('Inserting #all_available_plans (deadlocks)', 0, 0) WITH NOWAIT;
        END;

        SELECT
            ap.finding,
            ap.currentdbname,
            query_text =
                TRY_CAST(ap.query_text AS xml),
            ap.query_plan,
            ap.creation_time,
            ap.last_execution_time,
            ap.execution_count,
            ap.executions_per_second,
            ap.total_worker_time_ms,
            ap.avg_worker_time_ms,
            ap.total_elapsed_time_ms,
            ap.avg_elapsed_time,
            ap.total_logical_reads_mb,
            ap.total_physical_reads_mb,
            ap.total_logical_writes_mb,
            ap.min_grant_mb,
            ap.max_grant_mb,
            ap.min_used_grant_mb,
            ap.max_used_grant_mb,
            ap.min_reserved_threads,
            ap.max_reserved_threads,
            ap.min_used_threads,
            ap.max_used_threads,
            ap.total_rows,
            ap.sql_handle,
            ap.statement_start_offset,
            ap.statement_end_offset
        INTO #all_available_plans
        FROM
        (
            SELECT
                ap.*,
                c.statement_start_offset,
                c.statement_end_offset,
                c.creation_time,
                c.last_execution_time,
                c.execution_count,
                c.total_worker_time_ms,
                c.avg_worker_time_ms,
                c.total_elapsed_time_ms,
                c.avg_elapsed_time,
                c.executions_per_second,
                c.total_physical_reads_mb,
                c.total_logical_writes_mb,
                c.total_logical_reads_mb,
                c.min_grant_mb,
                c.max_grant_mb,
                c.min_used_grant_mb,
                c.max_used_grant_mb,
                c.min_reserved_threads,
                c.max_reserved_threads,
                c.min_used_threads,
                c.max_used_threads,
                c.total_rows,
                c.query_plan
            FROM #available_plans AS ap
            OUTER APPLY
            (
                SELECT
                    deqs.*,
                    query_plan =
                        TRY_CAST(deps.query_plan AS xml)
                FROM #dm_exec_query_stats_sh AS deqs
                OUTER APPLY sys.dm_exec_text_query_plan
                (
                    deqs.plan_handle,
                    deqs.statement_start_offset,
                    deqs.statement_end_offset
                ) AS deps
                WHERE deqs.sql_handle = ap.sql_handle
            ) AS c
        ) AS ap
        WHERE ap.query_plan IS NOT NULL
        ORDER BY
            ap.avg_worker_time_ms DESC
        OPTION(RECOMPILE);

        IF EXISTS
        (
            SELECT
                1/0
            FROM #all_available_plans AS ap
            WHERE ap.finding = 'available plans for blocking'
        )
        BEGIN
            SELECT
                aap.*
            FROM #all_available_plans AS aap
            WHERE aap.finding = 'available plans for blocking'
            ORDER BY
                aap.avg_worker_time_ms DESC
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            /* Only show this message if we found blocking but no plans */
            IF EXISTS
            (
                SELECT
                    1/0
                FROM #blocks AS b
            )
            BEGIN
                SELECT
                    finding = 'no cached plans found for blocking queries';
            END;
        END;

        IF EXISTS
        (
            SELECT
                1/0
            FROM #all_available_plans AS ap
            WHERE ap.finding = 'available plans for deadlocks'
        )
        BEGIN
            SELECT
                aap.*
            FROM #all_available_plans AS aap
            WHERE aap.finding = 'available plans for deadlocks'
            ORDER BY
                aap.avg_worker_time_ms DESC
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            /* Only show this message if we found deadlocks but no plans */
            IF EXISTS
            (
                SELECT
                    1/0
                FROM #deadlocks_parsed AS dp
            )
            BEGIN
                SELECT
                    finding = 'no cached plans found for deadlock queries';
            END;
        END;
    END; /*End locks*/
END; /*Final End*/
GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
██╗  ██╗██╗   ██╗███╗   ███╗ █████╗ ███╗   ██╗
██║  ██║██║   ██║████╗ ████║██╔══██╗████╗  ██║
███████║██║   ██║██╔████╔██║███████║██╔██╗ ██║
██╔══██║██║   ██║██║╚██╔╝██║██╔══██║██║╚██╗██║
██║  ██║╚██████╔╝██║ ╚═╝ ██║██║  ██║██║ ╚████║
╚═╝  ╚═╝ ╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝

███████╗██╗   ██╗███████╗███╗   ██╗████████╗███████╗
██╔════╝██║   ██║██╔════╝████╗  ██║╚══██╔══╝██╔════╝
█████╗  ██║   ██║█████╗  ██╔██╗ ██║   ██║   ███████╗
██╔══╝  ╚██╗ ██╔╝██╔══╝  ██║╚██╗██║   ██║   ╚════██║
███████╗ ╚████╔╝ ███████╗██║ ╚████║   ██║   ███████║
╚══════╝  ╚═══╝  ╚══════╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_HumanEvents
    @help = 1;

For working through errors:
EXECUTE sp_HumanEvents
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/

IF OBJECT_ID(N'dbo.sp_HumanEvents', N'P') IS NULL
   BEGIN
       EXECUTE (N'CREATE PROCEDURE dbo.sp_HumanEvents AS RETURN 138;');
   END;
GO

ALTER PROCEDURE
    dbo.sp_HumanEvents
(
    @event_type sysname = N'query',
    @query_duration_ms integer = 500,
    @query_sort_order nvarchar(10) = N'cpu',
    @skip_plans bit = 0,
    @blocking_duration_ms integer = 500,
    @wait_type nvarchar(4000) = N'ALL',
    @wait_duration_ms integer = 10,
    @client_app_name sysname = N'',
    @client_hostname sysname = N'',
    @database_name sysname = N'',
    @session_id nvarchar(7) = N'',
    @sample_divisor integer = 5,
    @username sysname = N'',
    @object_name sysname = N'',
    @object_schema sysname = N'dbo',
    @requested_memory_mb integer = 0,
    @seconds_sample tinyint = 10,
    @gimme_danger bit = 0,
    @keep_alive bit = 0,
    @custom_name sysname = N'',
    @output_database_name sysname = N'',
    @output_schema_name sysname = N'dbo',
    @delete_retention_days integer = 3,
    @cleanup bit = 0,
    @max_memory_kb bigint = 102400,
    @target_output sysname = N'ring_buffer', /*output target for extended events: ring_buffer or event_file*/
    @version varchar(30) = NULL OUTPUT,
    @version_date datetime = NULL OUTPUT,
    @debug bit = 0,
    @help bit = 0
)
AS
BEGIN
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
    @version = '7.3',
    @version_date = '20260301';

IF @help = 1
BEGIN
    /*Warnings, I guess*/
    SELECT [WARNING WARNING WARNING] =
        N'warning! achtung! peligro! chardonnay!' UNION ALL
    SELECT N'misuse of this procedure can harm performance' UNION ALL
    SELECT N'be very careful about introducing observer overhead, especially when gathering query plans' UNION ALL
    SELECT N'be even more careful when setting up permanent sessions!' UNION ALL
    SELECT N'for additional support: https://code.erikdarling.com' UNION ALL
    SELECT N'from your loving sql server consultant, erik darling: https://erikdarling.com';


    /*Introduction*/
    SELECT
        introduction = N'allow me to reintroduce myself' UNION ALL
    SELECT N'this can be used to start a time-limited extended event session to capture various things:' UNION ALL
    SELECT N'  * blocking' UNION ALL
    SELECT N'  * query performance and plans' UNION ALL
    SELECT N'  * compilations' UNION ALL
    SELECT N'  * recompilations' UNION ALL
    SELECT N'  * wait stats';


    /*Limitations*/
    SELECT
        limitations = N'frigid shortcomings' UNION ALL
    SELECT N'you need to be on at least SQL Server 2012 SP4 or higher to run this' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'if your version isn''t patched to where query_hash_signed is an available xe action, this won''t run' UNION ALL
    SELECT N'sp_HumanEvents is designed to make getting information from common extended events easier. with that in mind,' UNION ALL
    SELECT N'some of the customization is limited, and right now you can''t just choose your own adventure.' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'because i don''t want to create files, i''m using the ring buffer, which also has some pesky limitations.' UNION ALL
    SELECT N'https://techcommunity.microsoft.com/t5/sql-server-support/you-may-not-see-the-data-you-expect-in-extended-event-ring/ba-p/315838' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'in order to use the "blocking" session, you must enable the blocked process report' UNION ALL
    SELECT N'https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/blocked-process-threshold-server-configuration-option';


    /*Usage*/
    SELECT
        parameter =
            ap.name,
        t.name,
        description =
            CASE ap.name
                WHEN N'@event_type' THEN N'used to pick which session you want to run'
                WHEN N'@query_duration_ms' THEN N'(>=) used to set a minimum query duration to collect data for'
                WHEN N'@query_sort_order' THEN 'when you use the "query" event, lets you choose which metrics to sort results by'
                WHEN N'@skip_plans' THEN 'when you use the "query" event, lets you skip collecting actual execution plans'
                WHEN N'@blocking_duration_ms' THEN N'(>=) used to set a minimum blocking duration to collect data for'
                WHEN N'@wait_type' THEN N'(inclusive) filter to only specific wait types'
                WHEN N'@wait_duration_ms' THEN N'(>=) used to set a minimum time per wait to collect data for'
                WHEN N'@client_app_name' THEN N'(inclusive) filter to only specific app names'
                WHEN N'@client_hostname' THEN N'(inclusive) filter to only specific host names'
                WHEN N'@database_name' THEN N'(inclusive) filter to only specific databases'
                WHEN N'@session_id' THEN N'(inclusive) filter to only a specific session id, or a sample of session ids'
                WHEN N'@sample_divisor' THEN N'the divisor for session ids when sampling a workload, e.g. SPID % 5'
                WHEN N'@username' THEN N'(inclusive) filter to only a specific user'
                WHEN N'@object_name' THEN N'(inclusive) to only filter to a specific object name'
                WHEN N'@object_schema' THEN N'(inclusive) the schema of the object you want to filter to; only needed with blocking events'
                WHEN N'@requested_memory_mb' THEN N'(>=) the memory grant a query must ask for to have data collected'
                WHEN N'@seconds_sample' THEN N'the duration in seconds to run the event session for'
                WHEN N'@gimme_danger' THEN N'used to override default duration minimums for wait events, including zero-duration waits. only use if you''re okay with potentially adding a lot of observer overhead on your system, or for testing purposes.'
                WHEN N'@debug' THEN N'use to print out dynamic SQL'
                WHEN N'@keep_alive' THEN N'creates a permanent session, either to watch live or log to a table from'
                WHEN N'@custom_name' THEN N'if you want to custom name a permanent session'
                WHEN N'@output_database_name' THEN N'the database you want to log data to'
                WHEN N'@output_schema_name' THEN N'the schema you want to log data to'
                WHEN N'@delete_retention_days' THEN N'how many days of logged data you want to keep'
                WHEN N'@cleanup' THEN N'deletes all sessions, tables, and views. requires output database and schema.'
                WHEN N'@max_memory_kb' THEN N'set a max ring buffer size to log data to'
                WHEN N'@target_output' THEN N'choose between ring_buffer or event_file (not available for Azure SQL DB or Managed Instance)'
                WHEN N'@help' THEN N'well you''re here so you figured this one out'
                WHEN N'@version' THEN N'to make sure you have the most recent bits'
                WHEN N'@version_date' THEN N'to make sure you have the most recent bits'
                ELSE N'????'
            END,
        valid_inputs =
           CASE ap.name
               WHEN N'@event_type' THEN N'"blocking", "query", "waits", "recompiles", "compiles" and certain variations on those words'
               WHEN N'@query_duration_ms' THEN N'an integer'
               WHEN N'@query_sort_order' THEN '"cpu", "reads", "writes", "duration", "memory", "spills", and you can add "avg" to sort by averages, e.g. "avg cpu"'
               WHEN N'@skip_plans' THEN '1 or 0'
               WHEN N'@blocking_duration_ms' THEN N'an integer'
               WHEN N'@wait_type' THEN N'a single wait type, or a CSV list of wait types'
               WHEN N'@wait_duration_ms' THEN N'an integer'
               WHEN N'@client_app_name' THEN N'a stringy thing'
               WHEN N'@client_hostname' THEN N'a stringy thing'
               WHEN N'@database_name' THEN N'a stringy thing'
               WHEN N'@session_id' THEN N'an integer, or "sample" to sample a workload'
               WHEN N'@sample_divisor' THEN N'an integer'
               WHEN N'@username' THEN N'a stringy thing'
               WHEN N'@object_name' THEN N'a stringy thing'
               WHEN N'@object_schema' THEN N'a stringy thing'
               WHEN N'@requested_memory_mb' THEN N'an integer'
               WHEN N'@seconds_sample' THEN N'a tiny integer'
               WHEN N'@gimme_danger' THEN N'1 or 0'
               WHEN N'@debug' THEN N'1 or 0'
               WHEN N'@keep_alive' THEN N'1 or 0'
               WHEN N'@custom_name' THEN N'a stringy thing'
               WHEN N'@output_database_name' THEN N'a valid database name'
               WHEN N'@output_schema_name' THEN N'a valid schema'
               WHEN N'@delete_retention_days' THEN N'a POSITIVE integer'
               WHEN N'@cleanup' THEN N'1 or 0'
               WHEN N'@max_memory_kb' THEN N'an integer'
               WHEN N'@target_output' THEN N'ring_buffer, event_file'
               WHEN N'@help' THEN N'1 or 0'
               WHEN N'@version' THEN N'none, output'
               WHEN N'@version_date' THEN N'none, output'
               ELSE N'????'
           END,
        defaults =
           CASE ap.name
               WHEN N'@event_type' THEN N'"query"'
               WHEN N'@query_duration_ms' THEN N'500 (ms)'
               WHEN N'@query_sort_order' THEN N'"cpu"'
               WHEN N'@skip_plans' THEN '0'
               WHEN N'@blocking_duration_ms' THEN N'500 (ms)'
               WHEN N'@wait_type' THEN N'"all", which uses a list of "interesting" waits'
               WHEN N'@wait_duration_ms' THEN N'10 (ms)'
               WHEN N'@client_app_name' THEN N'intentionally left blank'
               WHEN N'@client_hostname' THEN N'intentionally left blank'
               WHEN N'@database_name' THEN N'intentionally left blank'
               WHEN N'@session_id' THEN N'intentionally left blank'
               WHEN N'@sample_divisor' THEN N'5'
               WHEN N'@username' THEN N'intentionally left blank'
               WHEN N'@object_name' THEN N'intentionally left blank'
               WHEN N'@object_schema' THEN N'dbo'
               WHEN N'@requested_memory_mb' THEN N'0'
               WHEN N'@seconds_sample' THEN N'10'
               WHEN N'@gimme_danger' THEN N'0'
               WHEN N'@keep_alive' THEN N'0'
               WHEN N'@custom_name' THEN N'intentionally left blank'
               WHEN N'@output_database_name' THEN N'intentionally left blank'
               WHEN N'@output_schema_name' THEN N'dbo'
               WHEN N'@delete_retention_days' THEN N'3 (days)'
               WHEN N'@debug' THEN N'0'
               WHEN N'@cleanup' THEN N'0'
               WHEN N'@max_memory_kb' THEN N'102400'
               WHEN N'@target_output' THEN N'ring_buffer'
               WHEN N'@help' THEN N'0'
               WHEN N'@version' THEN N'none, output'
               WHEN N'@version_date' THEN N'none, output'
               ELSE N'????'
           END
    FROM sys.all_parameters AS ap
    JOIN sys.all_objects AS o
      ON ap.object_id = o.object_id
    JOIN sys.types AS t
      ON  ap.system_type_id = t.system_type_id
      AND ap.user_type_id = t.user_type_id
    WHERE o.name = N'sp_HumanEvents';


    /*Example calls*/
    SELECT
        example_calls = N'EXAMPLE CALLS' UNION ALL
    SELECT N'note that not all filters are compatible with all sessions' UNION ALL
    SELECT N'this is handled dynamically, but please don''t think you''re crazy if one "doesn''t work"' UNION ALL
    SELECT N'to capture all types of "completed" queries that have run for at least one second for 20 seconds from a specific database' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''query'', @query_duration_ms = 1000, @seconds_sample = 20, @database_name = ''YourMom'';' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'or that have asked for 1gb of memory' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''query'', @query_duration_ms = 1000, @seconds_sample = 20, @requested_memory_mb = 1024;' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'maybe you want to find unparameterized queries from a poorly written app' UNION ALL
    SELECT N'newer versions will use sql_statement_post_compile, older versions will use uncached_sql_batch_statistics and sql_statement_recompile' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''compilations'', @client_app_name = N''GL00SNIFЯ'', @session_id = ''sample'', @sample_divisor = 3;' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'perhaps you think queries recompiling are the cause of your problems!' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''recompilations'', @seconds_sample = 30;' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'look, blocking is annoying. just turn on RCSI, you goblin.' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''blocking'', @seconds_sample = 60, @blocking_duration_ms = 5000;' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'i mean wait stats are probably a meme but whatever' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''waits'', @wait_duration_ms = 10, @seconds_sample = 100, @wait_type = N''all'';' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'note that THREADPOOL is SOS_WORKER in xe-land. why? i dunno.' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE dbo.sp_HumanEvents @event_type = ''waits'', @wait_duration_ms = 10, @seconds_sample = 100, @wait_type = N''SOS_WORKER,RESOURCE_SEMAPHORE,YOUR_MOM'';' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'to set up a permanent session for compiles, but you can specify any of the session types here' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE sp_HumanEvents @event_type = N''compiles'', @debug = 1, @keep_alive = 1;' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'to log to a database named whatever, and a schema called dbo' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'EXECUTE sp_HumanEvents @debug = 1, @output_database_name = N''whatever'', @output_schema_name = N''dbo'';' UNION ALL
    SELECT REPLICATE(N'-', 100);


    /*Views*/
    SELECT
        views_and_stuff = N'views that get created when you log to tables' UNION ALL
    SELECT N'these will get created in the same database that your output tables get created in for simplicity' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_Queries: View to look at data pulled from logged queries' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_WaitsByQueryAndDatabase: waits generated grouped by query and database. this is best effort, as the query grouping relies on them being present in the plan cache' UNION ALL
    SELECT N'HumanEvents_WaitsByDatabase: waits generated grouped by database' UNION ALL
    SELECT N'HumanEvents_WaitsTotal: total waits' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_Blocking: view to assemble blocking chains' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_CompilesByDatabaseAndObject: compiles by database and object' UNION ALL
    SELECT N'HumanEvents_CompilesByQuery: compiles by query' UNION ALL
    SELECT N'HumanEvents_CompilesByDuration: compiles by duration length' UNION ALL
    SELECT N'HumanEvents_Compiles_Legacy: compiles on older versions that don''t support new events' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_Parameterization: data collected from the parameterization event' UNION ALL
    SELECT REPLICATE(N'-', 100) UNION ALL
    SELECT N'HumanEvents_RecompilesByDatabaseAndObject: recompiles by database and object' UNION ALL
    SELECT N'HumanEvents_RecompilesByQuery: recompiles by query' UNION ALL
    SELECT N'HumanEvents_RecompilesByDuration: recompiles by long duration' UNION ALL
    SELECT N'HumanEvents_Recompiles_Legacy: recompiles on older versions that don''t support new events' UNION ALL
    SELECT REPLICATE(N'-', 100);


    /*License to F5*/
    SELECT
        mit_license_yo = N'i am MIT licensed, so like, do whatever' UNION ALL
    SELECT N'see printed messages for full license';
    RAISERROR(N'
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;
    RETURN;
END;

BEGIN TRY
CREATE TABLE
    #x
(
    x xml
);

CREATE TABLE
    #drop_commands
(
    id integer IDENTITY PRIMARY KEY,
    drop_command nvarchar(1000)
);

CREATE TABLE
    #user_waits
(
    wait_type nvarchar(60)
);

CREATE TABLE
    #papers_please
(
    ahem sysname
);

CREATE TABLE
    #human_events_xml_internal
(
    human_events_xml xml
);

CREATE TABLE
    #human_events_xml
(
    human_events_xml xml
);

CREATE TABLE
    #wait
(
    wait_type sysname
);

CREATE TABLE
    #human_events_worker
(
    id integer NOT NULL PRIMARY KEY IDENTITY,
    event_type sysname NOT NULL,
    event_type_short sysname NOT NULL,
    is_table_created bit NOT NULL DEFAULT 0,
    is_view_created bit NOT NULL DEFAULT 0,
    last_checked datetime2(7) NOT NULL DEFAULT '19000101',
    last_updated datetime2(7) NOT NULL DEFAULT '19000101',
    output_database sysname NOT NULL,
    output_schema sysname NOT NULL,
    output_table nvarchar(400) NOT NULL
);

CREATE UNIQUE NONCLUSTERED INDEX
    no_dupes
ON #human_events_worker
    (output_table)
WITH
    (IGNORE_DUP_KEY = ON);

CREATE TABLE
    #view_check
(
    id integer PRIMARY KEY IDENTITY,
    view_name sysname NOT NULL,
    view_definition varbinary(max) NOT NULL,
    output_database sysname NOT NULL DEFAULT N'',
    output_schema sysname NOT NULL DEFAULT N'',
    output_table sysname NOT NULL DEFAULT N'',
    view_converted AS
        CONVERT
        (
            nvarchar(max),
            view_definition
        ),
    view_converted_length AS
        DATALENGTH
        (
            CONVERT
            (
                nvarchar(max),
                view_definition
            )
        )
);


/*
I mean really stop it with the unsupported versions
*/
DECLARE
    @v decimal(5,0) =
        PARSENAME
        (
            CONVERT
            (
                nvarchar(128),
                SERVERPROPERTY('ProductVersion')
            ),
            4
        ),
    @mv integer =
        PARSENAME
        (
            CONVERT
            (
                nvarchar(128),
                SERVERPROPERTY('ProductVersion')
            ),
            2
        ),
    @azure bit =
        CASE
            WHEN CONVERT
                 (
                     integer,
                     SERVERPROPERTY('EngineEdition')
                 ) = 5
            THEN 1
            ELSE 0
        END,
    @drop_old_sql nvarchar(1000) = N'',
    @waitfor nvarchar(20) = N'',
    @session_name nvarchar(512) = N'',
    @session_with nvarchar(max) = N'',
    @session_sql nvarchar(max) = N'',
    @start_sql nvarchar(max) = N'',
    @stop_sql  nvarchar(max) = N'',
    @drop_sql  nvarchar(max) = N'',
    @session_filter nvarchar(max) = N'',
    @session_filter_query_plans nvarchar(max) = N'',
    @session_filter_waits nvarchar(max) = N'',
    @session_filter_recompile nvarchar(max)= N'',
    @session_filter_statement_completed nvarchar(max) = N'',
    @session_filter_blocking nvarchar(max) = N'',
    @session_filter_parameterization nvarchar(max) = N'',
    @query_duration_filter nvarchar(max) = N'',
    @blocking_duration_ms_filter nvarchar(max) = N'',
    @wait_type_filter nvarchar(max) = N'',
    @wait_duration_filter nvarchar(max) = N'',
    @client_app_name_filter nvarchar(max) = N'',
    @client_hostname_filter nvarchar(max) = N'',
    @database_name_filter nvarchar(max) = N'',
    @session_id_filter nvarchar(max) = N'',
    @username_filter nvarchar(max) = N'',
    @object_name_filter nvarchar(max) = N'',
    @requested_memory_mb_filter nvarchar(max) = N'',
    @compile_events bit = 0,
    @parameterization_events bit = 0,
    @fully_formed_babby nvarchar(1000) = N'',
    @s_out integer,
    @s_sql nvarchar(max) = N'',
    @s_params nvarchar(max) = N'',
    @object_id sysname = N'',
    @requested_memory_kb nvarchar(11) = N'',
    @the_sleeper_must_awaken nvarchar(max) = N'',
    @min_id integer,
    @max_id integer,
    @event_type_check sysname,
    @object_name_check nvarchar(1000) = N'',
    @table_sql nvarchar(max) = N'',
    @view_tracker bit,
    @spe nvarchar(max) = N'.sys.sp_executesql ',
    @view_sql nvarchar(max) = N'',
    @view_database sysname = N'',
    @date_filter datetime2(7),
    @Time time,
    @delete_tracker integer,
    @the_deleter_must_awaken nvarchar(max) = N'',
    @executer nvarchar(max),
    @cleanup_sessions nvarchar(max) = N'',
    @cleanup_tables nvarchar(max) = N'',
    @drop_holder nvarchar(max) = N'',
    @cleanup_views nvarchar(max) = N'',
    @nc10 nvarchar(2) = NCHAR(10),
    @inputbuf_bom nvarchar(1) = CONVERT(nvarchar(1), 0x0a00, 0);

/*check to make sure we're on a usable version*/
IF
(
    @v < 11
    OR
    (
          @v = 11
      AND @mv < 7001
    )
)
    BEGIN
        RAISERROR(N'This darn thing doesn''t seem to work on versions older than 2012 SP4.', 11, 1) WITH NOWAIT;
        RETURN;
    END;

/*one more check here for old versions. loiterers should arrested.*/
IF NOT EXISTS
(
    SELECT
        1/0
    FROM sys.dm_xe_packages AS xp
    JOIN sys.dm_xe_objects AS xo
      ON xp.guid = xo.package_guid
    WHERE (xo.capabilities IS NULL
             OR xo.capabilities & 1 = 0)
    AND   (xp.capabilities IS NULL
             OR xp.capabilities & 1 = 0)
    AND   xo.object_type = N'action'
    AND   xo.name = N'query_hash_signed'
)
    BEGIN
        RAISERROR(N'This server hasn''t been patched up to a supported version that has the query_hash_signed action.', 11, 1) WITH NOWAIT;
        RETURN;
    END;

/*clean up any old/dormant sessions*/
IF @azure = 0
BEGIN
    INSERT
        #drop_commands WITH(TABLOCK)
    (
        drop_command
    )
    SELECT
        N'DROP EVENT SESSION ' +
        ses.name +
        N' ON SERVER;'
    FROM sys.server_event_sessions AS ses
    LEFT JOIN sys.dm_xe_sessions AS dxe
      ON dxe.name = ses.name
    WHERE ses.name LIKE N'HumanEvents%'
    AND  (dxe.create_time < DATEADD(MINUTE, -1, SYSDATETIME())
    OR    dxe.create_time IS NULL);
END;

IF @azure = 1
BEGIN
    INSERT
        #drop_commands WITH(TABLOCK)
    (
        drop_command
    )
    SELECT
        N'DROP EVENT SESSION ' +
        ses.name +
        N' ON DATABASE;'
    FROM sys.database_event_sessions AS ses
    LEFT JOIN sys.dm_xe_database_sessions AS dxe
      ON dxe.name = ses.name
    WHERE ses.name LIKE N'HumanEvents%'
    AND  (dxe.create_time < DATEADD(MINUTE, -1, SYSDATETIME())
    OR    dxe.create_time IS NULL);
END;

IF EXISTS
(
    SELECT
        1/0
    FROM #drop_commands AS dc
)
BEGIN
    RAISERROR(N'Found old sessions, dropping those.', 0, 1) WITH NOWAIT;

    DECLARE
        @drop_cursor CURSOR;

    SET
        @drop_cursor =
        CURSOR
        LOCAL
        SCROLL
        DYNAMIC
        READ_ONLY
    FOR
    SELECT
        drop_command
    FROM #drop_commands;

    OPEN @drop_cursor;

    FETCH NEXT
    FROM @drop_cursor
    INTO @drop_old_sql;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT @drop_old_sql;
        EXECUTE (@drop_old_sql);

        FETCH NEXT
        FROM @drop_cursor
        INTO @drop_old_sql;
    END;
END;

IF @debug = 1 BEGIN RAISERROR(N'Setting up some variables', 0, 1) WITH NOWAIT; END;

/* Give sessions super unique names in case more than one person uses it at a time */
IF @keep_alive = 0
BEGIN
    SET @session_name +=
            REPLACE
            (
                N'HumanEvents_' +
                @event_type +
                N'_' +
                CONVERT
                (
                    nvarchar(36),
                    NEWID()
                ),
                N'-',
                N''
            );
END;

IF @keep_alive = 1
BEGIN
    SET @session_name +=
            N'keeper_HumanEvents_'  +
            @event_type +
            CASE
                WHEN @custom_name <> N''
                THEN N'_' + @custom_name
                ELSE N''
            END;
END;


/* set a lower max memory setting for azure */
IF @azure = 1
BEGIN
    SELECT TOP (1)
        @max_memory_kb =
            CONVERT
            (
                bigint,
                (rg.max_memory * .10) * 1024
            )
    FROM sys.dm_user_db_resource_governance AS rg
    WHERE UPPER(rg.database_name) = UPPER(@database_name)
    OR    @database_name = ''
    ORDER BY
        max_memory DESC;

    IF @debug = 1 BEGIN RAISERROR(N'Setting lower max memory for ringbuffer due to Azure, setting to %I64d kb',  0, 1, @max_memory_kb) WITH NOWAIT; END;
END;

/* session create options */
SET @session_with = N'
ADD TARGET ' +
    CASE
        WHEN LOWER(@target_output) = N'ring_buffer'
        THEN N'package0.ring_buffer
        ( SET max_memory = ' + RTRIM(@max_memory_kb) + N' )'
        WHEN LOWER(@target_output) = N'event_file'
        THEN N'package0.event_file
        ( SET filename = N''' + @session_name + N'.xel'', max_file_size = 1024, max_rollover_files = 5 )'
    END + N'
WITH
        (
            MAX_MEMORY = ' + RTRIM(@max_memory_kb) + N'KB,
            EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
            MAX_DISPATCH_LATENCY = 5 SECONDS,
            MAX_EVENT_SIZE = 0KB,
            MEMORY_PARTITION_MODE = PER_CPU,
            TRACK_CAUSALITY = OFF,
            STARTUP_STATE = OFF
        );' + @nc10;

/* azure can't create on server, just database */
SET @session_sql =
    N'
CREATE EVENT SESSION ' +
@session_name +
    CASE
        WHEN @azure = 0
        THEN N'
ON SERVER '
        ELSE N'
ON DATABASE '
    END;

/* STOP. DROP. SHUT'EM DOWN OPEN UP SHOP. */
SET @start_sql =
    N'ALTER EVENT SESSION ' +
    @session_name +
N' ON ' +
    CASE
        WHEN @azure = 1
        THEN 'DATABASE'
        ELSE 'SERVER'
    END +
' STATE = START;' +
@nc10;

SET @stop_sql  =
    N'ALTER EVENT SESSION ' +
    @session_name +
N' ON ' +
    CASE
        WHEN @azure = 1
        THEN N'DATABASE'
        ELSE N'SERVER'
    END +
N' STATE = STOP;' +
@nc10;

SET @drop_sql  =
    N'DROP EVENT SESSION '  +
    @session_name +
N' ON ' +
    CASE
        WHEN @azure = 1
        THEN N'DATABASE'
        ELSE N'SERVER'
    END +
N';' +
@nc10;


/*Some sessions can use all general filters*/
SET @session_filter = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;
/*query plans can filter on requested memory, too, along with the limited filters*/
SET @session_filter_query_plans = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;
/*only wait stats can filter on wait types, but can filter on everything else*/
SET @session_filter_waits = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;
/*only wait stats can filter on wait types, but can filter on everything else*/
SET @session_filter_recompile = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;
/*sql_statement_completed can do everything except object name*/
SET @session_filter_statement_completed = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;
/*for blocking because blah blah*/
SET @session_filter_blocking = @nc10 + N'         sqlserver.is_system = 1 ' + @nc10;
/*for parameterization because blah blah*/
SET @session_filter_parameterization = @nc10 + N'            sqlserver.is_system = 0 ' + @nc10;


IF @debug = 1 BEGIN RAISERROR(N'Checking for some event existence', 0, 1) WITH NOWAIT; END;
/* Determines if we use the new event or the old event(s) to track compiles */
IF EXISTS
(
    SELECT
        1/0
    FROM sys.dm_xe_objects AS dxo
    WHERE dxo.name = N'sql_statement_post_compile'
)
BEGIN
    SET @compile_events = 1;
END;


/* Or if we use this event at all! */
IF EXISTS
(
    SELECT
        1/0
    FROM sys.dm_xe_objects AS dxo
    WHERE dxo.name = N'query_parameterization_data'
)
BEGIN
    SET @parameterization_events = 1;
END;


/* You know what I don't wanna deal with? NULLs. */
IF @debug = 1 BEGIN RAISERROR(N'Nixing NULLs', 0, 1) WITH NOWAIT; END;
SET @event_type            = ISNULL(@event_type, N'');
SET @client_app_name       = ISNULL(@client_app_name, N'');
SET @client_hostname       = ISNULL(@client_hostname, N'');
SET @database_name         = ISNULL(@database_name, N'');
SET @session_id            = ISNULL(@session_id, N'');
SET @username              = ISNULL(@username, N'');
SET @object_name           = ISNULL(@object_name, N'');
SET @object_schema         = ISNULL(@object_schema, N'');
SET @custom_name           = ISNULL(@custom_name, N'');
SET @output_database_name  = ISNULL(@output_database_name, N'');
SET @output_schema_name    = ISNULL(@output_schema_name, N'');

/*I'm also very forgiving of some white space*/
SET @database_name = RTRIM(LTRIM(@database_name));
SET @query_sort_order = LOWER(@query_sort_order);
SET @event_type = LOWER(@event_type);

/*Assemble the full object name for easier wrangling*/
SET @fully_formed_babby =
        QUOTENAME(@database_name) +
        N'.' +
        QUOTENAME(@object_schema) +
        N'.' +
        QUOTENAME(@object_name);

/*Some sanity checking*/
IF @debug = 1 BEGIN RAISERROR(N'Sanity checking event types', 0, 1) WITH NOWAIT; END;
/* You can only do this right now. */
IF LOWER(@event_type) NOT IN
        (
            N'waits',
            N'blocking',
            N'locking',
            N'queries',
            N'compiles',
            N'recompiles',
            N'wait',
            N'block',
            N'blocks',
            N'lock',
            N'locks',
            N'query',
            N'compile',
            N'recompile',
            N'compilation',
            N'recompilation',
            N'compilations',
            N'recompilations'
        )
BEGIN
    RAISERROR(N'
You have chosen a value for @event_type... poorly. use @help = 1 to see valid arguments.
What on earth is %s?', 11, 1, @event_type) WITH NOWAIT;
    RETURN;
END;


IF @debug = 1 BEGIN RAISERROR(N'Checking target_output parameter', 0, 1) WITH NOWAIT; END;
IF LOWER(@target_output) NOT IN
(
    N'ring_buffer',
    N'event_file'
)
BEGIN
    RAISERROR(N'You have chosen an invalid value for @target_output: %s. Valid options are ring_buffer or event_file.', 11, 1, @target_output) WITH NOWAIT;
    RETURN;
END;

IF LOWER(@target_output) = N'event_file'
AND @azure = 1
BEGIN
    RAISERROR(N'The event_file target is not supported for Azure SQL Database or Azure SQL Managed Instance because additional setup is required that this procedure cannot complete. Please use ring_buffer instead.', 11, 1) WITH NOWAIT;
    RETURN;
END;


IF @debug = 1 BEGIN RAISERROR(N'Checking query sort order', 0, 1) WITH NOWAIT; END;
IF @query_sort_order NOT IN
(
    N'cpu',
    N'reads',
    N'writes',
    N'duration',
    N'memory',
    N'spills',
    N'avg cpu',
    N'avg reads',
    N'avg writes',
    N'avg duration',
    N'avg memory',
    N'avg spills'
)
BEGIN
   RAISERROR(N'That sort order (%s) you chose is so out of this world that i''m ignoring it', 0, 1, @query_sort_order) WITH NOWAIT;
   SET @query_sort_order = N'avg cpu';
END;


IF @debug = 1 BEGIN RAISERROR(N'Parsing any supplied waits', 0, 1) WITH NOWAIT; END;
SET @wait_type = UPPER(@wait_type);

/* There's no THREADPOOL in XE map values, it gets registered as SOS_WORKER */
SET @wait_type =
        REPLACE
        (
            @wait_type,
            N'THREADPOOL',
            N'SOS_WORKER'
        );

/* This will hold the CSV list of wait types someone passes in */

INSERT
    #user_waits WITH(TABLOCK)
SELECT
    wait_type =
        LTRIM
        (
            RTRIM
            (
                waits.wait_type
            )
        )
FROM
(
    SELECT
        wait_type =
            x.x.value
                (
                    '(./text())[1]',
                    'nvarchar(60)'
                )
    FROM
    (
      SELECT
          wait_type =
              CONVERT
              (
                  xml,
                  N'<x>' +
                  REPLACE
                  (
                      REPLACE
                      (
                          @wait_type,
                          N',',
                          N'</x><x>'
                      ),
                      N' ',
                      N''
                  ) +
                  N'</x>'
              ).query(N'.')
    ) AS w
    CROSS APPLY wait_type.nodes(N'x') AS x(x)
) AS waits
WHERE @wait_type <> N'ALL';


/*
If someone is passing in specific waits, let's make sure that
they're valid waits by checking them against what's available.
*/
IF @wait_type <> N'ALL'
BEGIN
    IF @debug = 1 BEGIN RAISERROR(N'Checking wait validity', 0, 1) WITH NOWAIT; END;

    SELECT DISTINCT
        invalid_waits =
             uw.wait_type
    INTO #invalid_waits
    FROM #user_waits AS uw
    WHERE NOT EXISTS
          (
              SELECT
                  1/0
              FROM sys.dm_xe_map_values AS dxmv
              WHERE dxmv.map_value COLLATE Latin1_General_BIN2 = uw.wait_type COLLATE Latin1_General_BIN2
              AND   dxmv.name = N'wait_types'
          );

    /* If we find any invalid waits, let people know */
    IF ROWCOUNT_BIG() > 0
    BEGIN
        SELECT
            invalid_waits =
                N'You have chosen some invalid wait types'

        UNION ALL

        SELECT
            iw.invalid_waits
        FROM #invalid_waits AS iw;

        RAISERROR(N'Waidaminnithataintawait', 11, 1) WITH NOWAIT;
        RETURN;
    END;
END;


/* I just don't want anyone to be disappointed */
IF @debug = 1 BEGIN RAISERROR(N'Avoiding disappointment', 0, 1) WITH NOWAIT; END;
IF
(
        @wait_type <> N''
    AND @wait_type <> N'ALL'
    AND LOWER(@event_type) NOT LIKE N'%wait%'
)
BEGIN
    RAISERROR(N'You can''t filter on wait stats unless you use the wait stats event.', 11, 1) WITH NOWAIT;
    RETURN;
END;


/* This is probably important, huh? */
IF @debug = 1 BEGIN RAISERROR(N'Are we trying to filter for a blocking session?', 0, 1) WITH NOWAIT; END;

/* blocking events need a database name to resolve objects */
IF
(
        LOWER(@event_type) LIKE N'%lock%'
    AND DB_ID(@database_name) IS NULL
    AND @object_name <> N''
)
BEGIN
    RAISERROR(N'The blocking event can only filter on an object_id, and we need a valid @database_name to resolve it correctly.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/* but could we resolve the object name? */
IF
(
        LOWER(@event_type) LIKE N'%lock%'
    AND @object_name <> N''
    AND OBJECT_ID(@fully_formed_babby) IS NULL
)
BEGIN
    RAISERROR(N'We couldn''t find the object you''re trying to find: %s', 11, 1, @fully_formed_babby) WITH NOWAIT;
    RETURN;
END;

/* no blocked process report, no love */
IF @debug = 1 BEGIN RAISERROR(N'Validating if the Blocked Process Report is on, if the session is for blocking', 0, 1) WITH NOWAIT; END;
IF @event_type LIKE N'%lock%'
AND EXISTS
(
    SELECT
        1/0
    FROM sys.configurations AS c
    WHERE c.name = N'blocked process threshold (s)'
    AND   CONVERT(integer, c.value_in_use) = 0
)
BEGIN
        RAISERROR(N'You need to set up the blocked process report in order to use this:
    EXECUTE sys.sp_configure ''show advanced options'', 1;
    EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */
    RECONFIGURE;',
    11, 0) WITH NOWAIT;
    RETURN;
END;

/* validatabase name */
IF @debug = 1 BEGIN RAISERROR(N'If there''s a database filter, is the name valid?', 0, 1) WITH NOWAIT; END;
IF @database_name <> N''
BEGIN
    IF DB_ID(@database_name) IS NULL
    BEGIN
        RAISERROR(N'It looks like you''re looking for a database that doesn''t wanna be looked for (%s); check that spelling!', 11, 1, @database_name) WITH NOWAIT;
        RETURN;
    END;
END;


/* session id has be be "sampled" or a number. */
IF @debug = 1 BEGIN RAISERROR(N'If there''s a session id filter, is it valid?', 0, 1) WITH NOWAIT; END;
IF
(
        LOWER(@session_id) NOT LIKE N'%sample%'
    AND @session_id LIKE N'%[^0-9]%'
    AND LOWER(@session_id) <> N''
)
BEGIN
   RAISERROR(N'That @session_id doesn''t look proper (%s). double check it for me.', 11, 1, @session_id) WITH NOWAIT;
   RETURN;
END;


/* some numbers won't be effective as sample divisors */
IF @debug = 1 BEGIN RAISERROR(N'No dividing by zero', 0, 1) WITH NOWAIT; END;
IF
(
        @sample_divisor < 2
    AND LOWER(@session_id) LIKE N'%sample%'
)
BEGIN
    RAISERROR(N'
@sample_divisor is used to divide @session_id when taking a sample of a workload.
we can''t really divide by zero, and dividing by 1 would be useless.', 11, 1) WITH NOWAIT;
    RETURN;
END;


/* CH-CH-CH-CHECK-IT-OUT */

/* check for existing session with the same name */
IF @debug = 1 BEGIN RAISERROR(N'Make sure the session doesn''t exist already', 0, 1) WITH NOWAIT; END;

IF @azure = 0
BEGIN
    IF EXISTS
    (
        SELECT
            1/0
        FROM sys.server_event_sessions AS ses
        LEFT JOIN sys.dm_xe_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name = @session_name
    )
    BEGIN
        RAISERROR(N'A session with the name %s already exists. dropping.', 0, 1, @session_name) WITH NOWAIT;

        EXECUTE sys.sp_executesql
            @drop_sql;
    END;
END;
ELSE
BEGIN
    IF EXISTS
    (
        SELECT
            1/0
        FROM sys.database_event_sessions AS ses
        LEFT JOIN sys.dm_xe_database_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name = @session_name
    )
    BEGIN
        IF @debug = 1 BEGIN RAISERROR(N'A session with the name %s already exists. dropping.', 0, 1, @session_name) WITH NOWAIT; END;

        EXECUTE sys.sp_executesql
            @drop_sql;
    END;
END;

/* check that the output database exists */
IF @debug = 1 BEGIN RAISERROR(N'Does the output database exist?', 0, 1) WITH NOWAIT; END;
IF @output_database_name <> N''
BEGIN
    IF DB_ID(@output_database_name) IS NULL
    BEGIN
        RAISERROR(N'It looks like you''re looking for a database (%s) that doesn''t wanna be looked for; check that spelling!', 11, 1, @output_database_name) WITH NOWAIT;
        RETURN;
    END;
END;


/* check that the output schema exists */
IF @debug = 1 BEGIN RAISERROR(N'Does the output schema exist?', 0, 1) WITH NOWAIT; END;
IF @output_schema_name NOT IN (N'dbo', N'')
BEGIN
    SELECT
        @s_sql = N'
    SELECT
        @is_out =
            COUNT_BIG(*)
    FROM ' + QUOTENAME(@output_database_name) + N'.sys.schemas AS s
    WHERE s.name = ' + QUOTENAME(@output_schema_name, '''') + N';',
        @s_params  =
            N'@is_out integer OUTPUT';

    EXECUTE sys.sp_executesql
        @s_sql,
        @s_params,
        @is_out = @s_out OUTPUT;

    IF @s_out = 0
    BEGIN
        RAISERROR(N'It looks like the schema %s doesn''t exist in the database %s', 11, 1, @output_schema_name, @output_database_name);
        RETURN;
    END;
END;


/* we need an output schema and database */
IF @debug = 1 BEGIN RAISERROR(N'Is output database OR schema filled in?', 0, 1) WITH NOWAIT; END;
IF
(
     LEN(@output_database_name + @output_schema_name) > 0
    AND  @output_schema_name <> N'dbo'
    AND (@output_database_name  = N''
    OR   @output_schema_name = N'')
)
BEGIN
    IF @output_database_name = N''
        BEGIN
            RAISERROR(N'@output_database_name can''t blank when outputting to tables or cleaning up', 11, 1) WITH NOWAIT;
            RETURN;
        END;

    IF @output_schema_name = N''
        BEGIN
            RAISERROR(N'@output_schema_name can''t blank when outputting to tables or cleaning up', 11, 1) WITH NOWAIT;
            RETURN;
        END;
END;


/* no goofballing in custom names */
IF @debug = 1 BEGIN RAISERROR(N'Is custom name something stupid?', 0, 1) WITH NOWAIT; END;
IF
(
    PATINDEX(N'%[^a-zA-Z0-9]%', @custom_name) > 0
      OR @custom_name LIKE N'[0-9]%'
)
BEGIN
    RAISERROR(N'
Dunno if I like the looks of @custom_name: %s
You can''t use special characters, or leading numbers.', 11, 1, @custom_name) WITH NOWAIT;
    RETURN;
END;


/* I'M LOOKING AT YOU */
IF @debug = 1 BEGIN RAISERROR(N'Someone is going to try it.', 0, 1) WITH NOWAIT; END;
IF @delete_retention_days < 1
BEGIN
    SET @delete_retention_days = CASE WHEN @delete_retention_days < 0 THEN @delete_retention_days * -1 ELSE 1 END;
    IF @debug = 1 BEGIN RAISERROR(N'Stay positive', 0, 1) WITH NOWAIT; END;
END;

/*
We need to do some seconds math here, because WAITFOR is very stupid
*/
IF @debug = 1 BEGIN RAISERROR(N'Wait For It! Wait For it!', 0, 1) WITH NOWAIT; END;
IF @seconds_sample > 0
BEGIN
    SELECT
        @waitfor =
            CONVERT
            (
                nvarchar(20),
                DATEADD
                (
                    SECOND,
                    @seconds_sample,
                    '19000101'
                 ),
                 114
            );
END;


/* Start setting up individual filters */
IF @debug = 1 BEGIN RAISERROR(N'Setting up individual filters', 0, 1) WITH NOWAIT; END;
IF @query_duration_ms > 0
BEGIN
    IF LOWER(@event_type) NOT LIKE N'%comp%' /* compile and recompile durations are tiny */
    BEGIN
        SET @query_duration_filter += N'     AND duration >= ' + CONVERT(nvarchar(20), (@query_duration_ms * 1000)) + @nc10;
    END;
END;

IF @blocking_duration_ms > 0
BEGIN
    SET @blocking_duration_ms_filter += N'     AND duration >= ' + CONVERT(nvarchar(20), (@blocking_duration_ms * 1000)) + @nc10;
END;

IF @wait_duration_ms > 0
BEGIN
    SET @wait_duration_filter += N'     AND duration >= ' + CONVERT(nvarchar(20), (@wait_duration_ms)) + @nc10;
END;

IF @client_app_name <> N''
BEGIN
    SET @client_app_name_filter += N'     AND sqlserver.client_app_name = N' + QUOTENAME(@client_app_name, N'''') + @nc10;
END;

IF @client_hostname <> N''
BEGIN
    SET @client_hostname_filter += N'     AND sqlserver.client_hostname = N' + QUOTENAME(@client_hostname, N'''') + @nc10;
END;

IF @database_name <> N''
BEGIN
    IF LOWER(@event_type) NOT LIKE N'%lock%'
    BEGIN
        SET @database_name_filter += N'     AND sqlserver.database_name = N' + QUOTENAME(@database_name, N'''') + @nc10;
    END;
    IF LOWER(@event_type) LIKE N'%lock%'
    BEGIN
        SET @database_name_filter += N'     AND database_name = N' + QUOTENAME(@database_name, N'''') + @nc10;
    END;
END;

IF @session_id <> N''
BEGIN
    IF LOWER(@session_id) NOT LIKE N'%sample%'
    BEGIN
        SET @session_id_filter += N'     AND sqlserver.session_id = ' + CONVERT(nvarchar(11), @session_id) + @nc10;
    END;
    IF LOWER(@session_id) LIKE N'%sample%'
    BEGIN
        SET @session_id_filter += N'     AND package0.divides_by_uint64(sqlserver.session_id, ' + CONVERT(nvarchar(11), @sample_divisor) + N') ' + @nc10;
    END;
END;

IF @username <> N''
BEGIN
    SET @username_filter += N'     AND sqlserver.username = N' + QUOTENAME(@username, '''') + @nc10;
END;

IF @object_name <> N''
BEGIN
    IF @event_type LIKE N'%lock%'
    BEGIN
        SET @object_id = OBJECT_ID(@fully_formed_babby);
        SET @object_name_filter += N'     AND object_id = ' + @object_id + @nc10;
    END;
    IF @event_type NOT LIKE N'%lock%'
    BEGIN
        SET @object_name_filter += N'     AND object_name = N' + QUOTENAME(@object_name, N'''') + @nc10;
    END;
END;

IF @requested_memory_mb > 0
BEGIN
    SET @requested_memory_kb = @requested_memory_mb * 1024.;
    SET @requested_memory_mb_filter += N'     AND requested_memory_kb >= ' + @requested_memory_kb + @nc10;
END;


/* At this point we'll either put my list of interesting waits in a temp table,
   or a list of user defined waits */
IF LOWER(@event_type) LIKE N'%wait%'
BEGIN
    INSERT
        #wait WITH(TABLOCK)
    (
        wait_type
    )
    SELECT
        x.wait_type
    FROM
    (
        VALUES
            (N'LCK_M_SCH_S'),
            (N'LCK_M_SCH_M'),
            (N'LCK_M_S'),
            (N'LCK_M_U'),
            (N'LCK_M_X'),
            (N'LCK_M_IS'),
            (N'LCK_M_IU'),
            (N'LCK_M_IX'),
            (N'LCK_M_SIU'),
            (N'LCK_M_SIX'),
            (N'LCK_M_UIX'),
            (N'LCK_M_BU'),
            (N'LCK_M_RS_S'),
            (N'LCK_M_RS_U'),
            (N'LCK_M_RIn_NL'),
            (N'LCK_M_RIn_S'),
            (N'LCK_M_RIn_U'),
            (N'LCK_M_RIn_X'),
            (N'LCK_M_RX_S'),
            (N'LCK_M_RX_U'),
            (N'LCK_M_RX_X'),
            (N'LATCH_NL'),
            (N'LATCH_KP'),
            (N'LATCH_SH'),
            (N'LATCH_UP'),
            (N'LATCH_EX'),
            (N'LATCH_DT'),
            (N'PAGELATCH_NL'),
            (N'PAGELATCH_KP'),
            (N'PAGELATCH_SH'),
            (N'PAGELATCH_UP'),
            (N'PAGELATCH_EX'),
            (N'PAGELATCH_DT'),
            (N'PAGEIOLATCH_NL'),
            (N'PAGEIOLATCH_KP'),
            (N'PAGEIOLATCH_SH'),
            (N'PAGEIOLATCH_UP'),
            (N'PAGEIOLATCH_EX'),
            (N'PAGEIOLATCH_DT'),
            (N'IO_COMPLETION'),
            (N'ASYNC_IO_COMPLETION'),
            (N'NETWORK_IO'),
            (N'WRITE_COMPLETION'),
            (N'RESOURCE_SEMAPHORE'),
            (N'RESOURCE_SEMAPHORE_QUERY_COMPILE'),
            (N'RESOURCE_SEMAPHORE_MUTEX'),
            (N'CMEMTHREAD'),
            (N'CXCONSUMER'),
            (N'CXPACKET'),
            (N'EXECSYNC'),
            (N'SOS_WORKER'),
            (N'SOS_SCHEDULER_YIELD'),
            (N'LOGBUFFER'),
            (N'WRITELOG')
    ) AS x (wait_type)
    WHERE @wait_type = N'ALL'

    UNION ALL

    SELECT
        uw.wait_type
    FROM #user_waits AS uw
    WHERE @wait_type <> N'ALL';

    /* This section creates a dynamic WHERE clause based on wait types
       The problem is that wait type IDs change frequently, which sucks. */
    WITH maps AS
    (
        SELECT
            dxmv.map_key,
            dxmv.map_value,
            rn =
                dxmv.map_key -
                ROW_NUMBER()
                OVER
                (
                    ORDER BY
                        dxmv.map_key
                )
        FROM sys.dm_xe_map_values AS dxmv
        WHERE dxmv.name = N'wait_types'
        AND   dxmv.map_value IN
              (
                  SELECT
                      w.wait_type
                  FROM #wait AS w
              )
    ),
         grps AS
    (
        SELECT
            minkey =
                MIN(maps.map_key),
            maxkey =
                MAX(maps.map_key)
            FROM maps AS maps
            GROUP BY
                maps.rn
    )
    SELECT
        @wait_type_filter +=
            SUBSTRING
            (
                (
                    SELECT
                        N'      AND  ((' +
                        STUFF
                        (
                            (
                                SELECT
                                    N'         OR ' +
                                    CASE
                                        WHEN grps.minkey < grps.maxkey
                                        THEN +
                                        N'(wait_type >= ' +
                                        CONVERT
                                        (
                                            nvarchar(11),
                                            grps.minkey
                                        ) +
                                        N' AND wait_type <= ' +
                                        CONVERT
                                        (
                                            nvarchar(11),
                                            grps.maxkey
                                        ) +
                                        N')' +
                                        @nc10
                                        ELSE N'(wait_type = ' +
                                        CONVERT
                                        (
                                            nvarchar(11),
                                            grps.minkey
                                        ) +
                                        N')' +
                                        @nc10
                                    END
                                FROM grps FOR XML PATH(N''), TYPE
                            ).value('./text()[1]', 'nvarchar(max)')
                            ,
                            1,
                            13,
                            N''
                        )
                ),
                0,
                8000
            ) +
            N')';
END;

/* End individual filters */

/* This section sets event-dependent filters */

IF @debug = 1 BEGIN RAISERROR(N'Combining session filters', 0, 1) WITH NOWAIT; END;
/* For full filter-able sessions */
SET @session_filter +=
    (
        ISNULL(@query_duration_filter, N'') +
        ISNULL(@client_app_name_filter, N'') +
        ISNULL(@client_hostname_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'') +
        ISNULL(@object_name_filter, N'')
    );

/* For waits specifically, because they also need to filter on wait type and wait duration */
SET @session_filter_waits +=
    (
        ISNULL(@wait_duration_filter, N'') +
        ISNULL(@wait_type_filter, N'') +
        ISNULL(@client_app_name_filter, N'') +
        ISNULL(@client_hostname_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'')
    );

/* For query plans, which can also filter on memory required */
SET @session_filter_query_plans +=
    (
        ISNULL(@query_duration_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'') +
        ISNULL(@object_name_filter, N'') +
        ISNULL(@requested_memory_mb_filter, N'')
    );

/* Recompile can have almost everything except... duration */
SET @session_filter_recompile +=
    (
        ISNULL(@client_app_name_filter, N'') +
        ISNULL(@client_hostname_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@object_name_filter, N'') +
        ISNULL(@username_filter, N'')
    );

/* Apparently statement completed can't filter on an object name so that's fun */
SET @session_filter_statement_completed +=
    (
        ISNULL(@query_duration_filter, N'') +
        ISNULL(@client_app_name_filter, N'') +
        ISNULL(@client_hostname_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'')
    );

/* Blocking woighoiughuohaeripugbapiouergb */
SET @session_filter_blocking +=
    (
        ISNULL(@blocking_duration_ms_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'') +
        ISNULL(@object_name_filter, N'')
    );

/* The parameterization event is pretty limited in weird ways */
SET @session_filter_parameterization +=
    (
        ISNULL(@client_app_name_filter, N'') +
        ISNULL(@client_hostname_filter, N'') +
        ISNULL(@database_name_filter, N'') +
        ISNULL(@session_id_filter, N'') +
        ISNULL(@username_filter, N'')
    );


/* This section sets up the event session definition */
IF @debug = 1 BEGIN RAISERROR(N'Setting up the event session', 0, 1) WITH NOWAIT; END;
SET @session_sql +=
        CASE
            WHEN LOWER(@event_type) LIKE N'%lock%'
            THEN N'
      ADD EVENT sqlserver.blocked_process_report
        (WHERE ( ' + @session_filter_blocking + N' ))'
            WHEN LOWER(@event_type) LIKE N'%quer%'
            THEN N'
      ADD EVENT sqlserver.module_end
        (SET collect_statement = 1
         ACTION (sqlserver.database_name, sqlserver.sql_text, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter + N' )),
      ADD EVENT sqlserver.rpc_completed
        (SET collect_statement = 1
         ACTION(sqlserver.database_name, sqlserver.sql_text, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter + N' )),
      ADD EVENT sqlserver.sp_statement_completed
        (SET collect_object_name = 1, collect_statement = 1
         ACTION(sqlserver.database_name, sqlserver.sql_text, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter + N' )),
      ADD EVENT sqlserver.sql_statement_completed
        (SET collect_statement = 1
         ACTION(sqlserver.database_name, sqlserver.sql_text, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter_statement_completed + N' ))'
                   + CASE
                         WHEN @skip_plans = 0
                         THEN N',
      ADD EVENT sqlserver.query_post_execution_showplan
        (
         ACTION(sqlserver.database_name, sqlserver.sql_text, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter_query_plans + N' ))'
                         ELSE N''
                     END
            WHEN LOWER(@event_type) LIKE N'%wait%'
            AND  @v > 11
            THEN N'
      ADD EVENT sqlos.wait_completed
        (SET collect_wait_resource = 1
         ACTION (sqlserver.database_name, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter_waits + N' ))'
            WHEN LOWER(@event_type) LIKE N'%wait%'
            AND  @v = 11
            THEN N'
      ADD EVENT sqlos.wait_info
        (
         ACTION (sqlserver.database_name, sqlserver.plan_handle, sqlserver.query_hash_signed, sqlserver.query_plan_hash_signed)
         WHERE ( ' + @session_filter_waits + N' ))'
            WHEN LOWER(@event_type) LIKE N'%recomp%'
            THEN CASE
                     WHEN @compile_events = 1
                     THEN N'
      ADD EVENT sqlserver.sql_statement_post_compile
        (SET collect_object_name = 1, collect_statement = 1
         ACTION(sqlserver.database_name)
         WHERE ( ' + @session_filter + N' ))'
                     ELSE N'
      ADD EVENT sqlserver.sql_statement_recompile
        (SET collect_object_name = 1, collect_statement = 1
         ACTION(sqlserver.database_name)
         WHERE ( ' + @session_filter_recompile + N' ))'
                 END
            WHEN (LOWER(@event_type) LIKE N'%comp%'
            AND   LOWER(@event_type) NOT LIKE N'%re%')
            THEN CASE
                     WHEN @compile_events = 1
                     THEN N'
      ADD EVENT sqlserver.sql_statement_post_compile
        (SET collect_object_name = 1, collect_statement = 1
         ACTION(sqlserver.database_name)
         WHERE ( ' + @session_filter + N' ))'
                     ELSE N'
      ADD EVENT sqlserver.uncached_sql_batch_statistics
        (
         ACTION(sqlserver.database_name)
         WHERE ( ' + @session_filter_recompile + N' )),
      ADD EVENT sqlserver.sql_statement_recompile
        (SET collect_object_name = 1, collect_statement = 1
         ACTION(sqlserver.database_name)
         WHERE ( ' + @session_filter_recompile + N' ))'
                 END
                + CASE
                      WHEN @parameterization_events = 1
                      THEN N',
      ADD EVENT sqlserver.query_parameterization_data
        (
         ACTION (sqlserver.database_name, sqlserver.plan_handle, sqlserver.sql_text)
         WHERE ( ' + @session_filter_parameterization + N' ))'
                      ELSE N''
                 END
            ELSE N'i have no idea what i''m doing.'
        END;
/* End event session definition */


/* This creates the event session */
SET @session_sql += @session_with;

IF @debug = 1 BEGIN RAISERROR(@session_sql, 0, 1) WITH NOWAIT; END;
EXECUTE (@session_sql);

/* This starts the event session */
IF @debug = 1 BEGIN RAISERROR(@start_sql, 0, 1) WITH NOWAIT; END;
EXECUTE (@start_sql);

/* bail out here if we want to keep the session and not log to tables*/
IF  @keep_alive = 1
AND @output_database_name = N''
AND @output_schema_name IN (N'', N'dbo')
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR(N'Session %s created, exiting.', 0, 1, @session_name) WITH NOWAIT;
        RAISERROR(N'To collect data from it, run this proc from an agent job with an output database and schema name', 0, 1) WITH NOWAIT;
        RAISERROR(N'Alternately, you can watch live data stream in by accessing the GUI', 0, 1) WITH NOWAIT;
        RAISERROR(N'Just don''t forget to stop it when you''re done with it!', 0, 1) WITH NOWAIT;
    END;
    RETURN;
END;

/*
If we're writing to a table, we don't want to do anything else
Or anything else after this, really
We want the session to get set up
*/
IF @debug = 1 BEGIN RAISERROR(N'Do we skip to the GOTO and log tables?', 0, 1) WITH NOWAIT; END;
IF
(
        @output_database_name <> N''
    AND @output_schema_name <> N''
    AND @cleanup = 0
)
BEGIN
    IF @debug = 1 BEGIN RAISERROR(N'Skipping all the other stuff and going to data logging', 0, 1) WITH NOWAIT; END;
    GOTO output_results;
    RETURN;
END;


/* just finishing up the second coat now */
IF @debug = 1 BEGIN RAISERROR(N'Do we skip to the GOTO and cleanup?', 0, 1) WITH NOWAIT; END;
IF
(
        @output_database_name <> N''
    AND @output_schema_name <> N''
    AND @cleanup = 1
)
BEGIN
    IF @debug = 1 BEGIN RAISERROR(N'Skipping all the other stuff and going to cleanup', 0, 1) WITH NOWAIT; END;
    GOTO cleanup;
    RETURN;
END;

/* NOW WE WAIT, MR. BOND */
WAITFOR DELAY @waitfor;


/* Dump whatever we got into a temp table */
IF LOWER(@target_output) = N'ring_buffer'
BEGIN
    IF @azure = 0
    BEGIN
        INSERT
            #x WITH(TABLOCK)
        (
            x
        )
        SELECT
            x =
                CONVERT
                (
                    xml,
                    t.target_data
                )
        FROM sys.dm_xe_session_targets AS t
        JOIN sys.dm_xe_sessions AS s
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        AND   t.target_name = N'ring_buffer';
    END;
    ELSE
    BEGIN
        INSERT
            #x WITH(TABLOCK)
        (
            x
        )
        SELECT
            x =
                CONVERT
                (
                    xml,
                    t.target_data
                )
        FROM sys.dm_xe_database_session_targets AS t
        JOIN sys.dm_xe_database_sessions AS s
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        AND   t.target_name = N'ring_buffer';
    END;
END;
ELSE IF LOWER(@target_output) = N'event_file'
BEGIN
    /*
    Read from event file target
    Azure SQL Database and Managed Instance are not supported for event_file
    */
    INSERT
        #x WITH(TABLOCK)
    (
        x
    )
    SELECT
        x =
            CONVERT
            (
                xml,
                f.event_data
            )
    FROM sys.fn_xe_file_target_read_file
    (
        @session_name + N'*.xel',
        NULL,
        NULL,
        NULL
    ) AS f;
END;


/*
Parse XML events based on target output type
ring_buffer wraps events in RingBufferTarget node, event_file does not
*/
IF LOWER(@target_output) = N'ring_buffer'
BEGIN
    INSERT
        #human_events_xml WITH(TABLOCK)
    (
        human_events_xml
    )
    SELECT
        human_events_xml = e.x.query('.')
    FROM #x AS x
    CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x);
END;
ELSE IF LOWER(@target_output) = N'event_file'
BEGIN
    INSERT
        #human_events_xml WITH(TABLOCK)
    (
        human_events_xml
    )
    SELECT
        human_events_xml = e.x.query('.')
    FROM #x AS x
    CROSS APPLY x.x.nodes('/event') AS e(x);
END;


IF @debug = 1
BEGIN
    SELECT N'#human_events_xml' AS table_name, hex.* FROM #human_events_xml AS hex;
END;


/*
This is where magic will happen
*/
IF LOWER(@event_type) LIKE N'%quer%'
BEGIN
    WITH
        queries AS
    (
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            object_name = oa.c.value('(data[@name="object_name"]/value/text())[1]', 'sysname'),
            sql_text = oa.c.value('(action[@name="sql_text"]/value/text())[1]', 'nvarchar(max)'),
            statement = oa.c.value('(data[@name="statement"]/value/text())[1]', 'nvarchar(max)'),
            showplan_xml = CASE WHEN @skip_plans = 0 THEN oa.c.query('(data[@name="showplan_xml"]/value/*)[1]') ELSE N'<skip>Skipped Plans</skip>' END,
            cpu_ms = oa.c.value('(data[@name="cpu_time"]/value/text())[1]', 'bigint') / 1000.,
            logical_reads = (oa.c.value('(data[@name="logical_reads"]/value/text())[1]', 'bigint') * 8) / 1024.,
            physical_reads = (oa.c.value('(data[@name="physical_reads"]/value/text())[1]', 'bigint') * 8) / 1024.,
            duration_ms = oa.c.value('(data[@name="duration"]/value/text())[1]', 'bigint') / 1000.,
            writes = (oa.c.value('(data[@name="writes"]/value/text())[1]', 'bigint') * 8) / 1024.,
            spills_mb = (oa.c.value('(data[@name="spills"]/value/text())[1]', 'bigint') * 8) / 1024.,
            row_count = oa.c.value('(data[@name="row_count"]/value/text())[1]', 'bigint'),
            estimated_rows = oa.c.value('(data[@name="estimated_rows"]/value/text())[1]', 'bigint'),
            dop = oa.c.value('(data[@name="dop"]/value/text())[1]', 'integer'),
            serial_ideal_memory_mb = oa.c.value('(data[@name="serial_ideal_memory_kb"]/value/text())[1]', 'bigint') / 1024.,
            requested_memory_mb = oa.c.value('(data[@name="requested_memory_kb"]/value/text())[1]', 'bigint') / 1024.,
            used_memory_mb = oa.c.value('(data[@name="used_memory_kb"]/value/text())[1]', 'bigint') / 1024.,
            ideal_memory_mb = oa.c.value('(data[@name="ideal_memory_kb"]/value/text())[1]', 'bigint') / 1024.,
            granted_memory_mb = oa.c.value('(data[@name="granted_memory_kb"]/value/text())[1]', 'bigint') / 1024.,
            query_plan_hash_signed =
                CONVERT
                (
                    binary(8),
                    oa.c.value('(action[@name="query_plan_hash_signed"]/value/text())[1]', 'bigint')
                ),
            query_hash_signed =
                CONVERT
                (
                    binary(8),
                    oa.c.value('(action[@name="query_hash_signed"]/value/text())[1]', 'bigint')
                ),
            plan_handle = oa.c.value('xs:hexBinary((action[@name="plan_handle"]/value/text())[1])', 'varbinary(64)')
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        WHERE oa.c.exist('(action[@name="query_hash_signed"]/value[. != 0])') = 1
    )
    SELECT
        q.*
    INTO #queries
    FROM queries AS q;

    IF @debug = 1 BEGIN SELECT N'#queries' AS table_name, q.* FROM #queries AS q; END;

    /* Add attribute StatementId to query plan if it is missing (versions before 2019) */
    IF @skip_plans = 0
    BEGIN
        WITH
            XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
        UPDATE
            q1
        SET
            showplan_xml.modify('insert attribute StatementId {"1"}
                                 into (/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple)[1]')
        FROM #queries AS q1
        CROSS APPLY
        (
            SELECT TOP (1)
                statement_text = q2.statement
            FROM #queries AS q2
            WHERE q1.query_hash_signed = q2.query_hash_signed
            AND   q1.query_plan_hash_signed = q2.query_plan_hash_signed
            AND   q2.statement IS NOT NULL
            ORDER BY
                q2.event_time DESC
        ) AS q2
        WHERE q1.showplan_xml IS NOT NULL
        AND   q1.showplan_xml.exist('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/@StatementId') = 0;

        /* Add attribute StatementText to query plan if it is missing (all versions) */
        WITH
            XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
        UPDATE
            q1
        SET showplan_xml.modify('insert attribute StatementText {sql:column("q2.statement_text")}
                                 into (/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple)[1]')
        FROM #queries AS q1
        CROSS APPLY
        (
            SELECT TOP (1)
                statement_text = q2.statement
            FROM #queries AS q2
            WHERE q1.query_hash_signed = q2.query_hash_signed
            AND   q1.query_plan_hash_signed = q2.query_plan_hash_signed
            AND   q2.statement IS NOT NULL
            ORDER BY
                q2.event_time DESC
        ) AS q2
        WHERE q1.showplan_xml IS NOT NULL
        AND   q1.showplan_xml.exist('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/@StatementText') = 0;
    END;

    WITH
        query_agg AS
    (
       SELECT
           q.query_plan_hash_signed,
           q.query_hash_signed,
           plan_handle = q.plan_handle,
           /*totals*/
           total_cpu_ms = ISNULL(q.cpu_ms, 0.),
           total_logical_reads = ISNULL(q.logical_reads, 0.),
           total_physical_reads = ISNULL(q.physical_reads, 0.),
           total_duration_ms = ISNULL(q.duration_ms, 0.),
           total_writes = ISNULL(q.writes, 0.),
           total_spills_mb = ISNULL(q.spills_mb, 0.),
           total_used_memory_mb = NULL,
           total_granted_memory_mb = NULL,
           total_rows = ISNULL(q.row_count, 0.),
           /*averages*/
           avg_cpu_ms = ISNULL(q.cpu_ms, 0.),
           avg_logical_reads = ISNULL(q.logical_reads, 0.),
           avg_physical_reads = ISNULL(q.physical_reads, 0.),
           avg_duration_ms = ISNULL(q.duration_ms, 0.),
           avg_writes = ISNULL(q.writes, 0.),
           avg_spills_mb = ISNULL(q.spills_mb, 0.),
           avg_used_memory_mb = NULL,
           avg_granted_memory_mb = NULL,
           avg_rows = ISNULL(q.row_count, 0)
       FROM #queries AS q
       WHERE q.event_type <> N'query_post_execution_showplan'

       UNION ALL

       SELECT
           q.query_plan_hash_signed,
           q.query_hash_signed,
           q.plan_handle,
           /*totals*/
           total_cpu_ms = NULL,
           total_logical_reads = NULL,
           total_physical_reads = NULL,
           total_duration_ms = NULL,
           total_writes = NULL,
           total_spills_mb = NULL,
           total_used_memory_mb = ISNULL(q.used_memory_mb, 0.),
           total_granted_memory_mb = ISNULL(q.granted_memory_mb, 0.),
           total_rows = NULL,
           /*averages*/
           avg_cpu_ms = NULL,
           avg_logical_reads = NULL,
           avg_physical_reads = NULL,
           avg_duration_ms = NULL,
           avg_writes = NULL,
           avg_spills_mb = NULL,
           avg_used_memory_mb = ISNULL(q.used_memory_mb, 0.),
           avg_granted_memory_mb = ISNULL(q.granted_memory_mb, 0.),
           avg_rows = NULL
       FROM #queries AS q
       WHERE q.event_type = N'query_post_execution_showplan'
    )
    SELECT
        qa.query_plan_hash_signed,
        qa.query_hash_signed,
        plan_handle = MAX(qa.plan_handle),
        total_cpu_ms = SUM(qa.total_cpu_ms),
        total_logical_reads_mb = SUM(qa.total_logical_reads),
        total_physical_reads_mb = SUM(qa.total_physical_reads),
        total_duration_ms = SUM(qa.total_duration_ms),
        total_writes_mb = SUM(qa.total_writes),
        total_spills_mb = SUM(qa.total_spills_mb),
        total_used_memory_mb = SUM(qa.total_used_memory_mb),
        total_granted_memory_mb = SUM(qa.total_granted_memory_mb),
        total_rows = SUM(qa.total_rows),
        avg_cpu_ms = AVG(qa.avg_cpu_ms),
        avg_logical_reads_mb = AVG(qa.avg_logical_reads),
        avg_physical_reads_mb = AVG(qa.avg_physical_reads),
        avg_duration_ms = AVG(qa.avg_duration_ms),
        avg_writes_mb = AVG(qa.avg_writes),
        avg_spills_mb = AVG(qa.avg_spills_mb),
        avg_used_memory_mb = AVG(qa.avg_used_memory_mb),
        avg_granted_memory_mb = AVG(qa.avg_granted_memory_mb),
        avg_rows = AVG(qa.avg_rows),
        executions = COUNT_BIG(qa.plan_handle)
    INTO #totals
    FROM query_agg AS qa
    GROUP BY
        qa.query_plan_hash_signed,
        qa.query_hash_signed;

    IF @debug = 1 BEGIN SELECT N'#totals' AS table_name, * FROM #totals AS t; END;

    WITH
        query_results AS
    (
        SELECT
            q.event_time,
            q.database_name,
            q.object_name,
            q2.statement_text,
            q.sql_text,
            q.showplan_xml,
            t.executions,
            t.total_cpu_ms,
            t.avg_cpu_ms,
            t.total_logical_reads_mb,
            t.avg_logical_reads_mb,
            t.total_physical_reads_mb,
            t.avg_physical_reads_mb,
            t.total_duration_ms,
            t.avg_duration_ms,
            t.total_writes_mb,
            t.avg_writes_mb,
            t.total_spills_mb,
            t.avg_spills_mb,
            t.total_used_memory_mb,
            t.avg_used_memory_mb,
            t.total_granted_memory_mb,
            t.avg_granted_memory_mb,
            t.total_rows,
            t.avg_rows,
            q.serial_ideal_memory_mb,
            q.requested_memory_mb,
            q.ideal_memory_mb,
            q.estimated_rows,
            q.dop,
            q.query_plan_hash_signed,
            q.query_hash_signed,
            q.plan_handle,
            n =
                ROW_NUMBER() OVER
                (
                    PARTITION BY
                        q.query_plan_hash_signed,
                        q.query_hash_signed,
                        q.plan_handle
                    ORDER BY
                        q.query_plan_hash_signed,
                        q.query_hash_signed,
                        q.plan_handle
                )
        FROM #queries AS q
        JOIN #totals AS t
          ON  q.query_hash_signed = t.query_hash_signed
          AND q.query_plan_hash_signed = t.query_plan_hash_signed
          AND (q.plan_handle = t.plan_handle OR @skip_plans = 1)
        CROSS APPLY
        (
            SELECT TOP (1)
                statement_text =
                    q2.statement
            FROM #queries AS q2
            WHERE q.query_hash_signed = q2.query_hash_signed
            AND   q.query_plan_hash_signed = q2.query_plan_hash_signed
            AND   q2.statement IS NOT NULL
            ORDER BY
                q2.event_time DESC
        ) AS q2
        WHERE q.showplan_xml.exist('*') = 1
        OR    @skip_plans = 1
    )
    SELECT
        q.event_time,
        q.database_name,
        q.object_name,
        statement_text =
            (
                SELECT
                    [processing-instruction(statement_text)] =
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            q.statement_text COLLATE Latin1_General_BIN2,
                        NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                        NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                        NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                FOR XML
                    PATH(N''),
                    TYPE
            ),
        sql_text =
            (
                SELECT
                    [processing-instruction(sql_text)] =
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            q.sql_text COLLATE Latin1_General_BIN2,
                        NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                        NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                        NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                FOR XML
                    PATH(N''),
                    TYPE
            ),
        q.showplan_xml,
        q.executions,
        q.total_cpu_ms,
        q.avg_cpu_ms,
        q.total_logical_reads_mb,
        q.avg_logical_reads_mb,
        q.total_physical_reads_mb,
        q.avg_physical_reads_mb,
        q.total_duration_ms,
        q.avg_duration_ms,
        q.total_writes_mb,
        q.avg_writes_mb,
        q.total_spills_mb,
        q.avg_spills_mb,
        q.total_used_memory_mb,
        q.avg_used_memory_mb,
        q.total_granted_memory_mb,
        q.avg_granted_memory_mb,
        q.total_rows,
        q.avg_rows,
        q.serial_ideal_memory_mb,
        q.requested_memory_mb,
        q.ideal_memory_mb,
        q.estimated_rows,
        q.dop,
        q.query_plan_hash_signed,
        q.query_hash_signed,
        q.plan_handle
    FROM query_results AS q
    WHERE q.n = 1
    ORDER BY
         CASE @query_sort_order
              WHEN N'cpu' THEN q.total_cpu_ms
              WHEN N'reads' THEN q.total_logical_reads_mb + q.total_physical_reads_mb
              WHEN N'writes' THEN q.total_writes_mb
              WHEN N'duration' THEN q.total_duration_ms
              WHEN N'spills' THEN q.total_spills_mb
              WHEN N'memory' THEN q.total_granted_memory_mb
              WHEN N'avg cpu' THEN q.avg_cpu_ms
              WHEN N'avg reads' THEN q.avg_logical_reads_mb + q.avg_physical_reads_mb
              WHEN N'avg writes' THEN q.avg_writes_mb
              WHEN N'avg duration' THEN q.avg_duration_ms
              WHEN N'avg spills' THEN q.avg_spills_mb
              WHEN N'avg memory' THEN q.avg_granted_memory_mb
              ELSE N'cpu'
         END DESC
     OPTION(RECOMPILE);
END;


IF LOWER(@event_type) LIKE N'%comp%' AND LOWER(@event_type) NOT LIKE N'%re%'
BEGIN
    IF @compile_events = 1
    BEGIN
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            object_name = oa.c.value('(data[@name="object_name"]/value/text())[1]', 'sysname'),
            statement_text = oa.c.value('(data[@name="statement"]/value/text())[1]', 'nvarchar(max)'),
            compile_cpu_ms = oa.c.value('(data[@name="cpu_time"]/value/text())[1]', 'bigint'),
            compile_duration_ms = oa.c.value('(data[@name="duration"]/value/text())[1]', 'bigint')
        INTO #compiles_1
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        WHERE oa.c.exist('(data[@name="is_recompile"]/value[. = "false"])') = 1
        AND   oa.c.exist('@name[.= "sql_statement_post_compile"]') = 1
        ORDER BY
            event_time;

        ALTER TABLE #compiles_1 ADD statement_text_checksum AS CHECKSUM(database_name + statement_text) PERSISTED;

        IF @debug = 1 BEGIN SELECT N'#compiles_1' AS table_name, c.* FROM #compiles_1 AS c; END;

        WITH
            cbq AS
        (
            SELECT
                statement_text_checksum,
                total_compiles = COUNT_BIG(*),
                total_compile_cpu_ms = SUM(c.compile_cpu_ms),
                avg_compile_cpu_ms = AVG(c.compile_cpu_ms),
                max_compile_cpu_ms = MAX(c.compile_cpu_ms),
                total_compile_duration_ms = SUM(c.compile_duration_ms),
                avg_compile_duration_ms = AVG(c.compile_duration_ms),
                max_compile_duration_ms = MAX(c.compile_duration_ms)
            FROM #compiles_1 AS c
            GROUP BY
                c.statement_text_checksum
        )
        SELECT
            pattern = N'total compiles',
            k.database_name,
            k.object_name,
            statement_text =
                (
                    SELECT
                        [processing-instruction(statement_text)] =
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                k.statement_text COLLATE Latin1_General_BIN2,
                            NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                            NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                            NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                    FOR XML
                        PATH(N''),
                        TYPE
                ),
            c.total_compiles,
            c.total_compile_cpu_ms,
            c.avg_compile_cpu_ms,
            c.max_compile_cpu_ms,
            c.total_compile_duration_ms,
            c.avg_compile_duration_ms,
            c.max_compile_duration_ms
        FROM cbq AS c
        CROSS APPLY
        (
            SELECT TOP (1)
                k.*
            FROM #compiles_1 AS k
            WHERE c.statement_text_checksum = k.statement_text_checksum
            ORDER BY
                k.event_time DESC
        ) AS k
        ORDER BY
            c.total_compiles DESC;

    END;
    IF @compile_events = 0
    BEGIN
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            object_name = oa.c.value('(data[@name="object_name"]/value/text())[1]', 'sysname'),
            statement_text = oa.c.value('(data[@name="statement"]/value/text())[1]', 'nvarchar(max)')
        INTO #compiles_0
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        ORDER BY
            event_time;

        IF @debug = 1 BEGIN SELECT N'#compiles_0' AS table_name, * FROM #compiles_0 AS c; END;

        SELECT
            c.event_time,
            c.event_type,
            c.database_name,
            c.object_name,
            statement_text =
                (
                    SELECT
                        [processing-instruction(statement_text)] =
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                c.statement_text COLLATE Latin1_General_BIN2,
                            NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                            NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                            NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                    FOR XML
                        PATH(N''),
                        TYPE
                )
        FROM #compiles_0 AS c
        ORDER BY
            c.event_time;

    END;

    IF @parameterization_events  = 1
    BEGIN
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            sql_text = oa.c.value('(action[@name="sql_text"]/value/text())[1]', 'nvarchar(max)'),
            compile_cpu_time_ms = oa.c.value('(data[@name="compile_cpu_time"]/value/text())[1]', 'bigint') / 1000.,
            compile_duration_ms = oa.c.value('(data[@name="compile_duration"]/value/text())[1]', 'bigint') / 1000.,
            query_param_type = oa.c.value('(data[@name="query_param_type"]/value/text())[1]', 'integer'),
            is_cached = oa.c.value('(data[@name="is_cached"]/value/text())[1]', 'bit'),
            is_recompiled = oa.c.value('(data[@name="is_recompiled"]/value/text())[1]', 'bit'),
            compile_code = oa.c.value('(data[@name="compile_code"]/text)[1]', 'sysname'),
            has_literals = oa.c.value('(data[@name="has_literals"]/value/text())[1]', 'bit'),
            is_parameterizable = oa.c.value('(data[@name="is_parameterizable"]/value/text())[1]', 'bit'),
            parameterized_values_count = oa.c.value('(data[@name="parameterized_values_count"]/value/text())[1]', 'bigint'),
            query_plan_hash = oa.c.value('xs:hexBinary((data[@name="query_plan_hash"]/value/text())[1])', 'binary(8)'),
            query_hash = oa.c.value('xs:hexBinary((data[@name="query_hash"]/value/text())[1])', 'binary(8)'),
            plan_handle = oa.c.value('xs:hexBinary((action[@name="plan_handle"]/value/text())[1])', 'varbinary(64)'),
            statement_sql_hash = oa.c.value('xs:hexBinary((data[@name="statement_sql_hash"]/value/text())[1])', 'varbinary(64)')
        INTO #parameterization
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        WHERE oa.c.exist('@name[. = "query_parameterization_data"]') = 1
        AND   oa.c.exist('(data[@name="is_recompiled"]/value[. = "false"])') = 1
        ORDER BY
            event_time;

        IF @debug = 1 BEGIN SELECT N'#parameterization' AS table_name, p.* FROM #parameterization AS p; END;

        WITH
            cpq AS
        (
            SELECT
                database_name,
                query_hash,
                total_compiles = COUNT_BIG(*),
                plan_count = COUNT_BIG(DISTINCT p.query_plan_hash),
                total_compile_cpu_ms = SUM(p.compile_cpu_time_ms),
                avg_compile_cpu_ms = AVG(p.compile_cpu_time_ms),
                max_compile_cpu_ms = MAX(p.compile_cpu_time_ms),
                total_compile_duration_ms = SUM(p.compile_duration_ms),
                avg_compile_duration_ms = AVG(p.compile_duration_ms),
                max_compile_duration_ms = MAX(p.compile_duration_ms)
            FROM #parameterization AS p
            GROUP BY
                p.database_name,
                p.query_hash
           )
           SELECT
               pattern = N'parameterization opportunities',
               c.database_name,
               sql_text =
                   (
                       SELECT
                           [processing-instruction(sql_text)] =
                               REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                               REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                               REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                   k.sql_text COLLATE Latin1_General_BIN2,
                               NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                               NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                               NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                       FOR XML
                           PATH(N''),
                           TYPE
                   ),
               k.is_parameterizable,
               c.total_compiles,
               c.plan_count,
               c.total_compile_cpu_ms,
               c.avg_compile_cpu_ms,
               c.max_compile_cpu_ms,
               c.total_compile_duration_ms,
               c.avg_compile_duration_ms,
               c.max_compile_duration_ms,
               k.query_param_type,
               k.is_cached,
               k.is_recompiled,
               k.compile_code,
               k.has_literals,
               k.parameterized_values_count
           FROM cpq AS c
           CROSS APPLY
           (
               SELECT TOP (1)
                   k.*
               FROM #parameterization AS k
               WHERE k.query_hash = c.query_hash
               ORDER BY
                   k.event_time DESC
           ) AS k
        ORDER BY
            c.total_compiles DESC;
    END;
END;

IF LOWER(@event_type) LIKE N'%recomp%'
BEGIN
IF @compile_events = 1
    BEGIN
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            object_name = oa.c.value('(data[@name="object_name"]/value/text())[1]', 'sysname'),
            recompile_cause = oa.c.value('(data[@name="recompile_cause"]/text)[1]', 'sysname'),
            statement_text = oa.c.value('(data[@name="statement"]/value/text())[1]', 'nvarchar(max)'),
            recompile_cpu_ms = oa.c.value('(data[@name="cpu_time"]/value/text())[1]', 'bigint'),
            recompile_duration_ms = oa.c.value('(data[@name="duration"]/value/text())[1]', 'bigint')
        INTO #recompiles_1
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        WHERE oa.c.exist('(data[@name="is_recompile"]/value[. = "false"])') = 0
        ORDER BY
            event_time;

        ALTER TABLE #recompiles_1 ADD statement_text_checksum AS CHECKSUM(database_name + statement_text) PERSISTED;

        IF @debug = 1 BEGIN SELECT N'#recompiles_1' AS table_name, r.* FROM #recompiles_1 AS r ORDER BY r.event_time; END;

        WITH
            cbq AS
        (
            SELECT
                statement_text_checksum,
                recompile_cause,
                total_recompiles = COUNT_BIG(*),
                total_recompile_cpu_ms = SUM(r.recompile_cpu_ms),
                avg_recompile_cpu_ms = AVG(r.recompile_cpu_ms),
                max_recompile_cpu_ms = MAX(r.recompile_cpu_ms),
                total_recompile_duration_ms = SUM(r.recompile_duration_ms),
                avg_recompile_duration_ms = AVG(r.recompile_duration_ms),
                max_recompile_duration_ms = MAX(r.recompile_duration_ms)
            FROM #recompiles_1 AS r
            GROUP BY
                r.statement_text_checksum,
                r.recompile_cause
        )
        SELECT
            pattern = N'total recompiles',
            k.recompile_cause,
            k.database_name,
            k.object_name,
            statement_text =
                (
                    SELECT
                        [processing-instruction(statement_text)] =
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                k.statement_text COLLATE Latin1_General_BIN2,
                            NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                            NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                            NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                    FOR XML
                        PATH(N''),
                        TYPE
                ),
            c.total_recompiles,
            c.total_recompile_cpu_ms,
            c.avg_recompile_cpu_ms,
            c.max_recompile_cpu_ms,
            c.total_recompile_duration_ms,
            c.avg_recompile_duration_ms,
            c.max_recompile_duration_ms
        FROM cbq AS c
        CROSS APPLY
        (
            SELECT
                k.*,
                n =
                ROW_NUMBER() OVER
                (
                    PARTITION BY
                        k.statement_text_checksum,
                        k.recompile_cause
                    ORDER BY
                        k.event_time DESC
                )
            FROM #recompiles_1 AS k
            WHERE c.statement_text_checksum = k.statement_text_checksum
            AND   c.recompile_cause = k.recompile_cause
        ) AS k
        WHERE k.n = 1
        ORDER BY
            c.total_recompiles DESC;

    END;
    IF @compile_events = 0
    BEGIN
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            object_name = oa.c.value('(data[@name="object_name"]/value/text())[1]', 'sysname'),
            recompile_cause = oa.c.value('(data[@name="recompile_cause"]/text)[1]', 'sysname'),
            statement_text = oa.c.value('(data[@name="statement"]/value/text())[1]', 'nvarchar(max)')
        INTO #recompiles_0
        FROM #human_events_xml AS xet
        OUTER APPLY xet.human_events_xml.nodes('//event') AS oa(c)
        ORDER BY
            event_time;

        IF @debug = 1 BEGIN SELECT N'#recompiles_0' AS table_name, * FROM #recompiles_0 AS r; END;

        SELECT
            r.event_time,
            r.event_type,
            r.database_name,
            r.object_name,
            r.recompile_cause,
            statement_text =
                (
                    SELECT
                        [processing-instruction(statement_text)] =
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                r.statement_text COLLATE Latin1_General_BIN2,
                            NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                            NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                            NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                    FOR XML
                        PATH(N''),
                        TYPE
                )
        FROM #recompiles_0 AS r
        ORDER BY
            r.event_time;
    END;
END;


IF LOWER(@event_type) LIKE N'%wait%'
BEGIN
    WITH
        waits AS
    (
        SELECT
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value('@timestamp', 'datetime2')
                ),
            event_type = oa.c.value('@name', 'sysname'),
            database_name = oa.c.value('(action[@name="database_name"]/value/text())[1]', 'sysname'),
            wait_type = oa.c.value('(data[@name="wait_type"]/text)[1]', 'nvarchar(60)'),
            duration_ms = oa.c.value('(data[@name="duration"]/value/text())[1]', 'bigint') ,
            signal_duration_ms = oa.c.value('(data[@name="signal_duration"]/value/text())[1]', 'bigint'),
            wait_resource =
                CASE
                    WHEN @v = 11
                    THEN N'Not Available < 2014'
                    ELSE oa.c.value('(data[@name="wait_resource"]/value/text())[1]', 'sysname')
                END,
           query_plan_hash_signed =
               CONVERT
               (
                   binary(8),
                   oa.c.value('(action[@name="query_plan_hash_signed"]/value/text())[1]', 'bigint')
               ),
            query_hash_signed =
                CONVERT
                (
                    binary(8),
                    oa.c.value('(action[@name="query_hash_signed"]/value/text())[1]', 'bigint')
                ),
            plan_handle = oa.c.value('xs:hexBinary((action[@name="plan_handle"]/value/text())[1])', 'varbinary(64)')
        FROM
        (
            SELECT TOP (2147483647)
                xet.human_events_xml
            FROM #human_events_xml AS xet
            WHERE (xet.human_events_xml.exist('(//event/data[@name="duration"]/value[. > 0])') = 1
              OR   @gimme_danger = 1)
        ) AS c
        OUTER APPLY c.human_events_xml.nodes('//event') AS oa(c)
    )
    SELECT
        w.*
    INTO #waits_agg
    FROM waits AS w;

    IF @debug = 1 BEGIN SELECT N'#waits_agg' AS table_name, * FROM #waits_agg AS wa; END;

    SELECT
        wait_pattern = N'total waits',
        min_event_time = MIN(wa.event_time),
        max_event_time = MAX(wa.event_time),
        wa.wait_type,
        total_waits = COUNT_BIG(*),
        sum_duration_ms = SUM(wa.duration_ms),
        sum_signal_duration_ms = SUM(wa.signal_duration_ms),
        avg_ms_per_wait = SUM(wa.duration_ms) / COUNT_BIG(*)
    FROM #waits_agg AS wa
    GROUP BY
        wa.wait_type
    ORDER BY
        sum_duration_ms DESC;

    SELECT
        wait_pattern = N'total waits by database',
        min_event_time = MIN(wa.event_time),
        max_event_time = MAX(wa.event_time),
        wa.database_name,
        wa.wait_type,
        total_waits = COUNT_BIG(*),
        sum_duration_ms = SUM(wa.duration_ms),
        sum_signal_duration_ms = SUM(wa.signal_duration_ms),
        avg_ms_per_wait = SUM(wa.duration_ms) / COUNT_BIG(*)
    FROM #waits_agg AS wa
    GROUP BY
        wa.database_name,
        wa.wait_type
    ORDER BY
        sum_duration_ms DESC;

    WITH
        plan_waits AS
    (
        SELECT
            wait_pattern =
                N'total waits by query and database',
            min_event_time =
                MIN(wa.event_time),
            max_event_time =
                MAX(wa.event_time),
            wa.database_name,
            wa.wait_type,
            total_waits =
                COUNT_BIG(*),
            wa.plan_handle,
            sum_duration_ms =
                SUM(wa.duration_ms),
            sum_signal_duration_ms =
                SUM(wa.signal_duration_ms),
            avg_ms_per_wait =
                SUM(wa.duration_ms) / COUNT_BIG(*)
        FROM #waits_agg AS wa
        GROUP BY
            wa.database_name,
            wa.wait_type,
            wa.plan_handle
    )
    SELECT
        pw.wait_pattern,
        pw.min_event_time,
        pw.max_event_time,
        pw.database_name,
        pw.wait_type,
        pw.total_waits,
        pw.sum_duration_ms,
        pw.sum_signal_duration_ms,
        pw.avg_ms_per_wait,
        statement_text =
            (
                SELECT
                    [processing-instruction(statement_text)] =
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                        REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            st.text COLLATE Latin1_General_BIN2,
                        NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
                        NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
                        NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
                FOR XML
                    PATH(N''),
                    TYPE
            ),
           qp.query_plan
    FROM plan_waits AS pw
    OUTER APPLY sys.dm_exec_query_plan(pw.plan_handle) AS qp
    OUTER APPLY sys.dm_exec_sql_text(pw.plan_handle) AS st
    ORDER BY
        pw.sum_duration_ms DESC;
END;


IF LOWER(@event_type) LIKE N'%lock%'
BEGIN
    SELECT
        event_time =
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                    MINUTE,
                    GETUTCDATE(),
                    SYSDATETIME()
                ),
                oa.c.value('@timestamp', 'datetime2')
            ),
        database_name = DB_NAME(c.value('(data[@name="database_id"]/value/text())[1]', 'integer')),
        database_id = oa.c.value('(data[@name="database_id"]/value/text())[1]', 'integer'),
        object_id = oa.c.value('(data[@name="object_id"]/value/text())[1]', 'integer'),
        transaction_id = oa.c.value('(data[@name="transaction_id"]/value/text())[1]', 'bigint'),
        resource_owner_type = oa.c.value('(data[@name="resource_owner_type"]/text)[1]', 'sysname'),
        monitor_loop = oa.c.value('(//@monitorLoop)[1]', 'integer'),
        blocking_spid = bg.value('(process/@spid)[1]', 'integer'),
        blocking_ecid = bg.value('(process/@ecid)[1]', 'integer'),
        blocked_spid = bd.value('(process/@spid)[1]', 'integer'),
        blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'),
        query_text_pre = bd.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
        wait_time = bd.value('(process/@waittime)[1]', 'bigint'),
        transaction_name = bd.value('(process/@transactionname)[1]', 'sysname'),
        last_transaction_started = bd.value('(process/@lasttranstarted)[1]', 'datetime2'),
        last_transaction_completed = CONVERT(datetime2, NULL),
        wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(100)'),
        lock_mode = bd.value('(process/@lockMode)[1]', 'nvarchar(10)'),
        status = bd.value('(process/@status)[1]', 'nvarchar(10)'),
        priority = bd.value('(process/@priority)[1]', 'integer'),
        transaction_count = bd.value('(process/@trancount)[1]', 'integer'),
        client_app = bd.value('(process/@clientapp)[1]', 'sysname'),
        host_name = bd.value('(process/@hostname)[1]', 'sysname'),
        login_name = bd.value('(process/@loginname)[1]', 'sysname'),
        isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
        log_used = bd.value('(process/@logused)[1]', 'bigint'),
        clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'),
        clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'),
        currentdbname = bd.value('(process/@currentdbname)[1]', 'sysname'),
        currentdbid = bd.value('(process/@currentdb)[1]', 'integer'),
        blocking_level = 0,
        sort_order = CAST('' AS varchar(400)),
        activity = CASE WHEN oa.c.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END,
        blocked_process_report = oa.c.query('.')
    INTO #blocked
    FROM #human_events_xml AS bx
    OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
    OPTION(RECOMPILE);

    ALTER TABLE #blocked
    ADD query_text AS
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           query_text_pre COLLATE Latin1_General_BIN2,
       NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
       NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
       NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
    PERSISTED;

    ALTER TABLE
        #blocked
    ADD blocking_desc AS
            ISNULL
            (
                '(' +
                CAST(blocking_spid AS varchar(10)) +
                ':' +
                CAST(blocking_ecid AS varchar(10)) +
                ')',
                'unresolved process'
            ) PERSISTED,
        blocked_desc AS
            '(' +
            CAST(blocked_spid AS varchar(10)) +
            ':' +
            CAST(blocked_ecid AS varchar(10)) +
            ')' PERSISTED;

    CREATE CLUSTERED INDEX
        blocking
    ON #blocked
        (monitor_loop, blocking_desc);

    CREATE INDEX
        blocked
    ON #blocked
        (monitor_loop, blocked_desc);

    IF @debug = 1 BEGIN SELECT '#blocked' AS table_name, * FROM #blocked AS wa; END;

    SELECT
        event_time =
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                    MINUTE,
                    GETUTCDATE(),
                    SYSDATETIME()
                ),
                oa.c.value('@timestamp', 'datetime2')
            ),
        database_name = DB_NAME(c.value('(data[@name="database_id"]/value/text())[1]', 'integer')),
        database_id = oa.c.value('(data[@name="database_id"]/value/text())[1]', 'integer'),
        object_id = oa.c.value('(data[@name="object_id"]/value/text())[1]', 'integer'),
        transaction_id = oa.c.value('(data[@name="transaction_id"]/value/text())[1]', 'bigint'),
        resource_owner_type = oa.c.value('(data[@name="resource_owner_type"]/text)[1]', 'sysname'),
        monitor_loop = oa.c.value('(//@monitorLoop)[1]', 'integer'),
        blocking_spid = bg.value('(process/@spid)[1]', 'integer'),
        blocking_ecid = bg.value('(process/@ecid)[1]', 'integer'),
        blocked_spid = bd.value('(process/@spid)[1]', 'integer'),
        blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'),
        query_text_pre = bg.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
        wait_time = bg.value('(process/@waittime)[1]', 'bigint'),
        transaction_name = bg.value('(process/@transactionname)[1]', 'sysname'),
        last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'),
        last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
        wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(100)'),
        lock_mode = bg.value('(process/@lockMode)[1]', 'nvarchar(10)'),
        status = bg.value('(process/@status)[1]', 'nvarchar(10)'),
        priority = bg.value('(process/@priority)[1]', 'integer'),
        transaction_count = bg.value('(process/@trancount)[1]', 'integer'),
        client_app = bg.value('(process/@clientapp)[1]', 'sysname'),
        host_name = bg.value('(process/@hostname)[1]', 'sysname'),
        login_name = bg.value('(process/@loginname)[1]', 'sysname'),
        isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
        log_used = bg.value('(process/@logused)[1]', 'bigint'),
        clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'),
        clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'),
        currentdbname = bg.value('(process/@currentdbname)[1]', 'sysname'),
        currentdbid = bg.value('(process/@currentdb)[1]', 'integer'),
        blocking_level = 0,
        sort_order = CAST('' AS varchar(400)),
        activity = CASE WHEN oa.c.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END,
        blocked_process_report = oa.c.query('.')
    INTO #blocking
    FROM #human_events_xml AS bx
    OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
    OPTION(RECOMPILE);

    ALTER TABLE #blocking
    ADD query_text AS
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           query_text_pre COLLATE Latin1_General_BIN2,
       NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
       NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
       NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
    PERSISTED;

    ALTER TABLE
        #blocking
    ADD blocking_desc AS
            ISNULL
            (
                '(' +
                CAST(blocking_spid AS varchar(10)) +
                ':' +
                CAST(blocking_ecid AS varchar(10)) +
                ')',
                'unresolved process'
            ) PERSISTED,
        blocked_desc AS
            '(' +
            CAST(blocked_spid AS varchar(10)) +
            ':' +
            CAST(blocked_ecid AS varchar(10)) +
            ')' PERSISTED;

    CREATE CLUSTERED INDEX
        blocking
    ON #blocking
        (monitor_loop, blocking_desc);

    CREATE INDEX
        blocked
    ON #blocking
        (monitor_loop, blocked_desc);

    IF @debug = 1 BEGIN SELECT '#blocking' AS table_name, b.* FROM #blocking AS b; END;

    WITH
        hierarchy AS
    (
        SELECT
            b.monitor_loop,
            b.blocking_desc,
            b.blocked_desc,
            level = 0,
            sort_order =
                CAST
                (
                    b.blocking_desc +
                    ' <-- ' +
                    b.blocked_desc AS varchar(400)
                )
        FROM #blocking AS b
        WHERE NOT EXISTS
        (
            SELECT
                1/0
            FROM #blocking AS b2
            WHERE b2.monitor_loop = b.monitor_loop
            AND   b2.blocked_desc = b.blocking_desc
        )

        UNION ALL

        SELECT
            bg.monitor_loop,
            bg.blocking_desc,
            bg.blocked_desc,
            h.level + 1,
            sort_order =
                CAST
                (
                    h.sort_order +
                    ' ' +
                    bg.blocking_desc +
                    ' <-- ' +
                    bg.blocked_desc AS varchar(400)
                )
        FROM hierarchy AS h
        JOIN #blocking AS bg
          ON  bg.monitor_loop = h.monitor_loop
          AND bg.blocking_desc = h.blocked_desc
    )
    UPDATE
        #blocked
    SET
        blocking_level = h.level,
        sort_order = h.sort_order
    FROM #blocked AS b
    JOIN hierarchy AS h
      ON  h.monitor_loop = b.monitor_loop
      AND h.blocking_desc = b.blocking_desc
      AND h.blocked_desc = b.blocked_desc
    OPTION(RECOMPILE);

    UPDATE
        #blocking
    SET
        blocking_level = bd.blocking_level,
        sort_order = bd.sort_order
    FROM #blocking AS bg
    JOIN #blocked AS bd
      ON  bd.monitor_loop = bg.monitor_loop
      AND bd.blocking_desc = bg.blocking_desc
      AND bd.blocked_desc = bg.blocked_desc
    OPTION(RECOMPILE);

    SELECT
        kheb.event_time,
        kheb.database_name,
        contentious_object =
            ISNULL
            (
                kheb.contentious_object,
                N'Unresolved: ' +
                N'database: ' +
                kheb.database_name +
                N' object_id: ' +
                RTRIM(kheb.object_id)
            ),
        kheb.activity,
        blocking_tree =
            REPLICATE(' > ', kheb.blocking_level) +
            CASE kheb.activity
                 WHEN 'blocking'
                 THEN '(' + kheb.blocking_desc + ') is blocking (' + kheb.blocked_desc + ')'
                 ELSE ' > (' + kheb.blocked_desc + ') is blocked by (' + kheb.blocking_desc + ')'
            END,
        spid =
            CASE kheb.activity
                 WHEN 'blocking'
                 THEN kheb.blocking_spid
                 ELSE kheb.blocked_spid
            END,
        ecid =
            CASE kheb.activity
                 WHEN 'blocking'
                 THEN kheb.blocking_ecid
                 ELSE kheb.blocked_ecid
            END,
        query_text =
            CASE
                WHEN kheb.query_text
                     LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|'
                THEN
                    (
                        SELECT
                            [processing-instruction(query)] =
                                OBJECT_SCHEMA_NAME
                                (
                                        SUBSTRING
                                        (
                                            kheb.query_text,
                                            CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                            LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                        )
                                        ,
                                        SUBSTRING
                                        (
                                            kheb.query_text,
                                            CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                            CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                        )
                                ) +
                                N'.' +
                                OBJECT_NAME
                                (
                                     SUBSTRING
                                     (
                                         kheb.query_text,
                                         CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                         LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                     )
                                     ,
                                     SUBSTRING
                                     (
                                         kheb.query_text,
                                         CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                         CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                     )
                                )
                        FOR XML
                            PATH(N''),
                            TYPE
                    )
                ELSE
                    (
                        SELECT
                            [processing-instruction(query)] =
                                kheb.query_text
                        FOR XML
                            PATH(N''),
                            TYPE
                    )
            END,
        wait_time_ms =
            kheb.wait_time,
        kheb.status,
        kheb.isolation_level,
        kheb.lock_mode,
        kheb.resource_owner_type,
        kheb.transaction_count,
        kheb.transaction_name,
        kheb.last_transaction_started,
        kheb.last_transaction_completed,
        client_option_1 =
            SUBSTRING
            (
                CASE WHEN kheb.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
                3,
                8000
            ),
        client_option_2 =
            SUBSTRING
            (
                CASE WHEN kheb.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
                3,
                8000
            ),
        kheb.wait_resource,
        kheb.priority,
        kheb.log_used,
        kheb.client_app,
        kheb.host_name,
        kheb.login_name,
        kheb.transaction_id,
        kheb.database_id,
        kheb.currentdbname,
        kheb.currentdbid,
        kheb.blocked_process_report,
        kheb.sort_order
    INTO #blocks
    FROM
    (
        SELECT
            bg.*,
            contentious_object =
                OBJECT_NAME
                (
                    bg.object_id,
                    bg.database_id
                )
        FROM #blocking AS bg
        WHERE (bg.database_name = @database_name
               OR @database_name = N'')

        UNION ALL

        SELECT
            bd.*,
            contentious_object =
                OBJECT_NAME
                (
                    bd.object_id,
                    bd.database_id
                )
        FROM #blocked AS bd
        WHERE (bd.database_name = @database_name
               OR @database_name = N'')
    ) AS kheb
    OPTION(RECOMPILE);

    SELECT
        blocked_process_report =
            'blocked_process_report',
        b.event_time,
        b.database_name,
        b.currentdbname,
        b.contentious_object,
        b.activity,
        b.blocking_tree,
        b.spid,
        b.ecid,
        b.query_text,
        b.wait_time_ms,
        b.status,
        b.isolation_level,
        b.lock_mode,
        b.resource_owner_type,
        b.transaction_count,
        b.transaction_name,
        b.last_transaction_started,
        b.last_transaction_completed,
        b.client_option_1,
        b.client_option_2,
        b.wait_resource,
        b.priority,
        b.log_used,
        b.client_app,
        b.host_name,
        b.login_name,
        b.transaction_id,
        blocked_process_report_xml =
            b.blocked_process_report
    FROM
    (
        SELECT
            b.*,
            n =
                ROW_NUMBER() OVER
                (
                    PARTITION BY
                        b.transaction_id,
                        b.spid,
                        b.ecid
                    ORDER BY
                        b.event_time DESC
                )
        FROM #blocks AS b
    ) AS b
    WHERE b.n = 1
    AND   (b.contentious_object = @object_name
           OR @object_name = N'')
    ORDER BY
        b.sort_order,
        CASE
            WHEN b.activity = 'blocking'
            THEN -1
            ELSE +1
        END
    OPTION(RECOMPILE);

    SELECT DISTINCT
        b.*
    INTO #available_plans
    FROM
    (
        SELECT
            available_plans =
                'available_plans',
            b.database_name,
            b.database_id,
            b.currentdbname,
            b.currentdbid,
            b.contentious_object,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks AS b
        CROSS APPLY b.blocked_process_report.nodes('/event/data/value/blocked-process-report/blocking-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE (b.database_name = @database_name
                OR @database_name = N'')
        AND  (b.contentious_object = @object_name
                OR @object_name = N'')

        UNION ALL

        SELECT
            available_plans =
                'available_plans',
            b.database_name,
            b.database_id,
            b.currentdbname,
            b.currentdbid,
            b.contentious_object,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks AS b
        CROSS APPLY b.blocked_process_report.nodes('/event/data/value/blocked-process-report/blocked-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE (b.database_name = @database_name
                OR @database_name = N'')
        AND  (b.contentious_object = @object_name
                OR @object_name = N'')
    ) AS b
    OPTION(RECOMPILE);

    IF @debug = 1 BEGIN SELECT '#available_plans' AS table_name, * FROM #available_plans AS wa OPTION(RECOMPILE); END;

    SELECT
        deqs.sql_handle,
        deqs.plan_handle,
        deqs.statement_start_offset,
        deqs.statement_end_offset,
        deqs.creation_time,
        deqs.last_execution_time,
        deqs.execution_count,
        total_worker_time_ms =
            deqs.total_worker_time / 1000.,
        avg_worker_time_ms =
            CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / NULLIF(deqs.execution_count, 0)),
        total_elapsed_time_ms =
            deqs.total_elapsed_time / 1000.,
        avg_elapsed_time =
            CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
        executions_per_second =
            ISNULL
            (
                deqs.execution_count /
                    NULLIF
                    (
                        DATEDIFF
                        (
                            SECOND,
                            deqs.creation_time,
                            NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
                        ),
                        0
                    ),
                    0
            ),
        total_physical_reads_mb =
            deqs.total_physical_reads * 8. / 1024.,
        total_logical_writes_mb =
            deqs.total_logical_writes * 8. / 1024.,
        total_logical_reads_mb =
            deqs.total_logical_reads * 8. / 1024.,
        min_grant_mb =
            deqs.min_grant_kb * 8. / 1024.,
        max_grant_mb =
            deqs.max_grant_kb * 8. / 1024.,
        min_used_grant_mb =
            deqs.min_used_grant_kb * 8. / 1024.,
        max_used_grant_mb =
            deqs.max_used_grant_kb * 8. / 1024.,
        deqs.min_reserved_threads,
        deqs.max_reserved_threads,
        deqs.min_used_threads,
        deqs.max_used_threads,
        deqs.total_rows
    INTO #dm_exec_query_stats
    FROM sys.dm_exec_query_stats AS deqs
    WHERE EXISTS
    (
        SELECT
            1/0
        FROM #available_plans AS ap
        WHERE ap.sql_handle = deqs.sql_handle
    )
    AND deqs.query_hash IS NOT NULL;

    CREATE CLUSTERED INDEX
        deqs
    ON #dm_exec_query_stats
    (
        sql_handle,
        plan_handle
    );

    SELECT
        ap.available_plans,
        ap.database_name,
        ap.currentdbname,
        query_text =
            TRY_CAST(ap.query_text AS xml),
        ap.query_plan,
        ap.creation_time,
        ap.last_execution_time,
        ap.execution_count,
        ap.executions_per_second,
        ap.total_worker_time_ms,
        ap.avg_worker_time_ms,
        ap.total_elapsed_time_ms,
        ap.avg_elapsed_time,
        ap.total_logical_reads_mb,
        ap.total_physical_reads_mb,
        ap.total_logical_writes_mb,
        ap.min_grant_mb,
        ap.max_grant_mb,
        ap.min_used_grant_mb,
        ap.max_used_grant_mb,
        ap.min_reserved_threads,
        ap.max_reserved_threads,
        ap.min_used_threads,
        ap.max_used_threads,
        ap.total_rows,
        ap.sql_handle,
        ap.statement_start_offset,
        ap.statement_end_offset
    FROM
    (

        SELECT
            ap.*,
            c.statement_start_offset,
            c.statement_end_offset,
            c.creation_time,
            c.last_execution_time,
            c.execution_count,
            c.total_worker_time_ms,
            c.avg_worker_time_ms,
            c.total_elapsed_time_ms,
            c.avg_elapsed_time,
            c.executions_per_second,
            c.total_physical_reads_mb,
            c.total_logical_writes_mb,
            c.total_logical_reads_mb,
            c.min_grant_mb,
            c.max_grant_mb,
            c.min_used_grant_mb,
            c.max_used_grant_mb,
            c.min_reserved_threads,
            c.max_reserved_threads,
            c.min_used_threads,
            c.max_used_threads,
            c.total_rows,
            c.query_plan
        FROM #available_plans AS ap
        OUTER APPLY
        (
            SELECT
                deqs.*,
                query_plan =
                    TRY_CAST(deps.query_plan AS xml)
            FROM #dm_exec_query_stats AS deqs
            OUTER APPLY sys.dm_exec_text_query_plan
            (
                deqs.plan_handle,
                deqs.statement_start_offset,
                deqs.statement_end_offset
            ) AS deps
            WHERE deqs.sql_handle = ap.sql_handle
            AND   deps.dbid IN (ap.database_id, ap.currentdbid)
        ) AS c
    ) AS ap
    WHERE ap.query_plan IS NOT NULL
    ORDER BY
        ap.avg_worker_time_ms DESC
    OPTION(RECOMPILE, LOOP JOIN, HASH JOIN);
END;

/* End magic happening */

IF @keep_alive = 0
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR(@stop_sql, 0, 1) WITH NOWAIT;
        RAISERROR(N'all done, stopping session', 0, 1) WITH NOWAIT;
    END;
    EXECUTE (@stop_sql);

    IF @debug = 1
    BEGIN
        RAISERROR(@drop_sql, 0, 1) WITH NOWAIT;
        RAISERROR(N'and dropping session', 0, 1) WITH NOWAIT;
    END;
    EXECUTE (@drop_sql);
END;
RETURN;


/*This section handles outputting data to tables*/
output_results:
IF @debug = 1 BEGIN RAISERROR(N'Starting data collection.', 0, 1) WITH NOWAIT; END;

WHILE 1 = 1
BEGIN
    SET @the_sleeper_must_awaken = N'';
    IF @azure = 0
    BEGIN
        IF NOT EXISTS
        (
            /*If we don't find any sessions to poll from, wait 5 seconds and restart loop*/
            SELECT
                1/0
            FROM sys.server_event_sessions AS ses
            LEFT JOIN sys.dm_xe_sessions AS dxs
              ON dxs.name = ses.name
            WHERE ses.name LIKE N'keeper_HumanEvents_%'
            AND   dxs.create_time IS NOT NULL
        )
        BEGIN
            IF @debug = 1 BEGIN RAISERROR(N'No matching active session names found starting with keeper_HumanEvents', 0, 1) WITH NOWAIT; END;
        END;

        /*If we find any stopped sessions, turn them back on*/
        SELECT
            @the_sleeper_must_awaken +=
                N'ALTER EVENT SESSION ' +
                ses.name +
                N' ON SERVER STATE = START;' +
                @nc10
        FROM sys.server_event_sessions AS ses
        LEFT JOIN sys.dm_xe_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name LIKE N'keeper_HumanEvents_%'
        AND   dxs.create_time IS NULL;
    END;
    ELSE
    BEGIN
        /*If we don't find any sessions to poll from, wait 5 seconds and restart loop*/
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.database_event_sessions AS ses
            JOIN sys.dm_xe_database_sessions AS dxs
              ON dxs.name = ses.name
            WHERE ses.name LIKE N'keeper_HumanEvents_%'
        )
        BEGIN
            IF @debug = 1 BEGIN RAISERROR(N'No matching active session names found starting with keeper_HumanEvents', 0, 1) WITH NOWAIT; END;
        END;

        /*If we find any stopped sessions, turn them back on*/
        SELECT
            @the_sleeper_must_awaken +=
                N'ALTER EVENT SESSION ' +
                ses.name +
                N' ON DATABASE STATE = START;' +
                @nc10
        FROM sys.database_event_sessions AS ses
        LEFT JOIN sys.dm_xe_database_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name LIKE N'keeper_HumanEvents_%'
        AND   dxs.create_time IS NULL;
    END;

    IF LEN(@the_sleeper_must_awaken) > 0
    BEGIN
        IF @debug = 1
        BEGIN
           RAISERROR(@the_sleeper_must_awaken, 0, 1) WITH NOWAIT;
           RAISERROR(N'Starting keeper_HumanEvents... inactive sessions', 0, 1) WITH NOWAIT;
        END;

        EXECUTE sys.sp_executesql
            @the_sleeper_must_awaken;
    END;

    IF
    (
        SELECT
            COUNT_BIG(*)
        FROM #human_events_worker AS hew
    ) = 0
    BEGIN
    /*Insert any sessions we find*/
    IF @azure = 0
        BEGIN
            INSERT
                #human_events_worker WITH(TABLOCK)
            (
                event_type,
                event_type_short,
                is_table_created,
                is_view_created,
                last_checked,
                last_updated,
                output_database,
                output_schema,
                output_table
            )
            SELECT
                s.name,
                N'',
                0,
                0,
                '19000101',
                '19000101',
                @output_database_name,
                @output_schema_name,
                s.name
            FROM sys.server_event_sessions AS s
            JOIN sys.dm_xe_sessions AS r
              ON r.name = s.name
            WHERE s.name LIKE N'keeper_HumanEvents_%';
        END;
        ELSE
        BEGIN
            INSERT
                #human_events_worker WITH(TABLOCK)
            (
                event_type,
                event_type_short,
                is_table_created,
                is_view_created,
                last_checked,
                last_updated,
                output_database,
                output_schema,
                output_table
            )
            SELECT
                s.name,
                N'',
                0,
                0,
                '19000101',
                '19000101',
                @output_database_name,
                @output_schema_name,
                s.name
            FROM sys.database_event_sessions AS s
            JOIN sys.dm_xe_database_sessions AS r
              ON r.name = s.name
            WHERE s.name LIKE N'keeper_HumanEvents_%';
        END;

        /*If we're getting compiles, and the parameterization event is available*/
        /*Add a row to the table so we account for it*/
        IF @parameterization_events = 1
           AND EXISTS
           (
               SELECT
                   1/0
               FROM #human_events_worker AS hew
               WHERE hew.event_type LIKE N'keeper_HumanEvents_compiles%'
           )
        BEGIN
            INSERT
                #human_events_worker WITH(TABLOCK)
            (
                event_type,
                event_type_short,
                is_table_created,
                is_view_created,
                last_checked,
                last_updated,
                output_database,
                output_schema,
                output_table
            )
            SELECT
                hew.event_type +
                N'_parameterization',
                N'',
                1,
                0,
                hew.last_checked,
                hew.last_updated,
                hew.output_database,
                hew.output_schema,
                hew.output_table + N'_parameterization'
            FROM #human_events_worker AS hew
            WHERE hew.event_type LIKE N'keeper_HumanEvents_compiles%';
        END;

        /*Update this column for when we see if we need to create views.*/
        UPDATE
            hew
        SET
            hew.event_type_short =
                CASE
                    WHEN hew.event_type LIKE N'%block%'
                    THEN N'[_]Blocking'
                    WHEN ( hew.event_type LIKE N'%comp%'
                             AND hew.event_type NOT LIKE N'%re%' )
                    THEN N'[_]Compiles'
                    WHEN hew.event_type LIKE N'%quer%'
                    THEN N'[_]Queries'
                    WHEN hew.event_type LIKE N'%recomp%'
                    THEN N'[_]Recompiles'
                    WHEN hew.event_type LIKE N'%wait%'
                    THEN N'[_]Waits'
                    ELSE N'?'
                END
        FROM #human_events_worker AS hew
        WHERE hew.event_type_short = N'';

        IF @debug = 1 BEGIN SELECT N'#human_events_worker' AS table_name, hew.* FROM #human_events_worker AS hew; END;

    END;

    /*This section is where tables that need tables get created*/
    IF EXISTS
    (
        SELECT
            1/0
        FROM #human_events_worker AS hew
        WHERE hew.is_table_created = 0
    )
    BEGIN
        IF @debug = 1 BEGIN RAISERROR(N'Sessions without tables found, starting loop.', 0, 1) WITH NOWAIT; END;

        SELECT
            @min_id =
                MIN(hew.id),
            @max_id =
                MAX(hew.id)
        FROM #human_events_worker AS hew
        WHERE hew.is_table_created = 0;

        IF @debug = 1 BEGIN RAISERROR(N'While, while, while...', 0, 1) WITH NOWAIT; END;
        WHILE @min_id <= @max_id
        BEGIN
            SELECT
                @event_type_check  =
                    hew.event_type,
                @object_name_check =
                    QUOTENAME(hew.output_database) +
                    N'.' +
                    QUOTENAME(hew.output_schema) +
                    N'.' +
                    QUOTENAME(hew.output_table)
            FROM #human_events_worker AS hew
            WHERE hew.id = @min_id
            AND   hew.is_table_created = 0;

            IF OBJECT_ID(@object_name_check) IS NULL
            BEGIN
                IF @debug = 1 BEGIN RAISERROR(N'Generating create table statement for %s', 0, 1, @event_type_check) WITH NOWAIT; END;
                SELECT
                    @table_sql =
                        CASE
                            WHEN @event_type_check LIKE N'%wait%'
                            THEN N'CREATE TABLE ' + @object_name_check + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL, event_type sysname NULL,  ' + @nc10 +
                                 N'  database_name sysname NULL, wait_type nvarchar(60) NULL, duration_ms bigint NULL, signal_duration_ms bigint NULL, ' + @nc10 +
                                 N'  wait_resource sysname NULL, query_plan_hash_signed binary(8) NULL, query_hash_signed binary(8) NULL, plan_handle varbinary(64) NULL );'
                            WHEN @event_type_check LIKE N'%lock%'
                            THEN N'CREATE TABLE ' + @object_name_check + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL, ' + @nc10 +
                                 N'  activity nvarchar(20) NULL, database_name sysname NULL, database_id integer NULL, object_id bigint NULL, contentious_object AS OBJECT_NAME(object_id, database_id), ' + @nc10 +
                                 N'  transaction_id bigint NULL, resource_owner_type sysname NULL, monitor_loop integer NULL, spid integer NULL, ecid integer NULL, query_text nvarchar(max) NULL, ' +
                                 N'  wait_time bigint NULL, transaction_name sysname NULL, last_transaction_started nvarchar(30) NULL, wait_resource nvarchar(100) NULL, ' + @nc10 +
                                 N'  lock_mode nvarchar(10) NULL, status nvarchar(10) NULL, priority integer NULL, transaction_count integer NULL, ' + @nc10 +
                                 N'  client_app sysname NULL, host_name sysname NULL, login_name sysname NULL, isolation_level nvarchar(30) NULL, sql_handle varbinary(64) NULL, blocked_process_report XML NULL );'
                            WHEN @event_type_check LIKE N'%quer%'
                            THEN N'CREATE TABLE ' + @object_name_check + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL, event_type sysname NULL, ' + @nc10 +
                                 N'  database_name sysname NULL, object_name nvarchar(512) NULL, sql_text nvarchar(max) NULL, statement nvarchar(max) NULL, ' + @nc10 +
                                 N'  showplan_xml XML NULL, cpu_ms decimal(18,2) NULL, logical_reads decimal(18,2) NULL, ' + @nc10 +
                                 N'  physical_reads decimal(18,2) NULL, duration_ms decimal(18,2) NULL, writes_mb decimal(18,2) NULL,' + @nc10 +
                                 N'  spills_mb decimal(18,2) NULL, row_count decimal(18,2) NULL, estimated_rows decimal(18,2) NULL, dop integer NULL,  ' + @nc10 +
                                 N'  serial_ideal_memory_mb decimal(18,2) NULL, requested_memory_mb decimal(18,2) NULL, used_memory_mb decimal(18,2) NULL, ideal_memory_mb decimal(18,2) NULL, ' + @nc10 +
                                 N'  granted_memory_mb decimal(18,2) NULL, query_plan_hash_signed binary(8) NULL, query_hash_signed binary(8) NULL, plan_handle varbinary(64) NULL );'
                            WHEN @event_type_check LIKE N'%recomp%'
                            THEN N'CREATE TABLE ' + @object_name_check + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL, event_type sysname NULL,  ' + @nc10 +
                                 N'  database_name sysname NULL, object_name nvarchar(512) NULL, recompile_cause sysname NULL, statement_text nvarchar(max) NULL, statement_text_checksum AS CHECKSUM(database_name + statement_text) PERSISTED '
                                 + CASE WHEN @compile_events = 1 THEN N', compile_cpu_ms bigint NULL, compile_duration_ms bigint NULL );' ELSE N' );' END
                            WHEN @event_type_check LIKE N'%comp%' AND @event_type_check NOT LIKE N'%re%'
                            THEN N'CREATE TABLE ' + @object_name_check + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL, event_type sysname NULL,  ' + @nc10 +
                                 N'  database_name sysname NULL, object_name nvarchar(512) NULL, statement_text nvarchar(max) NULL, statement_text_checksum AS CHECKSUM(database_name + statement_text) PERSISTED '
                                 + CASE WHEN @compile_events = 1 THEN N', compile_cpu_ms bigint NULL, compile_duration_ms bigint NULL );' ELSE N' );' END
                                 + CASE WHEN @parameterization_events = 1
                                        THEN
                                 @nc10 +
                                 N'CREATE TABLE ' + @object_name_check + N'_parameterization' + @nc10 +
                                 N'( id bigint PRIMARY KEY IDENTITY, server_name sysname NULL, event_time datetime2 NULL,  event_type sysname NULL,  ' + @nc10 +
                                 N'  database_name sysname NULL, sql_text nvarchar(max) NULL, compile_cpu_time_ms bigint NULL, compile_duration_ms bigint NULL, query_param_type integer NULL,  ' + @nc10 +
                                 N'  is_cached bit NULL, is_recompiled bit NULL, compile_code sysname NULL, has_literals bit NULL, is_parameterizable bit NULL, parameterized_values_count bigint NULL, ' + @nc10 +
                                 N'  query_plan_hash binary(8) NULL, query_hash binary(8) NULL, plan_handle varbinary(64) NULL, statement_sql_hash varbinary(64) NULL );'
                                        ELSE N''
                                   END
                            ELSE N''
                      END;
            END;

            IF @debug = 1 BEGIN RAISERROR(@table_sql, 0, 1) WITH NOWAIT; END;
            EXECUTE sys.sp_executesql
                @table_sql;

            IF @debug = 1 BEGIN RAISERROR(N'Updating #human_events_worker to set is_table_created for %s', 0, 1, @event_type_check) WITH NOWAIT; END;
            UPDATE
                #human_events_worker
            SET
                is_table_created = 1
            WHERE id = @min_id
            AND   is_table_created = 0;

            IF @debug = 1 BEGIN RAISERROR(N'@min_id: %i', 0, 1, @min_id) WITH NOWAIT; END;

            IF @debug = 1 BEGIN RAISERROR(N'Setting next id after %i out of %i total', 0, 1, @min_id, @max_id) WITH NOWAIT; END ;
            SET @min_id =
            (
                SELECT TOP (1)
                    hew.id
                FROM #human_events_worker AS hew
                WHERE hew.id > @min_id
                AND   hew.is_table_created = 0
                ORDER BY
                    hew.id
            );

            IF @debug = 1 BEGIN RAISERROR(N'new @min_id: %i', 0, 1, @min_id) WITH NOWAIT; END;
            IF @min_id IS NULL BREAK;
        END;
    END;

/*This section handles creating or altering views*/
IF EXISTS
(   /* Any views not created */
    SELECT
        1/0
    FROM #human_events_worker AS hew
    WHERE hew.is_table_created = 1
    AND   hew.is_view_created = 0
)
OR
(   /* If the proc has been modified, maybe views have been added or changed? */
    SELECT
        o.modify_date
    FROM sys.all_objects AS o
    WHERE o.type = N'P'
    AND   o.name = N'sp_HumanEvents'
) < DATEADD(HOUR, -1, SYSDATETIME())
BEGIN
    IF @debug = 1 BEGIN RAISERROR(N'Found views to create, beginning!', 0, 1) WITH NOWAIT; END;
    IF
    (
        SELECT
            COUNT_BIG(*)
        FROM #view_check AS vc
    ) = 0
    BEGIN
        IF @debug = 1 BEGIN RAISERROR(N'#view_check was empty, creating and populating', 0, 1) WITH NOWAIT; END;
        /* These binary values are the view definitions. If I didn't do this, I would have been adding >50k lines of code in here. */
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Blocking', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0042006C006F0063006B0069006E0067000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400370029000D000A0020002000200020006B006800650062002E006500760065006E0074005F00740069006D0065002C000D000A0020002000200020006B006800650062002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006B006800650062002E0063006F006E00740065006E00740069006F00750073005F006F0062006A006500630074002C000D000A0020002000200020006B006800650062002E00610063007400690076006900740079002C000D000A0020002000200020006B006800650062002E0073007000690064002C000D000A0020002000200020006B006800650062002E00710075006500720079005F0074006500780074002C000D000A0020002000200020006B006800650062002E0077006100690074005F00740069006D0065002C000D000A0020002000200020006B006800650062002E007300740061007400750073002C000D000A0020002000200020006B006800650062002E00690073006F006C006100740069006F006E005F006C006500760065006C002C000D000A0020002000200020006B006800650062002E006C006100730074005F007400720061006E00730061006300740069006F006E005F0073007400610072007400650064002C000D000A0020002000200020006B006800650062002E007400720061006E00730061006300740069006F006E005F006E0061006D0065002C000D000A0020002000200020006B006800650062002E006C006F0063006B005F006D006F00640065002C000D000A0020002000200020006B006800650062002E007000720069006F0072006900740079002C000D000A0020002000200020006B006800650062002E007400720061006E00730061006300740069006F006E005F0063006F0075006E0074002C000D000A0020002000200020006B006800650062002E0063006C00690065006E0074005F006100700070002C000D000A0020002000200020006B006800650062002E0068006F00730074005F006E0061006D0065002C000D000A0020002000200020006B006800650062002E006C006F00670069006E005F006E0061006D0065002C000D000A0020002000200020006B006800650062002E0062006C006F0063006B00650064005F00700072006F0063006500730073005F007200650070006F00720074000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B006800650062000D000A004F0052004400450052002000420059000D000A0020002000200020006B006800650062002E006500760065006E0074005F00740069006D0065002C000D000A00200020002000200043004100530045000D000A00200020002000200020002000200020005700480045004E0020006B006800650062002E006100630074006900760069007400790020003D002000270062006C006F0063006B0069006E00670027000D000A00200020002000200020002000200020005400480045004E00200031000D000A002000200020002000200020002000200045004C005300450020003900390039000D000A00200020002000200045004E0044003B00;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_CompilesByDatabaseAndObject', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0043006F006D00700069006C0065007300420079004400610074006100620061007300650041006E0064004F0062006A006500630074000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006D0069006E005F006500760065006E0074005F00740069006D00650020003D0020004D0049004E0028006500760065006E0074005F00740069006D00650029002C000D000A0020002000200020006D00610078005F006500760065006E0074005F00740069006D00650020003D0020004D004100580028006500760065006E0074005F00740069006D00650029002C000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D00650020003D0020000D000A002000200020002000200020002000200043004100530045000D000A002000200020002000200020002000200020002000200020005700480045004E0020006F0062006A006500630074005F006E0061006D00650020003D0020004E00270027000D000A002000200020002000200020002000200020002000200020005400480045004E0020004E0027004E002F00410027000D000A0020002000200020002000200020002000200020002000200045004C005300450020006F0062006A006500630074005F006E0061006D0065000D000A002000200020002000200020002000200045004E0044002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A0020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A0020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A00470052004F00550050002000420059000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D0065000D000A004F00520044004500520020004200590020000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C0065007300200044004500530043003B00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_CompilesByDuration', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0043006F006D00700069006C0065007300420079004400750072006100740069006F006E000D000A00410053000D000A0057004900540048000D000A0020002000200020006300620071002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A002000200020002000470052004F005500500020004200590020000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A00200020002000200048004100560049004E00470020000D000A0020002000200020002000200020002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D007300290020003E00200031003000300030000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006B002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020006B002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C00650073002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E000D000A00460052004F004D002000630062007100200041005300200063000D000A00430052004F005300530020004100500050004C0059000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020006B002E002A000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B000D000A00200020002000200057004800450052004500200063002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D0020003D0020006B002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A0020002000200020004F00520044004500520020004200590020000D000A00200020002000200020002000200020006B002E0069006400200044004500530043000D000A00290020004100530020006B000D000A004F00520044004500520020004200590020000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E00200044004500530043003B00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_CompilesByQuery', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0043006F006D00700069006C006500730042007900510075006500720079000D000A00410053000D000A0057004900540048000D000A0020002000200020006300620071002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A002000200020002000470052004F005500500020004200590020000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A00200020002000200048004100560049004E00470020000D000A002000200020002000200020002000200043004F0055004E0054005F0042004900470028002A00290020003E003D002000310030000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006B002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020006B002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C00650073002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E000D000A00460052004F004D002000630062007100200041005300200063000D000A00430052004F005300530020004100500050004C0059000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020006B002E002A000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B000D000A00200020002000200057004800450052004500200063002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D0020003D0020006B002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A0020002000200020004F00520044004500520020004200590020006B002E0069006400200044004500530043000D000A00290020004100530020006B000D000A004F00520044004500520020004200590020000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065007300200044004500530043003B00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Parameterization', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0050006100720061006D00650074006500720069007A006100740069006F006E000D000A00410053000D000A0057004900540048000D000A0020002000200020006300700071002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000710075006500720079005F0068006100730068002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000200020002000200070006C0061006E005F0063006F0075006E00740020003D00200043004F0055004E0054002800440049005300540049004E00430054002000710075006500720079005F0070006C0061006E005F00680061007300680029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F00740069006D0065005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F00740069006D0065005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F00740069006D0065005F006D00730029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A002000200020002000470052004F00550050002000420059000D000A0020002000200020002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000710075006500720079005F0068006100730068000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A00200020002000200063002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006B002E00730071006C005F0074006500780074002C000D000A0020002000200020006B002E00690073005F0070006100720061006D00650074006500720069007A00610062006C0065002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C00650073002C000D000A00200020002000200063002E0070006C0061006E005F0063006F0075006E0074002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A0020002000200020006B002E00710075006500720079005F0070006100720061006D005F0074007900700065002C000D000A0020002000200020006B002E00690073005F006300610063006800650064002C000D000A0020002000200020006B002E00690073005F007200650063006F006D00700069006C00650064002C000D000A0020002000200020006B002E0063006F006D00700069006C0065005F0063006F00640065002C000D000A0020002000200020006B002E006800610073005F006C00690074006500720061006C0073002C000D000A0020002000200020006B002E0070006100720061006D00650074006500720069007A00650064005F00760061006C007500650073005F0063006F0075006E0074000D000A00460052004F004D002000630070007100200041005300200063000D000A00430052004F005300530020004100500050004C0059000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020006B002E002A000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B000D000A0020002000200020005700480045005200450020006B002E00710075006500720079005F00680061007300680020003D00200063002E00710075006500720079005F0068006100730068000D000A0020002000200020004F00520044004500520020004200590020006B002E0069006400200044004500530043000D000A00290020004100530020006B000D000A004F00520044004500520020004200590020000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065007300200044004500530043003B00
        WHERE @parameterization_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Queries', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0051007500650072006900650073000D000A00410053000D000A0057004900540048000D000A002000200020002000710075006500720079005F006100670067002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A002000200020002000200020002000200074006F00740061006C005F006300700075005F006D00730020003D002000490053004E0055004C004C00280071002E006300700075005F006D0073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F006C006F0067006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E006C006F0067006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0070006800790073006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E0070006800790073006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F006400750072006100740069006F006E005F006D00730020003D002000490053004E0055004C004C00280071002E006400750072006100740069006F006E005F006D0073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F007700720069007400650073005F006D00620020003D002000490053004E0055004C004C00280071002E007700720069007400650073005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F007300700069006C006C0073005F006D00620020003D002000490053004E0055004C004C00280071002E007300700069006C006C0073005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F0072006F007700730020003D002000490053004E0055004C004C00280071002E0072006F0077005F0063006F0075006E0074002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006300700075005F006D00730020003D002000490053004E0055004C004C00280071002E006300700075005F006D0073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006C006F0067006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E006C006F0067006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F0070006800790073006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E0070006800790073006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006400750072006100740069006F006E005F006D00730020003D002000490053004E0055004C004C00280071002E006400750072006100740069006F006E005F006D0073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F007700720069007400650073005F006D00620020003D002000490053004E0055004C004C00280071002E007700720069007400650073005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F007300700069006C006C0073005F006D00620020003D002000490053004E0055004C004C00280071002E007300700069006C006C0073005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F0072006F007700730020003D002000490053004E0055004C004C00280071002E0072006F0077005F0063006F0075006E0074002C002000300029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D00200041005300200071000D000A00200020002000200057004800450052004500200071002E006500760065006E0074005F00740079007000650020003C003E0020004E002700710075006500720079005F0070006F00730074005F0065007800650063007500740069006F006E005F00730068006F00770070006C0061006E0027000D000A0020002000200020000D000A00200020002000200055004E0049004F004E00200041004C004C000D000A0020002000200020000D000A002000200020002000530045004C004500430054000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A00200020002000200020002000200020002F002A0074006F00740061006C0073002A002F000D000A002000200020002000200020002000200074006F00740061006C005F006300700075005F006D00730020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F006C006F0067006900630061006C005F007200650061006400730020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F0070006800790073006900630061006C005F007200650061006400730020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F006400750072006100740069006F006E005F006D00730020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F0077007200690074006500730020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F007300700069006C006C0073005F006D00620020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D002000490053004E0055004C004C00280071002E0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D002000490053004E0055004C004C00280071002E006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0072006F007700730020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020002F002A00610076006500720061006700650073002A002F000D000A00200020002000200020002000200020006100760067005F006300700075005F006D00730020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F006C006F0067006900630061006C005F007200650061006400730020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F0070006800790073006900630061006C005F007200650061006400730020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F006400750072006100740069006F006E005F006D00730020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F007700720069007400650073005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F007300700069006C006C0073005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D002000490053004E0055004C004C00280071002E0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D002000490053004E0055004C004C00280071002E006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F0072006F007700730020003D0020004E0055004C004C000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D00200041005300200071000D000A00200020002000200057004800450052004500200071002E006500760065006E0074005F00740079007000650020003D0020004E002700710075006500720079005F0070006F00730074005F0065007800650063007500740069006F006E005F00730068006F00770070006C0061006E0027000D000A0029002C000D000A00200020002000200074006F00740061006C0073002000410053000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A002000200020002000200020002000200065007800650063007500740069006F006E00730020003D0020000D000A0020002000200020002000200020002000200020002000200043004F0055004E0054005F0042004900470028002A00290020004F0056004500520020000D000A0020002000200020002000200020002000200020002000200028000D000A00200020002000200020002000200020002000200020002000200020002000200050004100520054004900540049004F004E002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200029002C000D000A00200020002000200020002000200020002F002A0074006F00740061006C0073002A002F000D000A002000200020002000200020002000200074006F00740061006C005F006300700075005F006D00730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006300700075005F006D0073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006C006F0067006900630061006C005F007200650061006400730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0070006800790073006900630061006C005F007200650061006400730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0077007200690074006500730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F007700720069007400650073005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F007300700069006C006C0073005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0072006F007700730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0072006F00770073002C00200030002E00290029002C000D000A00200020002000200020002000200020002F002A00610076006500720061006700650073002A002F000D000A00200020002000200020002000200020006100760067005F006300700075005F006D00730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006300700075005F006D0073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006C006F0067006900630061006C005F007200650061006400730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0070006800790073006900630061006C005F007200650061006400730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006400750072006100740069006F006E005F006D00730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006400750072006100740069006F006E005F006D0073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0077007200690074006500730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F007700720069007400650073005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F007300700069006C006C0073005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F007300700069006C006C0073005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0072006F007700730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0072006F00770073002C0020003000290029000D000A002000200020002000460052004F004D002000710075006500720079005F00610067006700200041005300200071000D000A002000200020002000470052004F00550050002000420059000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0029002C000D000A002000200020002000710075006500720079005F0072006500730075006C00740073002000410053000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000200020002000200071002E006500760065006E0074005F00740069006D0065002C000D000A002000200020002000200020002000200071002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000200020002000200071002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020002000200020002000710032002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A002000200020002000200020002000200071002E00730071006C005F0074006500780074002C000D000A002000200020002000200020002000200071002E00730068006F00770070006C0061006E005F0078006D006C002C000D000A002000200020002000200020002000200074002E0065007800650063007500740069006F006E0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006300700075005F006D0073002C000D000A002000200020002000200020002000200074002E006100760067005F006300700075005F006D0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000200020002000200074002E006100760067005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F007700720069007400650073002C000D000A002000200020002000200020002000200074002E006100760067005F007700720069007400650073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F007300700069006C006C0073005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0072006F00770073002C000D000A002000200020002000200020002000200074002E006100760067005F0072006F00770073002C000D000A002000200020002000200020002000200071002E00730065007200690061006C005F0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E007200650071007500650073007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E0065007300740069006D0061007400650064005F0072006F00770073002C000D000A002000200020002000200020002000200071002E0064006F0070002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A00200020002000200020002000200020006E0020003D00200052004F0057005F004E0055004D004200450052002800290020004F0056004500520020000D000A0020002000200020002000200020002000200020002000200028000D000A00200020002000200020002000200020002000200020002000200020002000200050004100520054004900540049004F004E002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200020002000200020004F0052004400450052002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D00200041005300200071000D000A0020002000200020004A004F0049004E00200074006F00740061006C007300200041005300200074000D000A00200020002000200020002000200020004F004E002000200071002E00710075006500720079005F0068006100730068005F007300690067006E006500640020003D00200074002E00710075006500720079005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E004400200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E006500640020003D00200074002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E004400200071002E0070006C0061006E005F00680061006E0064006C00650020003D00200074002E0070006C0061006E005F00680061006E0064006C0065000D000A002000200020002000430052004F005300530020004100500050004C0059000D000A00200020002000200028000D000A0020002000200020002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020002000200020002000730074006100740065006D0065006E0074005F00740065007800740020003D002000710032002E00730074006100740065006D0065006E0074000D000A0020002000200020002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D002000410053002000710032000D000A002000200020002000200020002000200057004800450052004500200071002E00710075006500720079005F0068006100730068005F007300690067006E006500640020003D002000710032002E00710075006500720079005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E00440020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E006500640020003D002000710032002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E00440020002000200071002E0070006C0061006E005F00680061006E0064006C00650020003D002000710032002E0070006C0061006E005F00680061006E0064006C0065000D000A002000200020002000200020002000200041004E004400200020002000710032002E00730074006100740065006D0065006E00740020004900530020004E004F00540020004E0055004C004C000D000A00200020002000200020002000200020004F00520044004500520020004200590020000D000A00200020002000200020002000200020002000200020002000710032002E006500760065006E0074005F00740069006D006500200044004500530043000D000A00200020002000200029002000410053002000710032000D000A00200020002000200057004800450052004500200071002E00730068006F00770070006C0061006E005F0078006D006C002E0065007800690073007400280027002A002700290020003D00200031000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A00200020002000200071002E006500760065006E0074005F00740069006D0065002C000D000A00200020002000200071002E00640061007400610062006100730065005F006E0061006D0065002C000D000A00200020002000200071002E006F0062006A006500630074005F006E0061006D0065002C000D000A002000200020002000730074006100740065006D0065006E0074005F00740065007800740020003D0020000D000A00200020002000200020002000200020005400520059005F004300410053005400280071002E00730074006100740065006D0065006E0074005F007400650078007400200041005300200078006D006C0029002C000D000A002000200020002000730071006C005F00740065007800740020003D0020000D000A00200020002000200020002000200020005400520059005F004300410053005400280071002E00730071006C005F007400650078007400200041005300200078006D006C0029002C000D000A00200020002000200071002E00730068006F00770070006C0061006E005F0078006D006C002C000D000A00200020002000200071002E0065007800650063007500740069006F006E0073002C000D000A00200020002000200071002E0074006F00740061006C005F006300700075005F006D0073002C000D000A00200020002000200071002E006100760067005F006300700075005F006D0073002C000D000A00200020002000200071002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C000D000A00200020002000200071002E006100760067005F006400750072006100740069006F006E005F006D0073002C000D000A00200020002000200071002E0074006F00740061006C005F007700720069007400650073002C000D000A00200020002000200071002E006100760067005F007700720069007400650073002C000D000A00200020002000200071002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C000D000A00200020002000200071002E006100760067005F007300700069006C006C0073005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F0072006F00770073002C000D000A00200020002000200071002E006100760067005F0072006F00770073002C000D000A00200020002000200071002E00730065007200690061006C005F0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E007200650071007500650073007400650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0065007300740069006D0061007400650064005F0072006F00770073002C000D000A00200020002000200071002E0064006F0070002C000D000A00200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A00200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A00200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A00460052004F004D002000710075006500720079005F0072006500730075006C0074007300200041005300200071000D000A0057004800450052004500200071002E006E0020003D00200031003B00
        WHERE @skip_plans = 0;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Queries', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0051007500650072006900650073005F006E0070000D000A00410053000D000A0057004900540048000D000A002000200020002000710075006500720079005F006100670067002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A002000200020002000200020002000200074006F00740061006C005F006300700075005F006D00730020003D002000490053004E0055004C004C00280071002E006300700075005F006D0073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F006C006F0067006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E006C006F0067006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0070006800790073006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E0070006800790073006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F006400750072006100740069006F006E005F006D00730020003D002000490053004E0055004C004C00280071002E006400750072006100740069006F006E005F006D0073002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F007700720069007400650073005F006D00620020003D002000490053004E0055004C004C00280071002E007700720069007400650073005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F007300700069006C006C0073005F006D00620020003D002000490053004E0055004C004C00280071002E007300700069006C006C0073005F006D0062002C00200030002E0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A002000200020002000200020002000200074006F00740061006C005F0072006F007700730020003D002000490053004E0055004C004C00280071002E0072006F0077005F0063006F0075006E0074002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006300700075005F006D00730020003D002000490053004E0055004C004C00280071002E006300700075005F006D0073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006C006F0067006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E006C006F0067006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F0070006800790073006900630061006C005F007200650061006400730020003D002000490053004E0055004C004C00280071002E0070006800790073006900630061006C005F00720065006100640073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F006400750072006100740069006F006E005F006D00730020003D002000490053004E0055004C004C00280071002E006400750072006100740069006F006E005F006D0073002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F007700720069007400650073005F006D00620020003D002000490053004E0055004C004C00280071002E007700720069007400650073005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F007300700069006C006C0073005F006D00620020003D002000490053004E0055004C004C00280071002E007300700069006C006C0073005F006D0062002C00200030002E0029002C000D000A00200020002000200020002000200020006100760067005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004E0055004C004C002C000D000A00200020002000200020002000200020006100760067005F0072006F007700730020003D002000490053004E0055004C004C00280071002E0072006F0077005F0063006F0075006E0074002C002000300029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D00200041005300200071000D000A00200020002000200057004800450052004500200071002E006500760065006E0074005F00740079007000650020003C003E0020004E002700710075006500720079005F0070006F00730074005F0065007800650063007500740069006F006E005F00730068006F00770070006C0061006E0027000D000A0029002C000D000A00200020002000200074006F00740061006C0073002000410053000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A002000200020002000200020002000200065007800650063007500740069006F006E00730020003D0020000D000A0020002000200020002000200020002000200020002000200043004F0055004E0054005F0042004900470028002A00290020004F0056004500520020000D000A0020002000200020002000200020002000200020002000200028000D000A00200020002000200020002000200020002000200020002000200020002000200050004100520054004900540049004F004E002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200029002C000D000A00200020002000200020002000200020002F002A0074006F00740061006C0073002A002F000D000A002000200020002000200020002000200074006F00740061006C005F006300700075005F006D00730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006300700075005F006D0073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006C006F0067006900630061006C005F007200650061006400730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0070006800790073006900630061006C005F007200650061006400730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0077007200690074006500730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F007700720069007400650073005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F007300700069006C006C0073005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A002000200020002000200020002000200074006F00740061006C005F0072006F007700730020003D002000530055004D002800490053004E0055004C004C00280071002E0074006F00740061006C005F0072006F00770073002C00200030002E00290029002C000D000A00200020002000200020002000200020002F002A00610076006500720061006700650073002A002F000D000A00200020002000200020002000200020006100760067005F006300700075005F006D00730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006300700075005F006D0073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006C006F0067006900630061006C005F007200650061006400730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0070006800790073006900630061006C005F007200650061006400730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006400750072006100740069006F006E005F006D00730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006400750072006100740069006F006E005F006D0073002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0077007200690074006500730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F007700720069007400650073005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F007300700069006C006C0073005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F007300700069006C006C0073005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0075007300650064005F006D0065006D006F00720079005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D00620020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C00200030002E00290029002C000D000A00200020002000200020002000200020006100760067005F0072006F007700730020003D0020004100560047002800490053004E0055004C004C00280071002E006100760067005F0072006F00770073002C0020003000290029000D000A002000200020002000460052004F004D002000710075006500720079005F00610067006700200041005300200071000D000A002000200020002000470052004F00550050002000420059000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0029002C000D000A002000200020002000710075006500720079005F0072006500730075006C00740073002000410053000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000200020002000200071002E006500760065006E0074005F00740069006D0065002C000D000A002000200020002000200020002000200071002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000200020002000200071002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020002000200020002000710032002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A002000200020002000200020002000200071002E00730071006C005F0074006500780074002C000D000A002000200020002000200020002000200071002E00730068006F00770070006C0061006E005F0078006D006C002C000D000A002000200020002000200020002000200074002E0065007800650063007500740069006F006E0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006300700075005F006D0073002C000D000A002000200020002000200020002000200074002E006100760067005F006300700075005F006D0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000200020002000200074002E006100760067005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F007700720069007400650073002C000D000A002000200020002000200020002000200074002E006100760067005F007700720069007400650073002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F007300700069006C006C0073005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200074002E0074006F00740061006C005F0072006F00770073002C000D000A002000200020002000200020002000200074002E006100760067005F0072006F00770073002C000D000A002000200020002000200020002000200071002E00730065007200690061006C005F0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E007200650071007500650073007400650064005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E0069006400650061006C005F006D0065006D006F00720079005F006D0062002C000D000A002000200020002000200020002000200071002E0065007300740069006D0061007400650064005F0072006F00770073002C000D000A002000200020002000200020002000200071002E0064006F0070002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065002C000D000A00200020002000200020002000200020006E0020003D00200052004F0057005F004E0055004D004200450052002800290020004F0056004500520020000D000A0020002000200020002000200020002000200020002000200028000D000A00200020002000200020002000200020002000200020002000200020002000200050004100520054004900540049004F004E002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200020002000200020004F0052004400450052002000420059000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A002000200020002000200020002000200020002000200020002000200020002000200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A0020002000200020002000200020002000200020002000200029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D00200041005300200071000D000A0020002000200020004A004F0049004E00200074006F00740061006C007300200041005300200074000D000A002000200020002000200020004F004E002000200071002E00710075006500720079005F0068006100730068005F007300690067006E006500640020003D00200074002E00710075006500720079005F0068006100730068005F007300690067006E00650064000D000A0020002000200020002000200041004E004400200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E006500640020003D00200074002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064000D000A0020002000200020004F00550054004500520020004100500050004C0059000D000A00200020002000200028000D000A0020002000200020002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020002000200020002000730074006100740065006D0065006E0074005F00740065007800740020003D002000710032002E00730074006100740065006D0065006E0074000D000A0020002000200020002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D002000410053002000710032000D000A002000200020002000200020002000200057004800450052004500200071002E00710075006500720079005F0068006100730068005F007300690067006E006500640020003D002000710032002E00710075006500720079005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E00440020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E006500640020003D002000710032002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064000D000A002000200020002000200020002000200041004E004400200020002000710032002E00730074006100740065006D0065006E00740020004900530020004E004F00540020004E0055004C004C000D000A00200020002000200020002000200020004F00520044004500520020004200590020000D000A00200020002000200020002000200020002000200020002000710032002E006500760065006E0074005F00740069006D006500200044004500530043000D000A00200020002000200029002000410053002000710032000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A00200020002000200071002E006500760065006E0074005F00740069006D0065002C000D000A00200020002000200071002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000730074006100740065006D0065006E0074005F00740065007800740020003D0020000D000A00200020002000200020002000200020005400520059005F004300410053005400280071002E00730074006100740065006D0065006E0074005F007400650078007400200041005300200078006D006C0029002C000D000A002000200020002000730071006C005F00740065007800740020003D0020000D000A00200020002000200020002000200020005400520059005F004300410053005400280071002E00730071006C005F007400650078007400200041005300200078006D006C0029002C000D000A00200020002000200071002E0065007800650063007500740069006F006E0073002C000D000A00200020002000200071002E0074006F00740061006C005F006300700075005F006D0073002C000D000A00200020002000200071002E006100760067005F006300700075005F006D0073002C000D000A00200020002000200071002E0074006F00740061006C005F006C006F0067006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E006100760067005F006C006F0067006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E0074006F00740061006C005F0070006800790073006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E006100760067005F0070006800790073006900630061006C005F00720065006100640073002C000D000A00200020002000200071002E0074006F00740061006C005F006400750072006100740069006F006E005F006D0073002C000D000A00200020002000200071002E006100760067005F006400750072006100740069006F006E005F006D0073002C000D000A00200020002000200071002E0074006F00740061006C005F007700720069007400650073002C000D000A00200020002000200071002E006100760067005F007700720069007400650073002C000D000A00200020002000200071002E0074006F00740061006C005F007300700069006C006C0073005F006D0062002C000D000A00200020002000200071002E006100760067005F007300700069006C006C0073005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E006100760067005F0075007300650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E006100760067005F006700720061006E007400650064005F006D0065006D006F00720079005F006D0062002C000D000A00200020002000200071002E0074006F00740061006C005F0072006F00770073002C000D000A00200020002000200071002E006100760067005F0072006F00770073002C000D000A00200020002000200071002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A00200020002000200071002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A00200020002000200071002E0070006C0061006E005F00680061006E0064006C0065000D000A00460052004F004D002000710075006500720079005F0072006500730075006C0074007300200041005300200071000D000A0057004800450052004500200071002E006E0020003D00200031003B00
        WHERE @skip_plans = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_RecompilesByDatabaseAndObject', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005200650063006F006D00700069006C0065007300420079004400610074006100620061007300650041006E0064004F0062006A006500630074000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006D0069006E005F006500760065006E0074005F00740069006D00650020003D0020004D0049004E0028006500760065006E0074005F00740069006D00650029002C000D000A0020002000200020006D00610078005F006500760065006E0074005F00740069006D00650020003D0020004D004100580028006500760065006E0074005F00740069006D00650029002C000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D00650020003D0020000D000A002000200020002000200020002000200043004100530045000D000A002000200020002000200020002000200020002000200020005700480045004E0020006F0062006A006500630074005F006E0061006D00650020003D0020004E00270027000D000A002000200020002000200020002000200020002000200020005400480045004E0020004E0027004E002F00410027000D000A0020002000200020002000200020002000200020002000200045004C005300450020006F0062006A006500630074005F006E0061006D0065000D000A002000200020002000200020002000200045004E0044002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A0020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A0020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A00470052004F00550050002000420059000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D0065000D000A004F005200440045005200200042005900200074006F00740061006C005F0063006F006D00700069006C0065007300200044004500530043003B00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_RecompilesByDuration', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005200650063006F006D00700069006C0065007300420079004400750072006100740069006F006E000D000A00410053000D000A0057004900540048000D000A0020002000200020006300620071002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A002000200020002000470052004F005500500020004200590020000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A00200020002000200048004100560049004E00470020000D000A0020002000200020002000200020002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D007300290020003E00200031003000300030000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006B002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020006B002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C00650073002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E000D000A00460052004F004D002000630062007100200041005300200063000D000A00430052004F005300530020004100500050004C0059000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020006B002E002A000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B000D000A00200020002000200057004800450052004500200063002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D0020003D0020006B002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A0020002000200020004F00520044004500520020004200590020006B002E0069006400200044004500530043000D000A00290020004100530020006B000D000A004F00520044004500520020004200590020000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E00200044004500530043003B00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_RecompilesByQuery', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005200650063006F006D00700069006C006500730042007900510075006500720079000D000A00410053000D000A0057004900540048000D000A0020002000200020006300620071002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D002C000D000A002000200020002000200020002000200074006F00740061006C005F007200650063006F006D00700069006C006500730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F0063007000750020003D002000530055004D00280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F0063007000750020003D002000410056004700280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F0063007000750020003D0020004D0041005800280063006F006D00700069006C0065005F006300700075005F006D00730029002C000D000A002000200020002000200020002000200074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000530055004D00280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D002000410056004700280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E0020003D0020004D0041005800280063006F006D00700069006C0065005F006400750072006100740069006F006E005F006D00730029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A002000200020002000470052004F005500500020004200590020000D000A0020002000200020002000200020002000730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A00200020002000200048004100560049004E00470020000D000A002000200020002000200020002000200043004F0055004E0054005F0042004900470028002A00290020003E003D002000310030000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006B002E006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020006B002E00730074006100740065006D0065006E0074005F0074006500780074002C000D000A00200020002000200063002E0074006F00740061006C005F007200650063006F006D00700069006C00650073002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006300700075002C000D000A00200020002000200063002E0074006F00740061006C005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006100760067005F0063006F006D00700069006C0065005F006400750072006100740069006F006E002C000D000A00200020002000200063002E006D00610078005F0063006F006D00700069006C0065005F006400750072006100740069006F006E000D000A00460052004F004D002000630062007100200041005300200063000D000A00430052004F005300530020004100500050004C0059000D000A0028000D000A002000200020002000530045004C00450043005400200054004F00500020002800310029000D000A00200020002000200020002000200020006B002E002A000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D0020004100530020006B000D000A00200020002000200057004800450052004500200063002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D0020003D0020006B002E00730074006100740065006D0065006E0074005F0074006500780074005F0063006800650063006B00730075006D000D000A0020002000200020004F00520044004500520020004200590020006B002E0069006400200044004500530043000D000A00290020004100530020006B000D000A004F005200440045005200200042005900200063002E0074006F00740061006C005F007200650063006F006D00700069006C0065007300200044004500530043003B000D000A00
        WHERE @compile_events = 1;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_WaitsByDatabase', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005700610069007400730042007900440061007400610062006100730065000D000A00410053000D000A0057004900540048000D000A002000200020002000770061006900740073002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A002000200020002000200020002000200077006100690074005F007000610074007400650072006E0020003D0020004E00270074006F00740061006C0020007700610069007400730020006200790020006400610074006100620061007300650027002C000D000A00200020002000200020002000200020006D0069006E005F006500760065006E0074005F00740069006D00650020003D0020004D0049004E002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A00200020002000200020002000200020006D00610078005F006500760065006E0074005F00740069006D00650020003D0020004D00410058002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A0020002000200020002000200020002000770061002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000770061002E0077006100690074005F0074007900700065002C000D000A002000200020002000200020002000200074006F00740061006C005F007700610069007400730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A0020002000200020002000200020002000730075006D005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020002000200020002000730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F006D0073005F007000650072005F00770061006900740020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D007300290020002F00200043004F0055004E0054005F0042004900470028002A0029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D002000410053002000770061000D000A002000200020002000470052004F00550050002000420059000D000A0020002000200020002000200020002000770061002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000770061002E0077006100690074005F0074007900700065000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000770061006900740073002E0077006100690074005F007000610074007400650072006E002C000D000A002000200020002000770061006900740073002E006D0069006E005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000770061006900740073002E006D00610078005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000770061006900740073002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000770061006900740073002E0077006100690074005F0074007900700065002C000D000A002000200020002000770061006900740073002E0074006F00740061006C005F00770061006900740073002C000D000A002000200020002000770061006900740073002E00730075006D005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000770061006900740073002E00730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000770061006900740073002E006100760067005F006D0073005F007000650072005F0077006100690074002C000D000A002000200020002000770061006900740073005F007000650072005F007300650063006F006E00640020003D0020000D000A0020002000200020002000200020002000490053004E0055004C004C00280043004F0055004E0054005F0042004900470028002A00290020002F0020004E0055004C004C004900460028004400410054004500440049004600460028005300450043004F004E0044002C002000770061006900740073002E006D0069006E005F006500760065006E0074005F00740069006D0065002C002000770061006900740073002E006D00610078005F006500760065006E0074005F00740069006D00650029002C002000300029002C002000300029002C000D000A002000200020002000770061006900740073005F007000650072005F0068006F007500720020003D0020000D000A0020002000200020002000200020002000490053004E0055004C004C00280043004F0055004E0054005F0042004900470028002A00290020002F0020004E0055004C004C0049004600280044004100540045004400490046004600280048004F00550052002C002000770061006900740073002E006D0069006E005F006500760065006E0074005F00740069006D0065002C002000770061006900740073002E006D00610078005F006500760065006E0074005F00740069006D00650029002C002000300029002C002000300029002C000D000A002000200020002000770061006900740073005F007000650072005F0064006100790020003D0020000D000A0020002000200020002000200020002000490053004E0055004C004C00280043004F0055004E0054005F0042004900470028002A00290020002F0020004E0055004C004C004900460028004400410054004500440049004600460028004400410059002C002000770061006900740073002E006D0069006E005F006500760065006E0074005F00740069006D0065002C002000770061006900740073002E006D00610078005F006500760065006E0074005F00740069006D00650029002C002000300029002C002000300029000D000A00460052004F004D002000770061006900740073000D000A00470052004F00550050002000420059000D000A002000200020002000770061006900740073002E0077006100690074005F007000610074007400650072006E002C000D000A002000200020002000770061006900740073002E006D0069006E005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000770061006900740073002E006D00610078005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000770061006900740073002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000770061006900740073002E0077006100690074005F0074007900700065002C000D000A002000200020002000770061006900740073002E0074006F00740061006C005F00770061006900740073002C000D000A002000200020002000770061006900740073002E00730075006D005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000770061006900740073002E00730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000770061006900740073002E006100760067005F006D0073005F007000650072005F0077006100690074000D000A004F00520044004500520020004200590020000D000A002000200020002000770061006900740073002E00730075006D005F006400750072006100740069006F006E005F006D007300200044004500530043003B00;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_WaitsByQueryAndDatabase', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0057006100690074007300420079005100750065007200790041006E006400440061007400610062006100730065000D000A00410053000D000A0057004900540048000D000A00200020002000200070006C0061006E005F00770061006900740073002000410053000D000A0028000D000A002000200020002000530045004C004500430054000D000A002000200020002000200020002000200077006100690074005F007000610074007400650072006E0020003D0020004E00270077006100690074007300200062007900200071007500650072007900200061006E00640020006400610074006100620061007300650027002C000D000A00200020002000200020002000200020006D0069006E005F006500760065006E0074005F00740069006D00650020003D0020004D0049004E002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A00200020002000200020002000200020006D00610078005F006500760065006E0074005F00740069006D00650020003D0020004D00410058002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A0020002000200020002000200020002000770061002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000770061002E0077006100690074005F0074007900700065002C000D000A002000200020002000200020002000200074006F00740061006C005F007700610069007400730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A0020002000200020002000200020002000770061002E0070006C0061006E005F00680061006E0064006C0065002C000D000A0020002000200020002000200020002000770061002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A0020002000200020002000200020002000770061002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A0020002000200020002000200020002000730075006D005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020002000200020002000730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730029002C000D000A00200020002000200020002000200020006100760067005F006D0073005F007000650072005F00770061006900740020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D007300290020002F00200043004F0055004E0054005F0042004900470028002A0029000D000A002000200020002000460052004F004D0020005B007200650070006C006100630065005F006D0065005D002000410053002000770061000D000A002000200020002000470052004F00550050002000420059000D000A0020002000200020002000200020002000770061002E00640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020002000200020002000770061002E0077006100690074005F0074007900700065002C000D000A0020002000200020002000200020002000770061002E00710075006500720079005F0068006100730068005F007300690067006E00650064002C000D000A0020002000200020002000200020002000770061002E00710075006500720079005F0070006C0061006E005F0068006100730068005F007300690067006E00650064002C000D000A0020002000200020002000200020002000770061002E0070006C0061006E005F00680061006E0064006C0065000D000A0029000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A002000200020002000700077002E0077006100690074005F007000610074007400650072006E002C000D000A002000200020002000700077002E006D0069006E005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000700077002E006D00610078005F006500760065006E0074005F00740069006D0065002C000D000A002000200020002000700077002E00640061007400610062006100730065005F006E0061006D0065002C000D000A002000200020002000700077002E0077006100690074005F0074007900700065002C000D000A002000200020002000700077002E0074006F00740061006C005F00770061006900740073002C000D000A002000200020002000700077002E00730075006D005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000700077002E00730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D0073002C000D000A002000200020002000700077002E006100760067005F006D0073005F007000650072005F0077006100690074002C000D000A002000200020002000730074002E0074006500780074002C000D000A002000200020002000710070002E00710075006500720079005F0070006C0061006E000D000A00460052004F004D00200070006C0061006E005F00770061006900740073002000410053002000700077000D000A004F00550054004500520020004100500050004C00590020007300790073002E0064006D005F0065007800650063005F00710075006500720079005F0070006C0061006E002800700077002E0070006C0061006E005F00680061006E0064006C00650029002000410053002000710070000D000A004F00550054004500520020004100500050004C00590020007300790073002E0064006D005F0065007800650063005F00730071006C005F0074006500780074002800700077002E0070006C0061006E005F00680061006E0064006C00650029002000410053002000730074000D000A004F00520044004500520020004200590020000D000A002000200020002000700077002E00730075006D005F006400750072006100740069006F006E005F006D007300200044004500530043003B00;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_WaitsTotal', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005700610069007400730054006F00740061006C000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A00200020002000200077006100690074005F007000610074007400650072006E0020003D0020004E00270074006F00740061006C0020007700610069007400730027002C000D000A0020002000200020006D0069006E005F006500760065006E0074005F00740069006D00650020003D0020004D0049004E002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A0020002000200020006D00610078005F006500760065006E0074005F00740069006D00650020003D0020004D00410058002800770061002E006500760065006E0074005F00740069006D00650029002C000D000A002000200020002000770061002E0077006100690074005F0074007900700065002C000D000A00200020002000200074006F00740061006C005F007700610069007400730020003D00200043004F0055004E0054005F0042004900470028002A0029002C000D000A002000200020002000730075006D005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D00730029002C000D000A002000200020002000730075006D005F007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730020003D002000530055004D002800770061002E007300690067006E0061006C005F006400750072006100740069006F006E005F006D00730029002C000D000A0020002000200020006100760067005F006D0073005F007000650072005F00770061006900740020003D002000530055004D002800770061002E006400750072006100740069006F006E005F006D007300290020002F00200043004F0055004E0054005F0042004900470028002A0029000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D002000410053002000770061000D000A00470052004F005500500020004200590020000D000A002000200020002000770061002E0077006100690074005F0074007900700065000D000A004F00520044004500520020004200590020000D000A002000200020002000730075006D005F006400750072006100740069006F006E005F006D007300200044004500530043003B00;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Compiles_Legacy', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F0043006F006D00700069006C00650073005F004C00650067006100630079000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006500760065006E0074005F00740069006D0065002C000D000A0020002000200020006500760065006E0074005F0074007900700065002C000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D0065002C000D000A002000200020002000730074006100740065006D0065006E0074005F0074006500780074000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A004F00520044004500520020004200590020000D000A0020002000200020006500760065006E0074005F00740069006D0065003B00
        WHERE @compile_events = 0;
        INSERT #view_check (view_name, view_definition)
        SELECT N'HumanEvents_Recompiles_Legacy', 0x430052004500410054004500200056004900450057002000640062006F002E00480075006D0061006E004500760065006E00740073005F005200650063006F006D00700069006C00650073005F004C00650067006100630079000D000A00410053000D000A00530045004C00450043005400200054004F00500020002800320031003400370034003800330036003400380029000D000A0020002000200020006500760065006E0074005F00740069006D0065002C000D000A0020002000200020006500760065006E0074005F0074007900700065002C000D000A002000200020002000640061007400610062006100730065005F006E0061006D0065002C000D000A0020002000200020006F0062006A006500630074005F006E0061006D0065002C000D000A0020002000200020007200650063006F006D00700069006C0065005F00630061007500730065002C000D000A002000200020002000730074006100740065006D0065006E0074005F0074006500780074000D000A00460052004F004D0020005B007200650070006C006100630065005F006D0065005D000D000A004F00520044004500520020004200590020000D000A0020002000200020006500760065006E0074005F00740069006D0065003B00
        WHERE @compile_events = 0;

        IF @debug = 1 BEGIN RAISERROR(N'Updating #view_check with output database (%s) and schema (%s)', 0, 1, @output_database_name, @output_schema_name) WITH NOWAIT; END;
        UPDATE
            #view_check
        SET
            output_database = @output_database_name,
            output_schema = @output_schema_name;

        IF @debug = 1 BEGIN RAISERROR(N'Updating #view_check with table names', 0, 1) WITH NOWAIT; END;
        UPDATE
            vc
        SET
            vc.output_table = hew.output_table
        FROM #view_check AS vc
        JOIN #human_events_worker AS hew
          ON  vc.view_name LIKE N'%' + hew.event_type_short + N'%'
          AND hew.is_table_created = 1
          AND hew.is_view_created = 0;

        UPDATE
            vc
        SET
            vc.output_table = hew.output_table + N'_parameterization'
        FROM #view_check AS vc
        JOIN #human_events_worker AS hew
          ON  vc.view_name = N'HumanEvents_Parameterization'
          AND hew.output_table LIKE N'keeper_HumanEvents_compiles%'
          AND hew.is_table_created = 1
          AND hew.is_view_created = 0;

        IF @debug = 1 BEGIN SELECT N'#view_check' AS table_name, * FROM #view_check AS vc; END;
    END;

    IF
    (
           @view_tracker IS NULL
        OR @view_tracker = 0
    )
    BEGIN
        IF @debug = 1 BEGIN RAISERROR(N'Starting view creation loop', 0, 1) WITH NOWAIT; END;

        SELECT
           @min_id = MIN(vc.id),
           @max_id = MAX(vc.id)
        FROM #view_check AS vc
        WHERE vc.output_table <> N''
        AND   EXISTS
        (
            SELECT
                1/0
            FROM #human_events_worker AS hew
            WHERE vc.view_name LIKE N'%' + hew.event_type_short + N'%'
            AND   hew.is_table_created = 1
            AND   hew.is_view_created = 0
        );

        WHILE @min_id <= @max_id
        BEGIN
            SELECT
                @event_type_check  =
                    LOWER(vc.view_name),
                @object_name_check =
                    QUOTENAME(vc.output_database) +
                    N'.' +
                    QUOTENAME(vc.output_schema) +
                    N'.' +
                    QUOTENAME(vc.view_name),
                @view_database =
                    QUOTENAME(vc.output_database),
                @view_sql =
                    REPLACE
                    (
                        REPLACE
                        (
                            REPLACE
                            (
                                vc.view_converted,
                                N'[replace_me]',
                                QUOTENAME(vc.output_schema)  +
                                N'.'  +
                                vc.output_table
                            ),
                            N'[dbo]' +
                            '.' +
                            QUOTENAME(vc.view_name),
                            QUOTENAME(vc.output_schema) +
                            '.' +
                            QUOTENAME(vc.view_name)
                        ),
                        N'',
                        N''''
                    )
            FROM #view_check AS vc
            WHERE vc.id = @min_id
            AND   vc.output_table <> N'';

            IF OBJECT_ID(@object_name_check) IS NOT NULL
            BEGIN
              IF @debug = 1 BEGIN RAISERROR(N'Uh oh, found a view', 0, 1) WITH NOWAIT; END;
              SET
                  @view_sql =
                      REPLACE
                      (
                          @view_sql,
                          N'CREATE VIEW',
                          N'ALTER VIEW'
                      );
            END;

            SELECT
                @spe =
                    @view_database +
                    @spe;

            IF @debug = 1 BEGIN RAISERROR(@spe, 0, 1) WITH NOWAIT; END;

            IF @debug = 1
            BEGIN
                PRINT SUBSTRING(@view_sql, 0,     4000);
                PRINT SUBSTRING(@view_sql, 4001,  8000);
                PRINT SUBSTRING(@view_sql, 8001,  12000);
                PRINT SUBSTRING(@view_sql, 12001, 16000);
                PRINT SUBSTRING(@view_sql, 16001, 20000);
                PRINT SUBSTRING(@view_sql, 20001, 24000);
                PRINT SUBSTRING(@view_sql, 24001, 28000);
                PRINT SUBSTRING(@view_sql, 28001, 32000);
                PRINT SUBSTRING(@view_sql, 32001, 36000);
                PRINT SUBSTRING(@view_sql, 36001, 40000);
            END;

            IF @debug = 1 BEGIN RAISERROR(N'creating view %s', 0, 1, @event_type_check) WITH NOWAIT; END;
            EXECUTE @spe @view_sql;

            IF @debug = 1
            BEGIN
                RAISERROR(N'@min_id: %i', 0, 1, @min_id) WITH NOWAIT;
                RAISERROR(N'Setting next id after %i out of %i total', 0, 1, @min_id, @max_id) WITH NOWAIT;
            END;

            SET @min_id =
            (
                SELECT TOP (1)
                    vc.id
                FROM #view_check AS vc
                WHERE vc.id > @min_id
                AND   vc.output_table <> N''
                ORDER BY
                    vc.id
            );

            IF @debug = 1 BEGIN RAISERROR(N'new @min_id: %i', 0, 1, @min_id) WITH NOWAIT; END;

            IF @min_id IS NULL BREAK;

            SET @spe = N'.sys.sp_executesql ';
        END;

        UPDATE
            #human_events_worker
        SET
            is_view_created = 1;

        SET @view_tracker = 1;
    END;
END;

    /*This section handles inserting data into tables*/
    IF EXISTS
    (
        SELECT
            1/0
        FROM #human_events_worker AS hew
        WHERE hew.is_table_created = 1
        AND   hew.last_checked < DATEADD(SECOND, -5, SYSDATETIME())
    )
    BEGIN
        IF @debug = 1 BEGIN RAISERROR(N'Sessions that need data found, starting loop.', 0, 1) WITH NOWAIT; END;

        SELECT
            @min_id = MIN(hew.id),
            @max_id = MAX(hew.id)
        FROM #human_events_worker AS hew
        WHERE hew.is_table_created = 1;

        WHILE @min_id <= @max_id
        BEGIN
            SELECT
                @event_type_check  =
                    hew.event_type,
                @object_name_check =
                    QUOTENAME(hew.output_database) +
                    N'.' +
                    QUOTENAME(hew.output_schema) +
                    N'.' +
                    QUOTENAME(hew.output_table),
                @date_filter =
                    DATEADD
                    (
                        MINUTE,
                        DATEDIFF
                        (
                            MINUTE,
                            SYSDATETIME(),
                            GETUTCDATE()
                        ),
                        hew.last_checked
                    )
            FROM #human_events_worker AS hew
            WHERE hew.id = @min_id
            AND   hew.is_table_created = 1;

            IF OBJECT_ID(@object_name_check) IS NOT NULL
            BEGIN
                IF @debug = 1 BEGIN RAISERROR(N'Generating insert table statement for %s', 0, 1, @event_type_check) WITH NOWAIT; END;
                SELECT
                    @table_sql = CONVERT
                                 (
                                     nvarchar(max),
                        CASE
                        WHEN @event_type_check LIKE N'%wait%' /*Wait stats!*/
                        THEN CONVERT
                             (
                                 nvarchar(max),
                             N'INSERT INTO ' + @object_name_check + N' WITH(TABLOCK) ' + @nc10 +
                             N'( server_name, event_time, event_type, database_name, wait_type, duration_ms, ' + @nc10 +
                             N'  signal_duration_ms, wait_resource,  query_plan_hash_signed, query_hash_signed, plan_handle )' + @nc10 +
                             N'SELECT
        server_name = @@SERVERNAME,
        event_time =
            DATEADD
            (
                MINUTE,
                DATEDIFF
                (
                     MINUTE,
                     GETUTCDATE(),
                     SYSDATETIME()
                 ),
                 c.value(''@timestamp'', ''datetime2'')
            ),
        event_type = c.value(''@name'', ''sysname''),
        database_name = c.value(''(action[@name="database_name"]/value/text())[1]'', ''sysname''),
        wait_type = c.value(''(data[@name="wait_type"]/text)[1]'', ''nvarchar(60)''),
        duration_ms = c.value(''(data[@name="duration"]/value/text())[1]'', ''bigint''),
        signal_duration_ms = c.value(''(data[@name="signal_duration"]/value/text())[1]'', ''bigint''),' + @nc10 +
CONVERT
(
    nvarchar(max),
CASE
    WHEN @v = 11 /*We can't get the wait resource on older versions of SQL Server*/
    THEN N'        ''Not Available < 2014'', ' + @nc10
    ELSE N'        wait_resource = c.value(''(data[@name="wait_resource"]/value/text())[1]'', ''sysname''), ' + @nc10
END
) + CONVERT(nvarchar(max), N'        query_plan_hash_signed =
                CONVERT
                (
                    binary(8),
                    c.value(''(action[@name="query_plan_hash_signed"]/value/text())[1]'', ''bigint'')
                ),
        query_hash_signed =
            CONVERT
            (
                binary(8),
                c.value(''(action[@name="query_hash_signed"]/value/text())[1]'', ''bigint'')
            ),
        plan_handle = c.value(''xs:hexBinary((action[@name="plan_handle"]/value/text())[1])'', ''varbinary(64)'')
FROM #human_events_xml_internal AS xet
OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
WHERE c.exist(''(data[@name="duration"]/value/text()[. > 0])'') = 1
AND   c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1;')
                             )
                        WHEN @event_type_check LIKE N'%lock%' /*Blocking!*/
                                                              /*To cut down on nonsense, I'm only inserting new blocking scenarios*/
                                                              /*Any existing blocking scenarios will update the blocking duration*/
                        THEN CONVERT
                             (
                                 nvarchar(max),
                             N'INSERT INTO ' + @object_name_check + N' WITH(TABLOCK) ' + @nc10 +
                             N'( server_name, event_time, activity, database_name, database_id, object_id, ' + @nc10 +
                             N'  transaction_id, resource_owner_type, monitor_loop, spid, ecid, query_text, wait_time, ' + @nc10 +
                             N'  transaction_name,  last_transaction_started, wait_resource, lock_mode, status, priority, ' + @nc10 +
                             N'  transaction_count, client_app, host_name, login_name, isolation_level, sql_handle, blocked_process_report )' + @nc10 +
CONVERT(nvarchar(max), N'
SELECT server_name, event_time, activity, database_name, database_id, object_id,
       transaction_id, resource_owner_type, monitor_loop, spid, ecid, text, waittime,
       transactionname,  lasttranstarted, wait_resource, lockmode, status, priority,
       trancount, clientapp, hostname, loginname, isolationlevel, sqlhandle, process_report
FROM
(
    SELECT
        x.*,
        x =
            ROW_NUMBER()
                OVER
                (
                    PARTITION BY
                        x.spid,
                        x.ecid,
                        x.transaction_id,
                        x.activity
                    ORDER BY
                        x.spid,
                        x.ecid,
                        x.transaction_id,
                        x.activity
                )
    FROM
    (
        SELECT
            server_name = @@SERVERNAME,
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ), oa.c.value(''@timestamp'', ''datetime2'')
                ),
            activity = ''blocked'',
            database_name = DB_NAME(oa.c.value(''(data[@name="database_id"]/value/text())[1]'', ''integer'')),
            database_id = oa.c.value(''(data[@name="database_id"]/value/text())[1]'', ''integer''),
            object_id = oa.c.value(''(data[@name="object_id"]/value/text())[1]'', ''integer''),
            transaction_id = oa.c.value(''(data[@name="transaction_id"]/value/text())[1]'', ''bigint''),
            resource_owner_type = oa.c.value(''(data[@name="resource_owner_type"]/text)[1]'', ''sysname''),
            monitor_loop = oa.c.value(''(//@monitorLoop)[1]'', ''integer''),
            spid = bd.value(''(process/@spid)[1]'', ''integer''),
            ecid = bd.value(''(process/@ecid)[1]'', ''integer''),
            text = bd.value(''(process/inputbuf/text())[1]'', ''nvarchar(max)''),
            waittime = bd.value(''(process/@waittime)[1]'', ''bigint''),
            transactionname = bd.value(''(process/@transactionname)[1]'', ''sysname''),
            lasttranstarted = bd.value(''(process/@lasttranstarted)[1]'', ''datetime2''),
            wait_resource = bd.value(''(process/@waitresource)[1]'', ''nvarchar(100)''),
            lockmode = bd.value(''(process/@lockMode)[1]'', ''nvarchar(10)''),
            status = bd.value(''(process/@status)[1]'', ''nvarchar(10)''),
            priority = bd.value(''(process/@priority)[1]'', ''integer''),
            trancount = bd.value(''(process/@trancount)[1]'', ''integer''),
            clientapp = bd.value(''(process/@clientapp)[1]'', ''sysname''),
            hostname = bd.value(''(process/@hostname)[1]'', ''sysname''),
            loginname = bd.value(''(process/@loginname)[1]'', ''sysname''),
            isolationlevel = bd.value(''(process/@isolationlevel)[1]'', ''nvarchar(50)''),
            sqlhandle =
                CONVERT
                (
                    varbinary(64),
                    bd.value(''(process/executionStack/frame/@sqlhandle)[1]'', ''nvarchar(260)''),
                    1
                ) AS sqlhandle,
            process_report = oa.c.query(''.'')
        FROM #human_events_xml_internal AS xet
        OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
        OUTER APPLY oa.c.nodes(''//blocked-process-report/blocked-process'') AS bd(bd)
        WHERE oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1

        UNION ALL

        SELECT
            server_name = @@SERVERNAME,
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    oa.c.value(''@timestamp'', ''datetime2'')
                ),
            activity = ''blocking'',
            database_name = DB_NAME(oa.c.value(''(data[@name="database_id"]/value/text())[1]'', ''integer'')),
            database_id = oa.c.value(''(data[@name="database_id"]/value/text())[1]'', ''integer''),
            object_id = oa.c.value(''(data[@name="object_id"]/value/text())[1]'', ''integer''),
            transaction_id = oa.c.value(''(data[@name="transaction_id"]/value/text())[1]'', ''bigint''),
            resource_owner_type = oa.c.value(''(data[@name="resource_owner_type"]/text)[1]'', ''sysname''),
            monitor_loop = oa.c.value(''(//@monitorLoop)[1]'', ''integer''),
            spid = bg.value(''(process/@spid)[1]'', ''integer''),
            ecid = bg.value(''(process/@ecid)[1]'', ''integer''),
            text = bg.value(''(process/inputbuf/text())[1]'', ''nvarchar(max)''),
            waittime = NULL,
            transactionname = NULL,
            lasttranstarted = NULL,
            wait_resource = NULL,
            lockmode = NULL,
            status = bg.value(''(process/@status)[1]'', ''nvarchar(10)''),
            priority = bg.value(''(process/@priority)[1]'', ''integer''),
            trancount = bg.value(''(process/@trancount)[1]'', ''integer''),
            clientapp = bg.value(''(process/@clientapp)[1]'', ''sysname''),
            hostname = bg.value(''(process/@hostname)[1]'', ''sysname''),
            loginname = bg.value(''(process/@loginname)[1]'', ''sysname''),
            isolationlevel = bg.value(''(process/@isolationlevel)[1]'', ''nvarchar(50)''),
            sqlhandle = NULL,
            process_report = oa.c.query(''.'')
        FROM #human_events_xml_internal AS xet
        OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
        OUTER APPLY oa.c.nodes(''//blocked-process-report/blocking-process'') AS bg(bg)
        WHERE oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
    ) AS x
) AS x
WHERE NOT EXISTS
(
    SELECT
        1/0
    FROM ' + @object_name_check + N' AS x2
    WHERE x.database_id = x2.database_id
    AND   x.object_id = x2.object_id
    AND   x.transaction_id = x2.transaction_id
    AND   x.spid = x2.spid
    AND   x.ecid = x2.ecid
    AND   x.clientapp = x2.client_app
    AND   x.hostname = x2.host_name
    AND   x.loginname = x2.login_name
)
AND x.x = 1;

UPDATE x2
    SET
        x2.wait_time = x.waittime
FROM ' + @object_name_check + N' AS x2
JOIN
(
    SELECT
        server_name = @@SERVERNAME,
        activity = ''blocked'',
        database_id = oa.c.value(''(data[@name="database_id"]/value/text())[1]'', ''integer''),
        object_id = oa.c.value(''(data[@name="object_id"]/value/text())[1]'', ''integer''),
        transaction_id = oa.c.value(''(data[@name="transaction_id"]/value/text())[1]'', ''bigint''),
        monitor_loop = oa.c.value(''(//@monitorLoop)[1]'', ''integer''),
        spid = bd.value(''(process/@spid)[1]'', ''integer''),
        ecid = bd.value(''(process/@ecid)[1]'', ''integer''),
        waittime = bd.value(''(process/@waittime)[1]'', ''bigint''),
        clientapp = bd.value(''(process/@clientapp)[1]'', ''sysname''),
        hostname = bd.value(''(process/@hostname)[1]'', ''sysname''),
        loginname = bd.value(''(process/@loginname)[1]'', ''sysname'')
    FROM #human_events_xml_internal AS xet
    OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
    OUTER APPLY oa.c.nodes(''//blocked-process-report/blocked-process'') AS bd(bd)
    WHERE oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
) AS x
    ON    x.database_id = x2.database_id
    AND   x.object_id = x2.object_id
    AND   x.transaction_id = x2.transaction_id
    AND   x.spid = x2.spid
    AND   x.ecid = x2.ecid
    AND   x.clientapp = x2.client_app
    AND   x.hostname = x2.host_name
    AND   x.loginname = x2.login_name;
'                                ))
                       WHEN @event_type_check LIKE N'%quer%' /*Queries!*/
                       THEN
                            CONVERT
                            (
                                nvarchar(max),
                            N'INSERT INTO ' + @object_name_check + N' WITH(TABLOCK) ' + @nc10 +
                            N'( server_name, event_time, event_type, database_name, object_name, sql_text, statement, ' + @nc10 +
                            N'  showplan_xml, cpu_ms, logical_reads, physical_reads, duration_ms, writes_mb, ' + @nc10 +
                            N'  spills_mb, row_count, estimated_rows, dop,  serial_ideal_memory_mb, ' + @nc10 +
                            N'  requested_memory_mb, used_memory_mb, ideal_memory_mb, granted_memory_mb, ' + @nc10 +
                            N'  query_plan_hash_signed, query_hash_signed, plan_handle )' + @nc10 +
                            CONVERT(nvarchar(max), N'SELECT
    server_name = @@SERVERNAME,
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ),
            oa.c.value(''@timestamp'', ''datetime2'')
        ),
    event_type = oa.c.value(''@name'', ''sysname''),
    database_name = oa.c.value(''(action[@name="database_name"]/value/text())[1]'', ''sysname''),
    [object_name] = oa.c.value(''(data[@name="object_name"]/value/text())[1]'', ''sysname''),
    sql_text = oa.c.value(''(action[@name="sql_text"]/value/text())[1]'', ''nvarchar(max)''),
    statement = oa.c.value(''(data[@name="statement"]/value/text())[1]'', ''nvarchar(max)''),
    [showplan_xml] = oa.c.query(''(data[@name="showplan_xml"]/value/*)[1]''),
    cpu_ms = oa.c.value(''(data[@name="cpu_time"]/value/text())[1]'', ''bigint'') / 1000.,
    logical_reads = (oa.c.value(''(data[@name="logical_reads"]/value/text())[1]'', ''bigint'') * 8) / 1024.,
    physical_reads = (oa.c.value(''(data[@name="physical_reads"]/value/text())[1]'', ''bigint'') * 8) / 1024.,
    duration_ms = oa.c.value(''(data[@name="duration"]/value/text())[1]'', ''bigint'') / 1000.,
    writes_mb = (oa.c.value(''(data[@name="writes"]/value/text())[1]'', ''bigint'') * 8) / 1024.,
    spills_mb = (oa.c.value(''(data[@name="spills"]/value/text())[1]'', ''bigint'') * 8) / 1024.,
    row_count = oa.c.value(''(data[@name="row_count"]/value/text())[1]'', ''bigint''),
    estimated_rows = oa.c.value(''(data[@name="estimated_rows"]/value/text())[1]'', ''bigint''),
    dop = oa.c.value(''(data[@name="dop"]/value/text())[1]'', ''integer''),
    serial_ideal_memory_mb = oa.c.value(''(data[@name="serial_ideal_memory_kb"]/value/text())[1]'', ''bigint'') / 1024.,
    requested_memory_mb = oa.c.value(''(data[@name="requested_memory_kb"]/value/text())[1]'', ''bigint'') / 1024.,
    used_memory_mb = oa.c.value(''(data[@name="used_memory_kb"]/value/text())[1]'', ''bigint'') / 1024.,
    ideal_memory_mb = oa.c.value(''(data[@name="ideal_memory_kb"]/value/text())[1]'', ''bigint'') / 1024.,
    granted_memory_mb = oa.c.value(''(data[@name="granted_memory_kb"]/value/text())[1]'', ''bigint'') / 1024.,
    query_plan_hash_signed =
        CONVERT
        (
            binary(8),
            oa.c.value(''(action[@name="query_plan_hash_signed"]/value/text())[1]'', ''bigint'')
        ),
    query_hash_signed =
        CONVERT
        (
            binary(8),
            oa.c.value(''(action[@name="query_hash_signed"]/value/text())[1]'', ''bigint'')
        ),
    plan_handle = oa.c.value(''xs:hexBinary((action[@name="plan_handle"]/value/text())[1])'', ''varbinary(64)'')
FROM #human_events_xml_internal AS xet
OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
WHERE oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
AND   oa.c.exist(''(action[@name="query_hash_signed"]/value[. != 0])'') = 1; '
                            ))
                       WHEN @event_type_check LIKE N'%recomp%' /*Recompiles!*/
                       THEN
                            CONVERT
                            (
                                nvarchar(max),
                            N'INSERT INTO ' + @object_name_check + N' WITH(TABLOCK) ' + @nc10 +
                            N'( server_name, event_time,  event_type,  ' + @nc10 +
                            N'  database_name, object_name, recompile_cause, statement_text '
                            + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 THEN N', compile_cpu_ms, compile_duration_ms )' ELSE N' )' END) + @nc10 +
                            CONVERT(nvarchar(max), N'SELECT
    server = @@SERVERNAME,
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ), oa.c.value(''@timestamp'', ''datetime2'')
        ),
    event_type = oa.c.value(''@name'', ''sysname''),
    database_name = oa.c.value(''(action[@name="database_name"]/value/text())[1]'', ''sysname''),
    [object_name] = oa.c.value(''(data[@name="object_name"]/value/text())[1]'', ''sysname''),
    recompile_cause = oa.c.value(''(data[@name="recompile_cause"]/text)[1]'', ''sysname''),
    statement_text = oa.c.value(''(data[@name="statement"]/value/text())[1]'', ''nvarchar(max)'')'
   + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 /*Only get these columns if we're using the newer XE: sql_statement_post_compile*/
          THEN
   N'  ,
    compile_cpu_ms = oa.c.value(''(data[@name="cpu_time"]/value/text())[1]'', ''bigint''),
    compile_duration_ms = oa.c.value(''(data[@name="duration"]/value/text())[1]'', ''bigint'')'
          ELSE N''
     END) + N'
FROM #human_events_xml_internal AS xet
OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
WHERE 1 = 1 '
      + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 /*Same here, where we need to filter data*/
             THEN
N'
AND oa.c.exist(''(data[@name="is_recompile"]/value[. = "false"])'') = 0 '
             ELSE N''
        END) + CONVERT(nvarchar(max), N'
AND oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
ORDER BY
    event_time;'
                            )))
                       WHEN @event_type_check LIKE N'%comp%' AND @event_type_check NOT LIKE N'%re%' /*Compiles!*/
                       THEN
                            CONVERT
                            (
                                nvarchar(max),
                            N'INSERT INTO ' + REPLACE(@object_name_check, N'_parameterization', N'') + N' WITH(TABLOCK) ' + @nc10 +
                            N'( server_name, event_time,  event_type,  ' + @nc10 +
                            N'  database_name, object_name, statement_text '
                            + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 THEN N', compile_cpu_ms, compile_duration_ms )' ELSE N' )' END) + @nc10 +
                            CONVERT(nvarchar(max), N'SELECT
    server_name = @@SERVERNAME,
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ),
            oa.c.value(''@timestamp'', ''datetime2'')
        ),
    event_type = oa.c.value(''@name'', ''sysname''),
    database_name = oa.c.value(''(action[@name="database_name"]/value/text())[1]'', ''sysname''),
    [object_name] = oa.c.value(''(data[@name="object_name"]/value/text())[1]'', ''sysname''),
    statement_text = oa.c.value(''(data[@name="statement"]/value/text())[1]'', ''nvarchar(max)'')'
   + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 /*Only get these columns if we're using the newer XE: sql_statement_post_compile*/
          THEN
   N'  ,
    compile_cpu_ms = oa.c.value(''(data[@name="cpu_time"]/value/text())[1]'', ''bigint''),
    compile_duration_ms = oa.c.value(''(data[@name="duration"]/value/text())[1]'', ''bigint'')'
          ELSE N''
     END) + N'
FROM #human_events_xml_internal AS xet
OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
WHERE 1 = 1 '
      + CONVERT(nvarchar(max), CASE WHEN @compile_events = 1 /*Just like above*/
             THEN
N'
AND oa.c.exist(''(data[@name="is_recompile"]/value[. = "false"])'') = 1 '
             ELSE N''
        END) + CONVERT(nvarchar(max), N'
AND   oa.c.exist(''@name[.= "sql_statement_post_compile"]'') = 1
AND   oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
ORDER BY
    event_time;' + @nc10
        )))
                            + CASE WHEN @parameterization_events = 1 /*The query_parameterization_data XE is only 2017+*/
                                   THEN
                            @nc10 +
                                CONVERT
                                (
                                    nvarchar(max),
                            N'INSERT INTO ' + REPLACE(@object_name_check, N'_parameterization', N'') + N'_parameterization' + N' WITH(TABLOCK) ' + @nc10 +
                            N'( server_name, event_time,  event_type, database_name, sql_text, compile_cpu_time_ms, ' + @nc10 +
                            N'  compile_duration_ms, query_param_type, is_cached, is_recompiled, compile_code, has_literals, ' + @nc10 +
                            N'  is_parameterizable, parameterized_values_count, query_plan_hash, query_hash, plan_handle, statement_sql_hash ) ' + @nc10 +
                            CONVERT(nvarchar(max), N'SELECT
    server_name = @@SERVERNAME,
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ),
            oa.c.value(''@timestamp'', ''datetime2'')
        ),
    event_type = oa.c.value(''@name'', ''sysname''),
    database_name = oa.c.value(''(action[@name="database_name"]/value/text())[1]'', ''sysname''),
    sql_text = oa.c.value(''(action[@name="sql_text"]/value/text())[1]'', ''nvarchar(max)''),
    compile_cpu_time_ms = oa.c.value(''(data[@name="compile_cpu_time"]/value/text())[1]'', ''bigint'') / 1000.,
    compile_duration_ms = oa.c.value(''(data[@name="compile_duration"]/value/text())[1]'', ''bigint'') / 1000.,
    query_param_type = oa.c.value(''(data[@name="query_param_type"]/value/text())[1]'', ''integer''),
    is_cached = oa.c.value(''(data[@name="is_cached"]/value/text())[1]'', ''bit''),
    is_recompiled = oa.c.value(''(data[@name="is_recompiled"]/value/text())[1]'', ''bit''),
    compile_code = oa.c.value(''(data[@name="compile_code"]/text)[1]'', ''sysname''),
    has_literals = oa.c.value(''(data[@name="has_literals"]/value/text())[1]'', ''bit''),
    is_parameterizable = oa.c.value(''(data[@name="is_parameterizable"]/value/text())[1]'', ''bit''),
    parameterized_values_count = oa.c.value(''(data[@name="parameterized_values_count"]/value/text())[1]'', ''bigint''),
    query_plan_hash = oa.c.value(''xs:hexBinary((data[@name="query_plan_hash"]/value/text())[1])'', ''binary(8)''),
    query_hash = oa.c.value(''xs:hexBinary((data[@name="query_hash"]/value/text())[1])'', ''binary(8)''),
    plan_handle = oa.c.value(''xs:hexBinary((action[@name="plan_handle"]/value/text())[1])'', ''varbinary(64)''),
    statement_sql_hash = oa.c.value(''xs:hexBinary((data[@name="statement_sql_hash"]/value/text())[1])'', ''varbinary(64)'')
FROM #human_events_xml_internal AS xet
OUTER APPLY xet.human_events_xml.nodes(''//event'') AS oa(c)
WHERE oa.c.exist(''@name[.= "query_parameterization_data"]'') = 1
AND   oa.c.exist(''(data[@name="is_recompiled"]/value[. = "false"])'') = 1
AND   oa.c.exist(''@timestamp[. > sql:variable("@date_filter")]'') = 1
ORDER BY
    event_time;'))
                                   ELSE N''
                              END
                       ELSE N''
                  END
                  );

            /* this table is only used for the inserts, hence the "internal" in the name */
            IF LOWER(@target_output) = N'ring_buffer'
            BEGIN
                IF @azure = 0
                BEGIN
                    INSERT
                        #x WITH(TABLOCK)
                    (
                        x
                    )
                    SELECT
                        x =
                            CONVERT
                            (
                                xml,
                                t.target_data
                            )
                    FROM sys.dm_xe_session_targets AS t
                    JOIN sys.dm_xe_sessions AS s
                      ON s.address = t.event_session_address
                    WHERE s.name = @event_type_check
                    AND   t.target_name = N'ring_buffer';
                END;
                ELSE
                BEGIN
                    INSERT
                        #x WITH(TABLOCK)
                    (
                        x
                    )
                    SELECT
                        x =
                            CONVERT
                            (
                                xml,
                                t.target_data
                            )
                    FROM sys.dm_xe_database_session_targets AS t
                    JOIN sys.dm_xe_database_sessions AS s
                      ON s.address = t.event_session_address
                    WHERE s.name = @event_type_check
                    AND   t.target_name = N'ring_buffer';
                END;
            END;
            ELSE IF LOWER(@target_output) = N'event_file'
            BEGIN
                /*
                Read from event file target
                Azure SQL Database and Managed Instance are not supported for event_file
                */
                INSERT
                    #x WITH(TABLOCK)
                (
                    x
                )
                SELECT
                    x =
                        CONVERT
                        (
                            xml,
                            f.event_data
                        )
                FROM sys.fn_xe_file_target_read_file
                (
                    @event_type_check + N'*.xel',
                    NULL,
                    NULL,
                    NULL
                ) AS f;
            END;

            /*
            Parse XML events based on target output type
            ring_buffer wraps events in RingBufferTarget node, event_file does not
            */
            IF LOWER(@target_output) = N'ring_buffer'
            BEGIN
                INSERT
                    #human_events_xml_internal WITH(TABLOCK)
                (
                    human_events_xml
                )
                SELECT
                    human_events_xml =
                        e.x.query('.')
                    FROM #x AS x
                CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x)
                WHERE e.x.exist('@timestamp[. > sql:variable("@date_filter")]') = 1;
            END;
            ELSE IF LOWER(@target_output) = N'event_file'
            BEGIN
                INSERT
                    #human_events_xml_internal WITH(TABLOCK)
                (
                    human_events_xml
                )
                SELECT
                    human_events_xml =
                        e.x.query('.')
                    FROM #x AS x
                CROSS APPLY x.x.nodes('/event') AS e(x)
                WHERE e.x.exist('@timestamp[. > sql:variable("@date_filter")]') = 1;
            END;

            IF @debug = 1
            BEGIN
                PRINT SUBSTRING(@table_sql, 0, 4000);
                PRINT SUBSTRING(@table_sql, 4001, 8000);
                PRINT SUBSTRING(@table_sql, 8001, 12000);
                PRINT SUBSTRING(@table_sql, 12001, 16000);
                PRINT SUBSTRING(@table_sql, 16001, 20000);
                PRINT SUBSTRING(@table_sql, 20001, 24000);
                PRINT SUBSTRING(@table_sql, 24001, 28000);
                PRINT SUBSTRING(@table_sql, 28001, 32000);
                PRINT SUBSTRING(@table_sql, 32001, 36000);
                PRINT SUBSTRING(@table_sql, 36001, 40000);
            END;

            /* this executes the insert */
            EXECUTE sys.sp_executesql
                @table_sql,
              N'@date_filter datetime2(7)',
                @date_filter;

            /*Update the worker table's last checked, and conditionally, updated dates*/
            UPDATE
                hew
            SET
                hew.last_checked =
                    SYSDATETIME(),
                hew.last_updated =
                    CASE
                        WHEN ROWCOUNT_BIG() > 0
                        THEN SYSDATETIME()
                        ELSE hew.last_updated
                    END
            FROM #human_events_worker AS hew
            WHERE hew.id = @min_id;

            IF @debug = 1
            BEGIN
                SELECT N'#human_events_worker' AS table_name, hew.* FROM #human_events_worker AS hew;
                SELECT N'#human_events_xml_internal' AS table_name, hew.* FROM #human_events_xml_internal AS hew;
            END;

            /*Clear the table out between runs*/
            TRUNCATE TABLE #human_events_xml_internal;
            TRUNCATE TABLE #x;

            IF @debug = 1
            BEGIN
                RAISERROR(N'@min_id: %i', 0, 1, @min_id) WITH NOWAIT;
                RAISERROR(N'Setting next id after %i out of %i total', 0, 1, @min_id, @max_id) WITH NOWAIT;
            END;

            SET @min_id =
            (
                SELECT TOP (1)
                    hew.id
                FROM #human_events_worker AS hew
                WHERE hew.id > @min_id
                AND   hew.is_table_created = 1
                ORDER BY
                    hew.id
            );

            IF @debug = 1 BEGIN RAISERROR(N'new @min_id: %i', 0, 1, @min_id) WITH NOWAIT; END;

            IF @min_id IS NULL BREAK;
            END;
        END;
    END;


/*This section handles deleting data from tables older than the retention period*/
/*The idea is to only check once an hour so we're not constantly purging*/
SET @Time = SYSDATETIME();
IF
(
    DATEPART
    (
        MINUTE,
        @Time
    ) <= 5
)
BEGIN
    IF
    (
           @delete_tracker IS NULL
        OR @delete_tracker <> DATEPART(HOUR, @Time)
    )
    BEGIN
        SET @the_deleter_must_awaken = N'';
        SELECT
            @the_deleter_must_awaken +=
                N' DELETE FROM ' +
                QUOTENAME(hew.output_database) +
                N'.' +
                QUOTENAME(hew.output_schema) +
                N'.' +
                QUOTENAME(hew.event_type) +
                N' WHERE event_time < DATEADD
                                      (
                                          DAY,
                                          (-1 * @delete_retention_days),
                                          SYSDATETIME()
                                      ); ' + @nc10
        FROM #human_events_worker AS hew;

        IF @debug = 1 BEGIN RAISERROR(@the_deleter_must_awaken, 0, 1) WITH NOWAIT; END;

        /* execute the delete */
        EXECUTE sys.sp_executesql
            @the_deleter_must_awaken,
          N'@delete_retention_days integer',
            @delete_retention_days;

        /* set this to the hour it was last checked */
        SET @delete_tracker = DATEPART(HOUR, SYSDATETIME());
    END;
END;

/*Wait 5 seconds, then start the output loop again*/
WAITFOR DELAY '00:00:05.000';
END;

/*This section handles cleaning up stuff.*/
cleanup:
BEGIN
    IF @debug = 1 BEGIN RAISERROR(N'CLEAN UP PARTY TONIGHT', 0, 1) WITH NOWAIT; END;

    SET @executer = QUOTENAME(@output_database_name) + N'.sys.sp_executesql ';

    /*Clean up sessions*/
    IF @azure = 0
    BEGIN
        SELECT
            @cleanup_sessions +=
                N'DROP EVENT SESSION ' +
                ses.name +
                N' ON SERVER;' +
                @nc10
        FROM sys.server_event_sessions AS ses
        LEFT JOIN sys.dm_xe_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name LIKE N'%HumanEvents_%';
    END;
    ELSE
    BEGIN
        SELECT
            @cleanup_sessions +=
                N'DROP EVENT SESSION ' +
                ses.name +
                N' ON DATABASE;' +
                @nc10
        FROM sys.database_event_sessions AS ses
        LEFT JOIN sys.dm_xe_database_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name LIKE N'%HumanEvents_%';
    END;

    EXECUTE sys.sp_executesql
        @cleanup_sessions;

    IF @debug = 1 BEGIN RAISERROR(@cleanup_sessions, 0, 1) WITH NOWAIT; END;


    /*Clean up tables*/
    IF @debug = 1 BEGIN RAISERROR(N'CLEAN UP PARTY TONIGHT', 0, 1) WITH NOWAIT; END;

    SELECT
        @cleanup_tables += N'
            SELECT
                @i_cleanup_tables +=
                    N''DROP TABLE '' +
                    QUOTENAME(SCHEMA_NAME(s.schema_id)) +
                    N''.'' +
                    QUOTENAME(s.name) +
                    ''; '' +
                    NCHAR(10)
            FROM ' + QUOTENAME(@output_database_name) + N'.sys.tables AS s
            WHERE s.name LIKE ''' + '%HumanEvents%' + N''';';

    EXECUTE sys.sp_executesql
        @cleanup_tables,
      N'@i_cleanup_tables nvarchar(max) OUTPUT',
        @i_cleanup_tables = @drop_holder OUTPUT;

    IF @debug = 1
    BEGIN
        RAISERROR(@executer, 0, 1) WITH NOWAIT;
        RAISERROR(@drop_holder, 0, 1) WITH NOWAIT;
    END;

    EXECUTE @executer @drop_holder;

    /*Cleanup views*/
    IF @debug = 1 BEGIN RAISERROR(N'CLEAN UP PARTY TONIGHT', 0, 1) WITH NOWAIT; END;

    SET @drop_holder = N'';

    SELECT
        @cleanup_views += N'
            SELECT
                @i_cleanup_views +=
                    N''DROP VIEW '' +
                    QUOTENAME(SCHEMA_NAME(v.schema_id)) +
                    N''.'' +
                    QUOTENAME(v.name) +
                    ''; '' +
                    NCHAR(10)
            FROM ' + QUOTENAME(@output_database_name) + N'.sys.views AS v
            WHERE v.name LIKE ''' + '%HumanEvents%' + N''';';

    EXECUTE sys.sp_executesql
        @cleanup_views,
      N'@i_cleanup_views nvarchar(max) OUTPUT',
        @i_cleanup_views = @drop_holder OUTPUT;

    IF @debug = 1
    BEGIN
        RAISERROR(@executer, 0, 1) WITH NOWAIT;
        RAISERROR(@drop_holder, 0, 1) WITH NOWAIT;
    END;

    EXECUTE @executer @drop_holder;

    RETURN;
END;
END TRY

/*Very professional error handling*/
BEGIN CATCH
    BEGIN
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

            /*Only try to drop a session if we're not outputting*/
            IF (@output_database_name = N''
                  AND @output_schema_name IN (N'', N'dbo'))
            BEGIN
                IF @debug = 1
                BEGIN
                    RAISERROR(@stop_sql, 0, 1) WITH NOWAIT;
                    RAISERROR(N'all done, stopping session', 0, 1) WITH NOWAIT;
                END;
                EXECUTE (@stop_sql);

                IF @debug = 1
                BEGIN
                    RAISERROR(@drop_sql, 0, 1) WITH NOWAIT;
                    RAISERROR(N'and dropping session', 0, 1) WITH NOWAIT;
                END;
                EXECUTE (@drop_sql);
            END;

            THROW;

    END;
END CATCH;
END;
GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
██╗  ██╗██╗   ██╗███╗   ███╗ █████╗ ███╗   ██╗
██║  ██║██║   ██║████╗ ████║██╔══██╗████╗  ██║
███████║██║   ██║██╔████╔██║███████║██╔██╗ ██║
██╔══██║██║   ██║██║╚██╔╝██║██╔══██║██║╚██╗██║
██║  ██║╚██████╔╝██║ ╚═╝ ██║██║  ██║██║ ╚████║
╚═╝  ╚═╝ ╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝

███████╗██╗   ██╗███████╗███╗   ██╗████████╗███████╗
██╔════╝██║   ██║██╔════╝████╗  ██║╚══██╔══╝██╔════╝
█████╗  ██║   ██║█████╗  ██╔██╗ ██║   ██║   ███████╗
██╔══╝  ╚██╗ ██╔╝██╔══╝  ██║╚██╗██║   ██║   ╚════██║
███████╗ ╚████╔╝ ███████╗██║ ╚████║   ██║   ███████║
╚══════╝  ╚═══╝  ╚══════╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝

██████╗ ██╗      ██████╗  ██████╗██╗  ██╗
██╔══██╗██║     ██╔═══██╗██╔════╝██║ ██╔╝
██████╔╝██║     ██║   ██║██║     █████╔╝
██╔══██╗██║     ██║   ██║██║     ██╔═██╗
██████╔╝███████╗╚██████╔╝╚██████╗██║  ██╗
╚═════╝ ╚══════╝ ╚═════╝  ╚═════╝╚═╝  ╚═╝

██╗   ██╗██╗███████╗██╗    ██╗███████╗██████╗
██║   ██║██║██╔════╝██║    ██║██╔════╝██╔══██╗
██║   ██║██║█████╗  ██║ █╗ ██║█████╗  ██████╔╝
╚██╗ ██╔╝██║██╔══╝  ██║███╗██║██╔══╝  ██╔══██╗
 ╚████╔╝ ██║███████╗╚███╔███╔╝███████╗██║  ██║
  ╚═══╝  ╚═╝╚══════╝ ╚══╝╚══╝ ╚══════╝╚═╝  ╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_HumanEventsBlockViewer
    @help = 1;

For working through errors:
EXECUTE sp_HumanEventsBlockViewer
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com
*/

IF OBJECT_ID(N'dbo.sp_HumanEventsBlockViewer', N'P') IS NULL
   BEGIN
       EXECUTE (N'CREATE PROCEDURE dbo.sp_HumanEventsBlockViewer AS RETURN 138;');
   END;
GO

ALTER PROCEDURE
    dbo.sp_HumanEventsBlockViewer
(
    @session_name sysname = N'keeper_HumanEvents_blocking', /*Event session name*/
    @target_type sysname = NULL, /*ring buffer, file, or table*/
    @start_date datetime2 = NULL, /*when to start looking for blocking*/
    @end_date datetime2 = NULL, /*when to stop looking for blocking*/
    @database_name sysname = NULL, /*target a specific database*/
    @object_name sysname = NULL, /*target a specific schema-prefixed table*/
    @target_database sysname = NULL, /*database containing the table with BPR data*/
    @target_schema sysname = NULL, /*schema of the table*/
    @target_table sysname = NULL, /*table name*/
    @target_column sysname = NULL, /*column containing XML data*/
    @timestamp_column sysname = NULL, /*column containing timestamp (optional)*/
    @log_to_table bit = 0, /*enable logging to permanent tables*/
    @log_database_name sysname = NULL, /*database to store logging tables*/
    @log_schema_name sysname = NULL, /*schema to store logging tables*/
    @log_table_name_prefix sysname = 'HumanEventsBlockViewer', /*prefix for all logging tables*/
    @log_retention_days integer = 30, /*Number of days to keep logs, 0 = keep indefinitely*/
    @max_blocking_events integer = 5000, /*maximum blocking events to analyze, 0 = unlimited*/
    @help bit = 0, /*get help with this procedure*/
    @debug bit = 0, /*print dynamic sql and select temp table contents*/
    @version varchar(30) = NULL OUTPUT, /*check the version number*/
    @version_date datetime = NULL OUTPUT /*check the version date*/
)
WITH RECOMPILE
AS
BEGIN
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
    @version = '5.3',
    @version_date = '20260301';

IF @help = 1
BEGIN
    SELECT
        introduction =
            'hi, i''m sp_HumanEventsBlockViewer!' UNION ALL
    SELECT  'you can use me in conjunction with sp_HumanEvents to quickly parse the sqlserver.blocked_process_report event' UNION ALL
    SELECT  'EXECUTE sp_HumanEvents @event_type = N''blocking'', @keep_alive = 1;' UNION ALL
    SELECT  'it will also work with any other extended event session that captures blocking' UNION ALL
    SELECT  'just use the @session_name parameter to point me there' UNION ALL
    SELECT  'EXECUTE dbo.sp_HumanEventsBlockViewer @session_name = N''blocked_process_report'';' UNION ALL
    SELECT  'the system_health session also works, if you are okay with its lousy blocked process report'
    SELECT  'all scripts and documentation are available here: https://code.erikdarling.com' UNION ALL
    SELECT  'from your loving sql server consultant, erik darling: https://erikdarling.com';

    SELECT
        parameter_name =
            ap.name,
        data_type = t.name,
        description =
            CASE ap.name
                 WHEN N'@session_name' THEN 'name of the extended event session to pull from'
                 WHEN N'@target_type' THEN 'target type of the extended event session (ring buffer, file) or ''table'' to read from a table'
                 WHEN N'@start_date' THEN 'filter by date'
                 WHEN N'@end_date' THEN 'filter by date'
                 WHEN N'@database_name' THEN 'filter by database name'
                 WHEN N'@object_name' THEN 'filter by table name'
                 WHEN N'@target_database' THEN 'database containing the table with blocked process report data'
                 WHEN N'@target_schema' THEN 'schema of the table containing blocked process report data'
                 WHEN N'@target_table' THEN 'table containing blocked process report data'
                 WHEN N'@target_column' THEN 'column containing blocked process report XML'
                 WHEN N'@timestamp_column' THEN 'column containing timestamp for filtering (optional)'
                 WHEN N'@log_to_table' THEN N'enable logging to permanent tables instead of returning results'
                 WHEN N'@log_database_name' THEN N'database to store logging tables'
                 WHEN N'@log_schema_name' THEN N'schema to store logging tables'
                 WHEN N'@log_table_name_prefix' THEN N'prefix for all logging tables'
                 WHEN N'@log_retention_days' THEN N'how many days of data to retain'
                 WHEN N'@max_blocking_events' THEN N'maximum blocking events to analyze, 0 = unlimited'
                 WHEN N'@help' THEN 'how you got here'
                 WHEN N'@debug' THEN 'dumps raw temp table contents'
                 WHEN N'@version' THEN 'OUTPUT; for support'
                 WHEN N'@version_date' THEN 'OUTPUT; for support'
            END,
        valid_inputs =
            CASE ap.name
                 WHEN N'@session_name' THEN 'extended event session name capturing sqlserver.blocked_process_report, system_health also works'
                 WHEN N'@target_type' THEN 'event_file or ring_buffer or table'
                 WHEN N'@start_date' THEN 'a reasonable date'
                 WHEN N'@end_date' THEN 'a reasonable date'
                 WHEN N'@database_name' THEN 'a database that exists on this server'
                 WHEN N'@object_name' THEN 'a schema-prefixed table name'
                 WHEN N'@target_database' THEN 'a database that exists on this server'
                 WHEN N'@target_schema' THEN 'a schema in the target database'
                 WHEN N'@target_table' THEN 'a table in the target schema'
                 WHEN N'@target_column' THEN 'an XML column containing blocked process report data'
                 WHEN N'@timestamp_column' THEN 'a datetime column for filtering by date range'
                 WHEN N'@log_to_table' THEN N'0 or 1'
                 WHEN N'@log_database_name' THEN N'any valid database name'
                 WHEN N'@log_schema_name' THEN N'any valid schema name'
                 WHEN N'@log_table_name_prefix' THEN N'any valid identifier'
                 WHEN N'@log_retention_days' THEN N'a positive integer'
                 WHEN N'@max_blocking_events' THEN N'0 to 2147483647 (0 = unlimited)'
                 WHEN N'@help' THEN '0 or 1'
                 WHEN N'@debug' THEN '0 or 1'
                 WHEN N'@version' THEN 'none; OUTPUT'
                 WHEN N'@version_date' THEN 'none; OUTPUT'
            END,
        defaults =
            CASE ap.name
                 WHEN N'@session_name' THEN 'keeper_HumanEvents_blocking'
                 WHEN N'@target_type' THEN 'NULL'
                 WHEN N'@start_date' THEN 'NULL; will shortcut to last 7 days'
                 WHEN N'@end_date' THEN 'NULL'
                 WHEN N'@database_name' THEN 'NULL'
                 WHEN N'@object_name' THEN 'NULL'
                 WHEN N'@target_database' THEN 'NULL'
                 WHEN N'@target_schema' THEN 'NULL'
                 WHEN N'@target_table' THEN 'NULL'
                 WHEN N'@target_column' THEN 'NULL'
                 WHEN N'@timestamp_column' THEN 'NULL'
                 WHEN N'@log_to_table' THEN N'0'
                 WHEN N'@log_database_name' THEN N'NULL (current database)'
                 WHEN N'@log_schema_name' THEN N'NULL (dbo)'
                 WHEN N'@log_table_name_prefix' THEN N'HumanEventsBlockViewer'
                 WHEN N'@log_retention_days' THEN N'30'
                 WHEN N'@max_blocking_events' THEN N'5000'
                 WHEN N'@help' THEN '0'
                 WHEN N'@debug' THEN '0'
                 WHEN N'@version' THEN 'none; OUTPUT'
                 WHEN N'@version_date' THEN 'none; OUTPUT'
            END
    FROM sys.all_parameters AS ap
    JOIN sys.all_objects AS o
      ON ap.object_id = o.object_id
    JOIN sys.types AS t
      ON  ap.system_type_id = t.system_type_id
      AND ap.user_type_id = t.user_type_id
    WHERE o.name = N'sp_HumanEventsBlockViewer'
    OPTION(RECOMPILE);

    SELECT
        blocked_process_report_setup =
            N'check the messages tab for setup commands';

    RAISERROR('
Unless you want to use the lousy version in system_health, the blocked process report needs to be enabled:
EXECUTE sys.sp_configure ''show advanced options'', 1;
EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */
RECONFIGURE;', 0, 1) WITH NOWAIT;

    RAISERROR('
/*Create an extended event to log the blocked process report*/
/*
This won''t work in Azure SQLDB, you need to customize it to create:
 * ON DATABASE instead of ON SERVER
 * With a ring_buffer target
*/
CREATE EVENT SESSION
    blocked_process_report
ON SERVER
    ADD EVENT
        sqlserver.blocked_process_report
    ADD TARGET
        package0.event_file
    (
        SET filename = N''bpr''
    )
WITH
(
    MAX_MEMORY = 4096KB,
    EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
    MAX_DISPATCH_LATENCY = 5 SECONDS,
    MAX_EVENT_SIZE = 0KB,
    MEMORY_PARTITION_MODE = NONE,
    TRACK_CAUSALITY = OFF,
    STARTUP_STATE = ON
);

ALTER EVENT SESSION
    blocked_process_report
ON SERVER
    STATE = START; ', 0, 1) WITH NOWAIT;

    SELECT
        mit_license_yo = 'i am MIT licensed, so like, do whatever'

    UNION ALL

    SELECT
        mit_license_yo = 'see printed messages for full license';

    RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;

    RETURN;
END;

/*Check if the blocked process report is on at all*/
IF EXISTS
(
    SELECT
        1/0
    FROM sys.configurations AS c
    WHERE c.name = N'blocked process threshold (s)'
    AND   CONVERT(integer, c.value_in_use) = 0
)
AND @session_name NOT LIKE N'system%health'
BEGIN
    RAISERROR(N'Unless you want to use the lousy version in system_health, the blocked process report needs to be enabled:
EXECUTE sys.sp_configure ''show advanced options'', 1;
EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */
RECONFIGURE;',
    11, 0) WITH NOWAIT;
    RETURN;
END;

/*Check if the blocked process report is well-configured*/
IF EXISTS
(
    SELECT
        1/0
    FROM sys.configurations AS c
    WHERE c.name = N'blocked process threshold (s)'
    AND   CONVERT(integer, c.value_in_use) <> 5
)
AND @session_name NOT LIKE N'system%health'
BEGIN
    RAISERROR(N'For best results, set up the blocked process report like this:
EXECUTE sys.sp_configure ''show advanced options'', 1;
EXECUTE sys.sp_configure ''blocked process threshold'', 5; /* Seconds of blocking before a report is generated */
RECONFIGURE;',
    10, 0) WITH NOWAIT;
END;

/*Set some variables for better decision-making later*/
IF @debug = 1
BEGIN
    RAISERROR('Declaring variables', 0, 1) WITH NOWAIT;
END;
DECLARE
    @azure bit =
        CASE
            WHEN CONVERT
                 (
                     integer,
                     SERVERPROPERTY('EngineEdition')
                 ) = 5
            THEN 1
            ELSE 0
        END,
    @azure_msg nchar(1),
    @session_id integer,
    @target_session_id integer,
    @file_name nvarchar(4000),
    @is_system_health bit = 0,
    @is_system_health_msg nchar(1),
    @inputbuf_bom nvarchar(1) =
        CONVERT(nvarchar(1), 0x0a00, 0),
    @start_date_original datetime2 = @start_date,
    @end_date_original datetime2 = @end_date,
    @validation_sql nvarchar(max),
    @extract_sql nvarchar(max),
    /*Log to table stuff*/
    @log_table_blocking sysname,
    @cleanup_date datetime2(7),
    @check_sql nvarchar(max) = N'',
    @create_sql nvarchar(max) = N'',
    @insert_sql nvarchar(max) = N'',
    @log_database_schema nvarchar(1024),
    @max_event_time datetime2(7),
    @dsql nvarchar(max) = N'',
    @mdsql nvarchar(max) = N'',
    @actual_start_date datetime2(7),
    @actual_end_date datetime2(7),
    @actual_event_count bigint;

/*Use some sane defaults for input parameters*/
IF @debug = 1
BEGIN
    RAISERROR('Setting variables', 0, 1) WITH NOWAIT;
END;
SELECT
    @start_date =
        CASE
            WHEN @start_date IS NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        SYSDATETIME(),
                        GETUTCDATE()
                    ),
                    DATEADD
                        (
                            DAY,
                            -7,
                            SYSDATETIME()
                        )
                )
            ELSE
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        SYSDATETIME(),
                        GETUTCDATE()
                    ),
                    @start_date
                )
        END,
    @end_date =
        CASE
            WHEN @end_date IS NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        SYSDATETIME(),
                        GETUTCDATE()
                    ),
                    SYSDATETIME()
                )
            ELSE
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        SYSDATETIME(),
                        GETUTCDATE()
                    ),
                    @end_date
                )
        END,
    @is_system_health =
        CASE
            WHEN @session_name LIKE N'system%health'
            THEN 1
            ELSE 0
        END,
    @mdsql = N'
IF OBJECT_ID(''{table_check}'', ''U'') IS NOT NULL
BEGIN
    SELECT
        @max_event_time =
            ISNULL
            (
                MAX({date_column}),
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        SYSDATETIME(),
                        GETUTCDATE()
                    ),
                    DATEADD
                    (
                        DAY,
                        -1,
                        SYSDATETIME()
                    )
                )
            )
    FROM {table_check};
END;';

IF @debug = 1
AND @is_system_health = 0
BEGIN
    RAISERROR('We are not using system_health', 0, 1) WITH NOWAIT;
END;

IF @is_system_health = 1
BEGIN
    RAISERROR('For best results, consider not using system_health as your target. Re-run with @help = 1 for guidance.', 0, 1) WITH NOWAIT;
END

/*
Note: I do not allow logging to a table from system_health, because the set of columns
and available data is too incomplete, and I don't want to juggle multiple
table definitions.

Logging to a table is only allowed from a blocked_process_report Extended Event,
but it can either be ring buffer or file target. I don't care about that.
*/
IF @is_system_health = 1
AND
(
  LOWER(@target_type) = N'table'
  OR @log_to_table = 1
)
BEGIN
    RAISERROR('Logging system_health to a table is not supported.
Either pick a different session or change both
@target_type to be ''event_file'' or ''ring_buffer''
and @log_to_table to be 0.', 11, 0) WITH NOWAIT;
    RETURN;
END

IF  @is_system_health = 1
AND @target_type IS NULL
BEGIN
    RAISERROR('No @target_type specified, using the ''event_file'' for system_health.', 0, 1) WITH NOWAIT;
    SELECT
        @target_type = 'event_file';
END

SELECT
    @azure_msg =
        CONVERT(nchar(1), @azure),
    @is_system_health_msg =
        CONVERT(nchar(1), @is_system_health);

/*Change this here in case someone leave it NULL*/
IF  ISNULL(@target_database, DB_NAME()) IS NOT NULL
AND ISNULL(@target_schema, N'dbo') IS NOT NULL
AND @target_table IS NOT NULL
AND @target_column IS NOT NULL
AND @is_system_health = 0
BEGIN
    SET @target_type = N'table';
END;

/* Check for table input early and validate */
IF LOWER(@target_type) = N'table'
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Table source detected, validating parameters', 0, 1) WITH NOWAIT;
    END;

    IF @target_database IS NULL
    BEGIN
        SET @target_database = DB_NAME();
    END;

    IF @target_schema IS NULL
    BEGIN
        SET @target_schema = N'dbo'
    END;

    /* Parameter validation  */
    IF @target_table IS NULL
    OR @target_column IS NULL
    BEGIN
        RAISERROR(N'
        When @target_type is ''table'', you must specify @target_table and @target_column.
        When @target_database or @target_schema is NULL, they default to DB_NAME() and dbo.
        ',
        11, 1) WITH NOWAIT;
        RETURN;
    END;

    /* Check if target database exists */
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.databases AS d
        WHERE d.name = @target_database
    )
    BEGIN
        RAISERROR(N'The specified @target_database ''%s'' does not exist.', 11, 1, @target_database) WITH NOWAIT;
        RETURN;
    END;

    /* Use dynamic SQL to validate schema, table, and column existence */
    SET @validation_sql = N'
    /*Validate schema exists*/
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
        WHERE s.name = @schema
    )
    BEGIN
        RAISERROR(N''The specified @target_schema %s does not exist in @database %s'', 11, 1, @schema, @database) WITH NOWAIT;
        RETURN;
    END;

    /*Validate table exists*/
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.tables AS t
        JOIN ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
          ON t.schema_id = s.schema_id
        WHERE t.name = @table
        AND   s.name = @schema
    )
    BEGIN
        RAISERROR(N''The specified @target_table %s does not exist in @schema %s in database %s'', 11, 1, @table, @schema, @database) WITH NOWAIT;
        RETURN;
    END;

    /*Validate column name exists*/
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.columns AS c
        JOIN ' + QUOTENAME(@target_database) + N'.sys.tables AS t
          ON c.object_id = t.object_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
          ON t.schema_id = s.schema_id
        WHERE c.name = @column
        AND   t.name = @table
        AND   s.name = @schema
    )
    BEGIN
        RAISERROR(N''The specified @target_column %s does not exist in table %s.%s in database %s'', 11, 1, @column, @schema, @table, @database) WITH NOWAIT;
        RETURN;
    END;

    /* Validate column is XML type */
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.columns AS c
        JOIN ' + QUOTENAME(@target_database) + N'.sys.types AS ty
          ON c.user_type_id = ty.user_type_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.tables AS t
          ON c.object_id = t.object_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
          ON t.schema_id = s.schema_id
        WHERE c.name = @column
        AND   t.name = @table
        AND   s.name = @schema
        AND   ty.name = ''xml''
    )
    BEGIN
        RAISERROR(N''The specified @target_column %s must be of XML data type.'', 11, 1, @column) WITH NOWAIT;
        RETURN;
    END;
    ';

    /* Validate timestamp_column if specified */
    IF @timestamp_column IS NOT NULL
    BEGIN
        SET @validation_sql = @validation_sql + N'
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.columns AS c
        JOIN ' + QUOTENAME(@target_database) + N'.sys.tables AS t
          ON c.object_id = t.object_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
          ON t.schema_id = s.schema_id
        WHERE c.name = @timestamp_column
        AND   t.name = @table
        AND   s.name = @schema
    )
    BEGIN
        RAISERROR(N''The specified @timestamp_column %s does not exist in table %s.%s in database %s'', 11, 1, @timestamp_column, @schema, @table, @database) WITH NOWAIT;
        RETURN;
    END;

    /* Validate timestamp column is date-ish type */
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM ' + QUOTENAME(@target_database) + N'.sys.columns AS c
        JOIN ' + QUOTENAME(@target_database) + N'.sys.types AS ty
          ON c.user_type_id = ty.user_type_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.tables AS t
          ON c.object_id = t.object_id
        JOIN ' + QUOTENAME(@target_database) + N'.sys.schemas AS s
          ON t.schema_id = s.schema_id
        WHERE c.name = @timestamp_column
        AND   t.name = @table
        AND   s.name = @schema
        AND   ty.name LIKE N''%date%''
    )
    BEGIN
        RAISERROR(N''The specified @timestamp_column %s must be of datetime data type.'', 11, 1, @timestamp_column) WITH NOWAIT;
        RETURN;
    END;';
    END;

    IF @debug = 1
    BEGIN
        PRINT @validation_sql;
    END;

    EXECUTE sys.sp_executesql
        @validation_sql,
        N'
        @database sysname,
        @schema sysname,
        @table sysname,
        @column sysname,
        @timestamp_column sysname
        ',
        @target_database,
        @target_schema,
        @target_table,
        @target_column,
        @timestamp_column;
END;

/* Validate logging parameters */
IF @log_to_table = 1
BEGIN
    SELECT
        /* Default database name to current database if not specified */
        @log_database_name = ISNULL(@log_database_name, DB_NAME()),
        /* Default schema name to dbo if not specified */
        @log_schema_name = ISNULL(@log_schema_name, N'dbo'),
        @log_retention_days =
            CASE
                WHEN @log_retention_days < 0
                THEN ABS(@log_retention_days)
                ELSE @log_retention_days
            END;

    /* Validate database exists */
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.databases AS d
        WHERE d.name = @log_database_name
    )
    BEGIN
        RAISERROR('The specified logging database %s does not exist. Logging will be disabled.', 11, 1, @log_database_name) WITH NOWAIT;
        RETURN;
    END;

    SET
        @log_database_schema =
            QUOTENAME(@log_database_name) +
            N'.' +
            QUOTENAME(@log_schema_name) +
            N'.';

    /* Generate fully qualified table names */
    SELECT
        @log_table_blocking =
            @log_database_schema +
            QUOTENAME(@log_table_name_prefix + N'_BlockedProcessReport');

    /* Check if schema exists and create it if needed */
    SET @check_sql = N'
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
            WHERE s.name = @schema_name
        )
        BEGIN
            DECLARE
                @create_schema_sql nvarchar(max) = N''CREATE SCHEMA '' + QUOTENAME(@schema_name);

            EXECUTE ' + QUOTENAME(@log_database_name) + N'.sys.sp_executesql @create_schema_sql;
            IF @debug = 1 BEGIN RAISERROR(''Created schema %s in database %s for logging.'', 0, 1, @schema_name, @db_name) WITH NOWAIT; END;
        END';

    EXECUTE sys.sp_executesql
        @check_sql,
      N'@schema_name sysname,
        @db_name sysname,
        @debug bit',
        @log_schema_name,
        @log_database_name,
        @debug;

    SET @create_sql = N'
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
            JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
              ON t.schema_id = s.schema_id
            WHERE t.name = @table_name + N''_BlockedProcessReport''
            AND   s.name = @schema_name
        )
        BEGIN
            CREATE TABLE ' + @log_table_blocking + N'
            (
                id bigint IDENTITY,
                collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                blocked_process_report varchar(22) NOT NULL,
                event_time datetime2(7) NULL,
                database_name nvarchar(128) NULL,
                currentdbname nvarchar(256) NULL,
                contentious_object nvarchar(4000) NULL,
                activity varchar(8) NULL,
                blocking_tree varchar(8000) NULL,
                spid integer NULL,
                ecid integer NULL,
                query_text xml NULL,
                wait_time_ms bigint NULL,
                status nvarchar(10) NULL,
                isolation_level nvarchar(50) NULL,
                lock_mode nvarchar(10) NULL,
                resource_owner_type nvarchar(256) NULL,
                transaction_count integer NULL,
                transaction_name nvarchar(1024) NULL,
                last_transaction_started datetime2(7) NULL,
                last_transaction_completed datetime2(7) NULL,
                client_option_1 varchar(261) NULL,
                client_option_2 varchar(307) NULL,
                wait_resource nvarchar(1024) NULL,
                priority integer NULL,
                log_used bigint NULL,
                client_app nvarchar(256) NULL,
                host_name nvarchar(256) NULL,
                login_name nvarchar(256) NULL,
                transaction_id bigint NULL,
                blocked_process_report_xml xml NULL
                PRIMARY KEY CLUSTERED (collection_time, id)
            );
            IF @debug = 1 BEGIN RAISERROR(''Created table %s for significant waits logging.'', 0, 1, ''' + @log_table_blocking + N''') WITH NOWAIT; END;
        END';

    EXECUTE sys.sp_executesql
        @create_sql,
      N'@schema_name sysname,
        @table_name sysname,
        @debug bit',
        @log_schema_name,
        @log_table_name_prefix,
        @debug;

    /* Handle log retention if specified */
    IF @log_to_table = 1 AND @log_retention_days > 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Cleaning up log tables older than %i days', 0, 1, @log_retention_days) WITH NOWAIT;
        END;

        SET @cleanup_date =
            DATEADD
            (
                DAY,
                -@log_retention_days,
                SYSDATETIME()
            );

        /* Clean up each log table */
        SET @dsql = N'
        DELETE FROM ' + @log_table_blocking + '
        WHERE collection_time < @cleanup_date;';

        IF @debug = 1 BEGIN PRINT @dsql; END;

        EXECUTE sys.sp_executesql
            @dsql,
          N'@cleanup_date datetime2(7)',
            @cleanup_date;

        IF @debug = 1
        BEGIN
            RAISERROR('Log cleanup complete', 0, 1) WITH NOWAIT;
        END;
    END;
END;

/*Temp tables for staging results*/
IF @debug = 1
BEGIN
    RAISERROR('Creating temp tables', 0, 1) WITH NOWAIT;
END;
CREATE TABLE
    #x
(
    x xml
);

CREATE TABLE
    #blocking_xml
(
    human_events_xml xml
);

CREATE TABLE
    #block_findings
(
    id integer IDENTITY PRIMARY KEY CLUSTERED,
    check_id integer NOT NULL,
    database_name nvarchar(256) NULL,
    object_name nvarchar(1000) NULL,
    finding_group nvarchar(100) NULL,
    finding nvarchar(4000) NULL,
    sort_order bigint
);

CREATE TABLE
    #sp_server_diagnostics_component_result
(
    sp_server_diagnostics_component_result xml
);

IF LOWER(@target_type) = N'table'
BEGIN
    GOTO TableMode;
END;

/*Look to see if the session exists and is running*/
IF @debug = 1
BEGIN
    RAISERROR('Checking if the session exists', 0, 1) WITH NOWAIT;
END;
IF @azure = 0
BEGIN
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.server_event_sessions AS ses
        JOIN sys.dm_xe_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name = @session_name
        AND   dxs.create_time IS NOT NULL
    )
    BEGIN
        RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT;
        RETURN;
    END;
END;

IF @azure = 1
BEGIN
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.database_event_sessions AS ses
        JOIN sys.dm_xe_database_sessions AS dxs
          ON dxs.name = ses.name
        WHERE ses.name = @session_name
        AND   dxs.create_time IS NOT NULL
    )
    BEGIN
        RAISERROR('A session with the name %s does not exist or is not currently active.', 11, 1, @session_name) WITH NOWAIT;
        RETURN;
    END;
END;

/*Figure out if we have a file or ring buffer target*/
IF @debug = 1
BEGIN
    RAISERROR('What kind of target does %s have?', 0, 1, @session_name) WITH NOWAIT;
END;
IF  @target_type IS NULL
AND @is_system_health = 0
BEGIN
    IF @azure = 0
    BEGIN
        SELECT TOP (1)
            @target_type =
                t.target_name
        FROM sys.dm_xe_sessions AS s
        JOIN sys.dm_xe_session_targets AS t
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        ORDER BY
            t.target_name
        OPTION(RECOMPILE);
    END;

    IF @azure = 1
    BEGIN
        SELECT TOP (1)
            @target_type =
                t.target_name
        FROM sys.dm_xe_database_sessions AS s
        JOIN sys.dm_xe_database_session_targets AS t
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        ORDER BY
            t.target_name
        OPTION(RECOMPILE);
    END;
END;

/*
Dump whatever we got into a temp table
Note that system_health hits this branch if we use the ring_buffer target.
*/
IF  LOWER(@target_type) = N'ring_buffer'
BEGIN
    IF @azure = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Azure: %s', 0, 1, @azure_msg) WITH NOWAIT;
            RAISERROR('Inserting to #x for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
        END;

        INSERT
            #x WITH(TABLOCKX)
        (
            x
        )
        SELECT
            x = TRY_CAST(t.target_data AS xml)
        FROM sys.dm_xe_session_targets AS t
        JOIN sys.dm_xe_sessions AS s
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        AND   t.target_name = N'ring_buffer'
        OPTION(RECOMPILE);
    END;

    IF @azure = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Azure: %s', 0, 1, @azure_msg) WITH NOWAIT;
            RAISERROR('Inserting to #x for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
        END;

        INSERT
            #x WITH(TABLOCKX)
        (
            x
        )
        SELECT
            x = TRY_CAST(t.target_data AS xml)
        FROM sys.dm_xe_database_session_targets AS t
        JOIN sys.dm_xe_database_sessions AS s
          ON s.address = t.event_session_address
        WHERE s.name = @session_name
        AND   t.target_name = N'ring_buffer'
        OPTION(RECOMPILE);
    END;
END;

IF  LOWER(@target_type) = N'event_file'
AND @is_system_health = 0
BEGIN
    IF @azure = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Azure: %s', 0, 1, @azure_msg) WITH NOWAIT;
            RAISERROR('Inserting to #x for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
        END;

        SELECT
            @session_id =
                t.event_session_id,
            @target_session_id =
                t.target_id
        FROM sys.server_event_session_targets t
        JOIN sys.server_event_sessions s
          ON s.event_session_id = t.event_session_id
        WHERE t.name = @target_type
        AND   s.name = @session_name
        OPTION(RECOMPILE);

        SELECT
            @file_name =
                CASE
                    WHEN f.file_name LIKE N'%.xel'
                    THEN REPLACE(f.file_name, N'.xel', N'*.xel')
                    ELSE f.file_name + N'*.xel'
                END
        FROM
        (
            SELECT
                file_name =
                    CONVERT
                    (
                        nvarchar(4000),
                        f.value
                    )
            FROM sys.server_event_session_fields AS f
            WHERE f.event_session_id = @session_id
            AND   f.object_id = @target_session_id
            AND   f.name = N'filename'
        ) AS f
        OPTION(RECOMPILE);
    END;

    IF @azure = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Azure: %s', 0, 1, @azure_msg) WITH NOWAIT;
            RAISERROR('Inserting to #x for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
        END;

        SELECT
            @session_id =
                t.event_session_id,
            @target_session_id =
                t.target_id
        FROM sys.database_event_session_targets AS t
        JOIN sys.database_event_sessions AS s
          ON s.event_session_id = t.event_session_id
        WHERE t.name = @target_type
        AND   s.name = @session_name
        OPTION(RECOMPILE);

        SELECT
            @file_name =
                CASE
                    WHEN f.file_name LIKE N'%.xel'
                    THEN REPLACE(f.file_name, N'.xel', N'*.xel')
                    ELSE f.file_name + N'*.xel'
                END
        FROM
        (
            SELECT
                file_name =
                    CONVERT
                    (
                        nvarchar(4000),
                        f.value
                    )
            FROM sys.database_event_session_fields AS f
            WHERE f.event_session_id = @session_id
            AND   f.object_id = @target_session_id
            AND   f.name = N'filename'
        ) AS f
        OPTION(RECOMPILE);
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('Azure: %s', 0, 1, @azure_msg) WITH NOWAIT;
        RAISERROR('Inserting to #x for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
        RAISERROR('File name: %s', 0, 1, @file_name) WITH NOWAIT;
    END;

    INSERT
        #x WITH(TABLOCKX)
    (
        x
    )
    SELECT
        x = TRY_CAST(f.event_data AS xml)
    FROM sys.fn_xe_file_target_read_file
         (
             @file_name,
             NULL,
             NULL,
             NULL
         ) AS f
    OPTION(RECOMPILE);
END;


IF  LOWER(@target_type) = N'ring_buffer'
AND @is_system_health = 0
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Inserting to #blocking_xml for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
    END;

    INSERT
        #blocking_xml
    WITH
        (TABLOCKX)
    (
        human_events_xml
    )
    SELECT
        human_events_xml
    FROM
    (
        SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 2147483647 END)
            human_events_xml = e.x.query('.'),
            event_timestamp = e.x.value('@timestamp', 'datetime2')
        FROM #x AS x
        CROSS APPLY x.x.nodes('/RingBufferTarget/event') AS e(x)
        WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1
        AND   e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1
        ORDER BY
            event_timestamp DESC
    ) AS most_recent
    OPTION(RECOMPILE);
END;

IF  LOWER(@target_type) = N'event_file'
AND @is_system_health = 0
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Inserting to #blocking_xml for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
    END;

    INSERT
        #blocking_xml
    WITH
        (TABLOCKX)
    (
        human_events_xml
    )
    SELECT
        human_events_xml
    FROM
    (
        SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 2147483647 END)
            human_events_xml = e.x.query('.'),
            event_timestamp = e.x.value('@timestamp', 'datetime2')
        FROM #x AS x
        CROSS APPLY x.x.nodes('/event') AS e(x)
        WHERE e.x.exist('@name[ .= "blocked_process_report"]') = 1
        AND   e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1
        ORDER BY
            event_timestamp DESC
    ) AS most_recent
    OPTION(RECOMPILE);
END;

/*
This section is special for the well-hidden and much less comprehensive blocked
process report stored in the system health extended event session.

We disallow many features here.
See where @is_system_health was declared for details.
That is also where we error out if somebody tries to use an unsupported feature.
*/
IF  @is_system_health = 1
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Inserting to #sp_server_diagnostics_component_result for target type: %s and system health: %s', 0, 1, @target_type, @is_system_health_msg) WITH NOWAIT;
    END;

    IF @target_type = N'ring_buffer'
    BEGIN
        INSERT
            #sp_server_diagnostics_component_result
        WITH
            (TABLOCKX)
        (
            sp_server_diagnostics_component_result
        )
        SELECT
            sp_server_diagnostics_component_result = e.x.query('.')
        FROM #x AS xml
        CROSS APPLY xml.x.nodes('/RingBufferTarget/event') AS e(x)
        WHERE e.x.exist('@name[ .= "sp_server_diagnostics_component_result"]') = 1
        AND   e.x.exist('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report') = 1
        AND   e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1
        OPTION(RECOMPILE);
    END
    ELSE
    BEGIN
        INSERT
            #sp_server_diagnostics_component_result
        WITH
            (TABLOCKX)
        (
            sp_server_diagnostics_component_result
        )
        SELECT
            xml.sp_server_diagnostics_component_result
        FROM
        (
            SELECT
                sp_server_diagnostics_component_result =
                    TRY_CAST(fx.event_data AS xml)
            FROM sys.fn_xe_file_target_read_file(N'system_health*.xel', NULL, NULL, NULL) AS fx
            WHERE fx.object_name = N'sp_server_diagnostics_component_result'
        ) AS xml
        CROSS APPLY xml.sp_server_diagnostics_component_result.nodes('/event') AS e(x)
        WHERE e.x.exist('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report') = 1
        AND   e.x.exist('@timestamp[. >= sql:variable("@start_date") and .< sql:variable("@end_date")]') = 1
        OPTION(RECOMPILE);
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#sp_server_diagnostics_component_result',
            ssdcr.*
        FROM #sp_server_diagnostics_component_result AS ssdcr
        OPTION(RECOMPILE);

        RAISERROR('Inserting to #blocking_xml_sh', 0, 1) WITH NOWAIT;
    END;

    SELECT
        event_time,
        human_events_xml
    INTO #blocking_xml_sh
    FROM
    (
        SELECT TOP (CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 2147483647 END)
            event_time =
                DATEADD
                (
                    MINUTE,
                    DATEDIFF
                    (
                        MINUTE,
                        GETUTCDATE(),
                        SYSDATETIME()
                    ),
                    w.x.value('(//@timestamp)[1]', 'datetime2')
                ),
            human_events_xml = w.x.query('//data[@name="data"]/value/queryProcessing/blockingTasks/blocked-process-report'),
            event_timestamp = w.x.value('(//@timestamp)[1]', 'datetime2')
        FROM #sp_server_diagnostics_component_result AS wi
        CROSS APPLY wi.sp_server_diagnostics_component_result.nodes('//event') AS w(x)
        ORDER BY
            event_timestamp DESC
    ) AS most_recent
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#blocking_xml',
            bxs.*
        FROM #blocking_xml_sh AS bxs
        OPTION(RECOMPILE);

        RAISERROR('Inserting to #blocked_sh', 0, 1) WITH NOWAIT;
    END;

    SELECT
        bx.event_time,
        currentdbname = bd.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
        spid = bd.value('(process/@spid)[1]', 'integer'),
        ecid = bd.value('(process/@ecid)[1]', 'integer'),
        query_text_pre = bd.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
        wait_time = bd.value('(process/@waittime)[1]', 'bigint'),
        last_transaction_started = bd.value('(process/@lastbatchstarted)[1]', 'datetime2'),
        last_transaction_completed = bd.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
        wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
        status = bd.value('(process/@status)[1]', 'nvarchar(10)'),
        priority = bd.value('(process/@priority)[1]', 'integer'),
        transaction_count = bd.value('(process/@trancount)[1]', 'integer'),
        client_app = bd.value('(process/@clientapp)[1]', 'nvarchar(256)'),
        host_name = bd.value('(process/@hostname)[1]', 'nvarchar(256)'),
        login_name = bd.value('(process/@loginname)[1]', 'nvarchar(256)'),
        isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
        log_used = bd.value('(process/@logused)[1]', 'bigint'),
        clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'),
        clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'),
        activity = CASE WHEN bd.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END,
        blocked_process_report = bd.query('.')
    INTO #blocked_sh
    FROM #blocking_xml_sh AS bx
    OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
    WHERE bd.exist('process/@spid') = 1
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Adding query_text to #blocked_sh', 0, 1) WITH NOWAIT;
    END;

    ALTER TABLE #blocked_sh
    ADD query_text AS
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           query_text_pre COLLATE Latin1_General_BIN2,
       NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
       NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
       NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
    PERSISTED;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#blocking_xml_sh',
            bxs.*
        FROM #blocking_xml_sh AS bxs
        OPTION(RECOMPILE);

        RAISERROR('Inserting to #blocking_sh', 0, 1) WITH NOWAIT;
    END;

    /*Blocking queries*/
    SELECT
        bx.event_time,
        currentdbname = bg.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
        spid = bg.value('(process/@spid)[1]', 'integer'),
        ecid = bg.value('(process/@ecid)[1]', 'integer'),
        query_text_pre = bg.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
        wait_time = bg.value('(process/@waittime)[1]', 'bigint'),
        last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'),
        last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
        wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
        status = bg.value('(process/@status)[1]', 'nvarchar(10)'),
        priority = bg.value('(process/@priority)[1]', 'integer'),
        transaction_count = bg.value('(process/@trancount)[1]', 'integer'),
        client_app = bg.value('(process/@clientapp)[1]', 'nvarchar(256)'),
        host_name = bg.value('(process/@hostname)[1]', 'nvarchar(256)'),
        login_name = bg.value('(process/@loginname)[1]', 'nvarchar(256)'),
        isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
        log_used = bg.value('(process/@logused)[1]', 'bigint'),
        clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'),
        clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'),
        activity = CASE WHEN bg.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END,
        blocked_process_report = bg.query('.')
    INTO #blocking_sh
    FROM #blocking_xml_sh AS bx
    OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
    OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
    WHERE bg.exist('process/@spid') = 1
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Adding query_text to #blocking_sh', 0, 1) WITH NOWAIT;
    END;

    ALTER TABLE #blocking_sh
    ADD query_text AS
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
           query_text_pre COLLATE Latin1_General_BIN2,
       NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
       NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
       NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
    PERSISTED;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#blocking_sh',
            bs.*
        FROM #blocking_sh AS bs
        OPTION(RECOMPILE);

        RAISERROR('Inserting to #blocks_sh', 0, 1) WITH NOWAIT;
    END;

    /*Put it together*/
    SELECT
        kheb.event_time,
        kheb.currentdbname,
        kheb.activity,
        kheb.spid,
        kheb.ecid,
        query_text =
            CASE
                WHEN kheb.query_text
                     LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|'
                THEN
                    (
                        SELECT
                            [processing-instruction(query)] =
                                OBJECT_SCHEMA_NAME
                                (
                                        SUBSTRING
                                        (
                                            kheb.query_text,
                                            CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                            LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                        )
                                        ,
                                        SUBSTRING
                                        (
                                            kheb.query_text,
                                            CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                            CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                        )
                                ) +
                                N'.' +
                                OBJECT_NAME
                                (
                                     SUBSTRING
                                     (
                                         kheb.query_text,
                                         CHARINDEX(N'Object Id = ', kheb.query_text) + 12,
                                         LEN(kheb.query_text) - (CHARINDEX(N'Object Id = ', kheb.query_text) + 12)
                                     )
                                     ,
                                     SUBSTRING
                                     (
                                         kheb.query_text,
                                         CHARINDEX(N'Database Id = ', kheb.query_text) + 14,
                                         CHARINDEX(N'Object Id', kheb.query_text) - (CHARINDEX(N'Database Id = ', kheb.query_text) + 14)
                                     )
                                )
                        FOR XML
                            PATH(N''),
                            TYPE
                    )
                ELSE
                    (
                        SELECT
                            [processing-instruction(query)] =
                                kheb.query_text
                        FOR XML
                            PATH(N''),
                            TYPE
                    )
            END,
        wait_time_ms =
            kheb.wait_time,
        kheb.status,
        kheb.isolation_level,
        kheb.transaction_count,
        kheb.last_transaction_started,
        kheb.last_transaction_completed,
        client_option_1 =
            SUBSTRING
            (
                CASE WHEN kheb.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
                CASE WHEN kheb.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
                3,
                8000
            ),
        client_option_2 =
            SUBSTRING
            (
                CASE WHEN kheb.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
                CASE WHEN kheb.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
                3,
                8000
            ),
        kheb.wait_resource,
        kheb.priority,
        kheb.log_used,
        kheb.client_app,
        kheb.host_name,
        kheb.login_name,
        kheb.blocked_process_report
    INTO #blocks_sh
    FROM
    (
        SELECT
            bg.*
        FROM #blocking_sh AS bg
        WHERE (bg.currentdbname = @database_name
               OR @database_name IS NULL)

        UNION ALL

        SELECT
            bd.*
        FROM #blocked_sh AS bd
        WHERE (bd.currentdbname = @database_name
               OR @database_name IS NULL)
    ) AS kheb
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#blocks_sh',
            bs.*
        FROM #blocks_sh AS bs
        OPTION(RECOMPILE);
    END;

    SELECT
        b.event_time,
        b.currentdbname,
        b.activity,
        b.spid,
        b.ecid,
        b.query_text,
        b.wait_time_ms,
        b.status,
        b.isolation_level,
        b.transaction_count,
        b.last_transaction_started,
        b.last_transaction_completed,
        b.client_option_1,
        b.client_option_2,
        b.wait_resource,
        b.priority,
        b.log_used,
        b.client_app,
        b.host_name,
        b.login_name,
        b.blocked_process_report
    FROM #blocks_sh AS b
    ORDER BY
        b.event_time DESC,
        CASE
            WHEN b.activity = 'blocking'
            THEN -1
            ELSE +1
        END
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting to #available_plans_sh', 0, 1) WITH NOWAIT;
    END;

    SELECT DISTINCT
        b.*
    INTO #available_plans_sh
    FROM
    (
        SELECT
            available_plans =
                'available_plans',
            b.currentdbname,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks_sh AS b
        CROSS APPLY b.blocked_process_report.nodes('/blocked-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE (b.currentdbname = @database_name
                OR @database_name IS NULL)

        UNION ALL

        SELECT
            available_plans =
                'available_plans',
            b.currentdbname,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks_sh AS b
        CROSS APPLY b.blocked_process_report.nodes('/blocking-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE (b.currentdbname = @database_name
                OR @database_name IS NULL)
    ) AS b
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#available_plans_sh',
            aps.*
        FROM #available_plans_sh AS aps
        OPTION(RECOMPILE);

        RAISERROR('Inserting to #dm_exec_query_stats_sh', 0, 1) WITH NOWAIT;
    END;

    SELECT
        deqs.sql_handle,
        deqs.plan_handle,
        deqs.statement_start_offset,
        deqs.statement_end_offset,
        deqs.creation_time,
        deqs.last_execution_time,
        deqs.execution_count,
        total_worker_time_ms =
            deqs.total_worker_time / 1000.,
        avg_worker_time_ms =
            CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / NULLIF(deqs.execution_count, 0)),
        total_elapsed_time_ms =
            deqs.total_elapsed_time / 1000.,
        avg_elapsed_time_ms =
            CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
        executions_per_second =
            ISNULL
            (
                deqs.execution_count /
                    NULLIF
                    (
                        DATEDIFF
                        (
                            SECOND,
                            deqs.creation_time,
                            NULLIF(deqs.last_execution_time, '19000101')
                        ),
                        0
                    ),
                    0
            ),
        total_physical_reads_mb =
            deqs.total_physical_reads * 8. / 1024.,
        total_logical_writes_mb =
            deqs.total_logical_writes * 8. / 1024.,
        total_logical_reads_mb =
            deqs.total_logical_reads * 8. / 1024.,
        min_grant_mb =
            deqs.min_grant_kb / 1024.,
        max_grant_mb =
            deqs.max_grant_kb / 1024.,
        min_used_grant_mb =
            deqs.min_used_grant_kb / 1024.,
        max_used_grant_mb =
            deqs.max_used_grant_kb / 1024.,
        deqs.min_reserved_threads,
        deqs.max_reserved_threads,
        deqs.min_used_threads,
        deqs.max_used_threads,
        deqs.total_rows,
        max_worker_time_ms =
            deqs.max_worker_time / 1000.,
        max_elapsed_time_ms =
            deqs.max_elapsed_time / 1000.
    INTO #dm_exec_query_stats_sh
    FROM sys.dm_exec_query_stats AS deqs
    WHERE EXISTS
    (
        SELECT
            1/0
        FROM #available_plans_sh AS ap
        WHERE ap.sql_handle = deqs.sql_handle
    )
    AND deqs.query_hash IS NOT NULL
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Creating clustered index on #dm_exec_query_stats_sh', 0, 1) WITH NOWAIT;
    END;

    CREATE CLUSTERED INDEX
        deqs_sh
    ON #dm_exec_query_stats_sh
    (
        sql_handle,
        plan_handle
    );

    SELECT
        ap.available_plans,
        ap.currentdbname,
        query_text =
            TRY_CAST(ap.query_text AS xml),
        ap.query_plan,
        ap.creation_time,
        ap.last_execution_time,
        ap.execution_count,
        ap.executions_per_second,
        ap.total_worker_time_ms,
        ap.avg_worker_time_ms,
        ap.max_worker_time_ms,
        ap.total_elapsed_time_ms,
        ap.avg_elapsed_time_ms,
        ap.max_elapsed_time_ms,
        ap.total_logical_reads_mb,
        ap.total_physical_reads_mb,
        ap.total_logical_writes_mb,
        ap.min_grant_mb,
        ap.max_grant_mb,
        ap.min_used_grant_mb,
        ap.max_used_grant_mb,
        ap.min_reserved_threads,
        ap.max_reserved_threads,
        ap.min_used_threads,
        ap.max_used_threads,
        ap.total_rows,
        ap.sql_handle,
        ap.statement_start_offset,
        ap.statement_end_offset
    FROM
    (
        SELECT
            ap.*,
            c.statement_start_offset,
            c.statement_end_offset,
            c.creation_time,
            c.last_execution_time,
            c.execution_count,
            c.total_worker_time_ms,
            c.avg_worker_time_ms,
            c.total_elapsed_time_ms,
            c.avg_elapsed_time_ms,
            c.executions_per_second,
            c.total_physical_reads_mb,
            c.total_logical_writes_mb,
            c.total_logical_reads_mb,
            c.min_grant_mb,
            c.max_grant_mb,
            c.min_used_grant_mb,
            c.max_used_grant_mb,
            c.min_reserved_threads,
            c.max_reserved_threads,
            c.min_used_threads,
            c.max_used_threads,
            c.total_rows,
            c.query_plan,
            c.max_worker_time_ms,
            c.max_elapsed_time_ms
        FROM #available_plans_sh AS ap
        OUTER APPLY
        (
            SELECT
                deqs.*,
                query_plan =
                    TRY_CAST(deps.query_plan AS xml)
            FROM #dm_exec_query_stats_sh AS deqs
            OUTER APPLY sys.dm_exec_text_query_plan
            (
                deqs.plan_handle,
                deqs.statement_start_offset,
                deqs.statement_end_offset
            ) AS deps
            WHERE deqs.sql_handle = ap.sql_handle
        ) AS c
    ) AS ap
    WHERE ap.query_plan IS NOT NULL
    ORDER BY
        ap.avg_worker_time_ms DESC
    OPTION(RECOMPILE, LOOP JOIN, HASH JOIN);
    RETURN;
    /*End system health section, skips checks because most of them won't run*/
END;

TableMode:
IF LOWER(@target_type) = N'table'
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Extracting blocked process reports from table %s.%s.%s', 0, 1, @target_database, @target_schema, @target_table) WITH NOWAIT;
    END;

    /* Build dynamic SQL to extract the XML */
    SET @extract_sql = N'
    SELECT TOP (' + CONVERT(nvarchar(20), CASE WHEN @max_blocking_events > 0 THEN @max_blocking_events ELSE 2147483647 END) + N')
        human_events_xml = e.x.query(''.'')
    FROM ' +
    QUOTENAME(@target_database) +
    N'.' +
    QUOTENAME(@target_schema) +
    N'.' +
    QUOTENAME(@target_table) +
    N' AS x
    CROSS APPLY x.' +
    QUOTENAME(@target_column) +
    N'.nodes(''/event'') AS e(x)
    WHERE e.x.exist(''@name[ .= "blocked_process_report"]'') = 1';

    /* Add timestamp filtering if specified*/
    IF @timestamp_column IS NOT NULL
    BEGIN
            SET @extract_sql = @extract_sql + N'
    AND   x.' + QUOTENAME(@timestamp_column) + N' >= @start_date
    AND   x.' + QUOTENAME(@timestamp_column) + N' < @end_date';
    END;

    IF @timestamp_column IS NULL
    BEGIN
        SET @extract_sql = @extract_sql + N'
    AND   e.x.exist(''@timestamp[. >= sql:variable("@start_date") and . < sql:variable("@end_date")]'') = 1';
    END;

    SET @extract_sql = @extract_sql + N'
    OPTION(RECOMPILE);
    ';

    IF @debug = 1
    BEGIN
        PRINT @extract_sql;
    END;

    /* Execute the dynamic SQL*/
    INSERT
        #blocking_xml
    WITH
        (TABLOCKX)
    (
        human_events_xml
    )
    EXECUTE sys.sp_executesql
        @extract_sql,
      N'@start_date datetime2,
        @end_date datetime2',
        @start_date,
        @end_date;
END;

IF @debug = 1
BEGIN
    SELECT
        table_name = N'#blocking_xml',
        bx.*
    FROM #blocking_xml AS bx
    OPTION(RECOMPILE);

    RAISERROR('Inserting to #blocked', 0, 1) WITH NOWAIT;
END;

SELECT
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ),
            c.value('@timestamp', 'datetime2')
        ),
    database_name = DB_NAME(c.value('(data[@name="database_id"]/value/text())[1]', 'integer')),
    database_id = c.value('(data[@name="database_id"]/value/text())[1]', 'integer'),
    object_id = c.value('(data[@name="object_id"]/value/text())[1]', 'integer'),
    transaction_id = c.value('(data[@name="transaction_id"]/value/text())[1]', 'bigint'),
    resource_owner_type = c.value('(data[@name="resource_owner_type"]/text)[1]', 'nvarchar(256)'),
    monitor_loop = c.value('(//@monitorLoop)[1]', 'integer'),
    blocking_spid = bg.value('(process/@spid)[1]', 'integer'),
    blocking_ecid = bg.value('(process/@ecid)[1]', 'integer'),
    blocked_spid = bd.value('(process/@spid)[1]', 'integer'),
    blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'),
    query_text_pre = bd.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
    wait_time = bd.value('(process/@waittime)[1]', 'bigint'),
    transaction_name = bd.value('(process/@transactionname)[1]', 'nvarchar(1024)'),
    last_transaction_started = bd.value('(process/@lasttranstarted)[1]', 'datetime2'),
    last_transaction_completed = bd.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
    wait_resource = bd.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
    lock_mode = bd.value('(process/@lockMode)[1]', 'nvarchar(10)'),
    status = bd.value('(process/@status)[1]', 'nvarchar(10)'),
    priority = bd.value('(process/@priority)[1]', 'integer'),
    transaction_count = bd.value('(process/@trancount)[1]', 'integer'),
    client_app = bd.value('(process/@clientapp)[1]', 'nvarchar(256)'),
    host_name = bd.value('(process/@hostname)[1]', 'nvarchar(256)'),
    login_name = bd.value('(process/@loginname)[1]', 'nvarchar(256)'),
    isolation_level = bd.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
    log_used = bd.value('(process/@logused)[1]', 'bigint'),
    clientoption1 = bd.value('(process/@clientoption1)[1]', 'bigint'),
    clientoption2 = bd.value('(process/@clientoption2)[1]', 'bigint'),
    currentdbname = bd.value('(process/@currentdbname)[1]', 'nvarchar(256)'),
    currentdbid = bd.value('(process/@currentdb)[1]', 'integer'),
    blocking_level = 0,
    sort_order = CONVERT(varchar(400), ''),
    activity = CASE WHEN oa.c.exist('//blocked-process-report/blocked-process') = 1 THEN 'blocked' END,
    blocked_process_report = c.query('.')
INTO #blocked
FROM #blocking_xml AS bx
OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
OPTION(RECOMPILE);

IF @debug = 1
BEGIN
    RAISERROR('Adding query_text to #blocked', 0, 1) WITH NOWAIT;
END;

ALTER TABLE
    #blocked
ADD query_text AS
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       query_text_pre COLLATE Latin1_General_BIN2,
   NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
   NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
   NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
PERSISTED;

IF @debug = 1
BEGIN
    RAISERROR('Adding blocking_desc to #blocked', 0, 1) WITH NOWAIT;
END;

ALTER TABLE
    #blocked
ADD blocking_desc AS
        ISNULL
        (
            '(' +
            CONVERT(varchar(10), blocking_spid) +
            ':' +
            CONVERT(varchar(10), blocking_ecid) +
            ')',
            'unresolved process'
        ) PERSISTED,
    blocked_desc AS
        '(' +
        CONVERT(varchar(10), blocked_spid) +
        ':' +
        CONVERT(varchar(10), blocked_ecid) +
        ')' PERSISTED;

IF @debug = 1
BEGIN
    RAISERROR('Adding indexes to to #blocked', 0, 1) WITH NOWAIT;
END;

CREATE CLUSTERED INDEX
    blocking
ON #blocked
    (monitor_loop, blocking_desc, blocked_desc);

CREATE INDEX
    blocked
ON #blocked
    (monitor_loop, blocked_desc, blocking_desc);

IF @debug = 1
BEGIN
    SELECT
        '#blocked' AS table_name,
        wa.*
    FROM #blocked AS wa
    OPTION(RECOMPILE);

    RAISERROR('Inserting to #blocking', 0, 1) WITH NOWAIT;
END;

SELECT
    event_time =
        DATEADD
        (
            MINUTE,
            DATEDIFF
            (
                MINUTE,
                GETUTCDATE(),
                SYSDATETIME()
            ),
            c.value('@timestamp', 'datetime2')
        ),
    database_name = DB_NAME(c.value('(data[@name="database_id"]/value/text())[1]', 'integer')),
    database_id = c.value('(data[@name="database_id"]/value/text())[1]', 'integer'),
    object_id = c.value('(data[@name="object_id"]/value/text())[1]', 'integer'),
    transaction_id = c.value('(data[@name="transaction_id"]/value/text())[1]', 'bigint'),
    resource_owner_type = c.value('(data[@name="resource_owner_type"]/text)[1]', 'nvarchar(256)'),
    monitor_loop = c.value('(//@monitorLoop)[1]', 'integer'),
    blocking_spid = bg.value('(process/@spid)[1]', 'integer'),
    blocking_ecid = bg.value('(process/@ecid)[1]', 'integer'),
    blocked_spid = bd.value('(process/@spid)[1]', 'integer'),
    blocked_ecid = bd.value('(process/@ecid)[1]', 'integer'),
    query_text_pre = bg.value('(process/inputbuf/text())[1]', 'nvarchar(max)'),
    wait_time = bg.value('(process/@waittime)[1]', 'bigint'),
    transaction_name = bg.value('(process/@transactionname)[1]', 'nvarchar(1024)'),
    last_transaction_started = bg.value('(process/@lastbatchstarted)[1]', 'datetime2'),
    last_transaction_completed = bg.value('(process/@lastbatchcompleted)[1]', 'datetime2'),
    wait_resource = bg.value('(process/@waitresource)[1]', 'nvarchar(1024)'),
    lock_mode = bg.value('(process/@lockMode)[1]', 'nvarchar(10)'),
    status = bg.value('(process/@status)[1]', 'nvarchar(10)'),
    priority = bg.value('(process/@priority)[1]', 'integer'),
    transaction_count = bg.value('(process/@trancount)[1]', 'integer'),
    client_app = bg.value('(process/@clientapp)[1]', 'nvarchar(256)'),
    host_name = bg.value('(process/@hostname)[1]', 'nvarchar(256)'),
    login_name = bg.value('(process/@loginname)[1]', 'nvarchar(256)'),
    isolation_level = bg.value('(process/@isolationlevel)[1]', 'nvarchar(50)'),
    log_used = bg.value('(process/@logused)[1]', 'bigint'),
    clientoption1 = bg.value('(process/@clientoption1)[1]', 'bigint'),
    clientoption2 = bg.value('(process/@clientoption2)[1]', 'bigint'),
    currentdbname = bg.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
    currentdbid = bg.value('(process/@currentdb)[1]', 'integer'),
    blocking_level = 0,
    sort_order = CONVERT(varchar(400), ''),
    activity = CASE WHEN oa.c.exist('//blocked-process-report/blocking-process') = 1 THEN 'blocking' END,
    blocked_process_report = c.query('.')
INTO #blocking
FROM #blocking_xml AS bx
OUTER APPLY bx.human_events_xml.nodes('/event') AS oa(c)
OUTER APPLY oa.c.nodes('//blocked-process-report/blocked-process') AS bd(bd)
OUTER APPLY oa.c.nodes('//blocked-process-report/blocking-process') AS bg(bg)
OPTION(RECOMPILE);

IF @debug = 1
BEGIN
    RAISERROR('Adding query_text to to #blocking', 0, 1) WITH NOWAIT;
END;

ALTER TABLE
    #blocking
ADD query_text AS
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
   REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
       query_text_pre COLLATE Latin1_General_BIN2,
   NCHAR(31),N'?'),NCHAR(30),N'?'),NCHAR(29),N'?'),NCHAR(28),N'?'),NCHAR(27),N'?'),NCHAR(26),N'?'),NCHAR(25),N'?'),NCHAR(24),N'?'),NCHAR(23),N'?'),NCHAR(22),N'?'),
   NCHAR(21),N'?'),NCHAR(20),N'?'),NCHAR(19),N'?'),NCHAR(18),N'?'),NCHAR(17),N'?'),NCHAR(16),N'?'),NCHAR(15),N'?'),NCHAR(14),N'?'),NCHAR(12),N'?'),
   NCHAR(11),N'?'),NCHAR(8),N'?'),NCHAR(7),N'?'),NCHAR(6),N'?'),NCHAR(5),N'?'),NCHAR(4),N'?'),NCHAR(3),N'?'),NCHAR(2),N'?'),NCHAR(1),N'?'),NCHAR(0),N'?')
PERSISTED;

IF @debug = 1
BEGIN
    RAISERROR('Adding blocking_desc to to #blocking', 0, 1) WITH NOWAIT;
END;

ALTER TABLE
    #blocking
ADD blocking_desc AS
        ISNULL
        (
            '(' +
            CONVERT(varchar(10), blocking_spid) +
            ':' +
            CONVERT(varchar(10), blocking_ecid) +
            ')',
            'unresolved process'
        ) PERSISTED,
    blocked_desc AS
        '(' +
        CONVERT(varchar(10), blocked_spid) +
        ':' +
        CONVERT(varchar(10), blocked_ecid) +
        ')' PERSISTED;

IF @debug = 1
BEGIN
    RAISERROR('Creating indexes on #blocking', 0, 1) WITH NOWAIT;
END;

CREATE CLUSTERED INDEX
    blocking
ON #blocking
    (monitor_loop, blocking_desc, blocked_desc);

CREATE INDEX
    blocked
ON #blocking
    (monitor_loop, blocked_desc, blocking_desc);

IF @debug = 1
BEGIN
    SELECT
        '#blocking' AS table_name,
        wa.*
    FROM #blocking AS wa
    OPTION(RECOMPILE);

    RAISERROR('Updating #blocked', 0, 1) WITH NOWAIT;
END;

WITH
    hierarchy AS
(
    SELECT
        b.monitor_loop,
        blocking_desc,
        blocked_desc,
        level = 0,
        sort_order =
            CONVERT
            (
                varchar(400),
                blocking_desc +
                ' </* ' +
                blocked_desc
            )
    FROM #blocking AS b
    WHERE NOT EXISTS
    (
        SELECT
            1/0
        FROM #blocking AS b2
        WHERE b2.monitor_loop = b.monitor_loop
        AND   b2.blocked_desc = b.blocking_desc
    )

    UNION ALL

    SELECT
        bg.monitor_loop,
        bg.blocking_desc,
        bg.blocked_desc,
        h.level + 1,
        sort_order =
            CONVERT
            (
                varchar(400),
                h.sort_order +
                ' ' +
                bg.blocking_desc +
                ' </* ' +
                bg.blocked_desc
            )
    FROM hierarchy AS h
    JOIN #blocking AS bg
      ON  bg.monitor_loop = h.monitor_loop
      AND bg.blocking_desc = h.blocked_desc
)
UPDATE
    #blocked
SET
    blocking_level = h.level,
    sort_order = h.sort_order
FROM #blocked AS b
JOIN hierarchy AS h
  ON  h.monitor_loop = b.monitor_loop
  AND h.blocking_desc = b.blocking_desc
  AND h.blocked_desc = b.blocked_desc
OPTION(RECOMPILE, MAXRECURSION 0);

IF @debug = 1
BEGIN
    RAISERROR('Updating #blocking', 0, 1) WITH NOWAIT;
END;

UPDATE
    #blocking
SET
    blocking_level = bd.blocking_level,
    sort_order = bd.sort_order
FROM #blocking AS bg
JOIN #blocked AS bd
  ON  bd.monitor_loop = bg.monitor_loop
  AND bd.blocking_desc = bg.blocking_desc
  AND bd.blocked_desc = bg.blocked_desc
OPTION(RECOMPILE);

IF @debug = 1
BEGIN
    RAISERROR('Inserting #blocks', 0, 1) WITH NOWAIT;
END;

SELECT
    kheb.event_time,
    kheb.database_name,
    kheb.object_id,
    contentious_object = CONVERT(nvarchar(4000), NULL),
    kheb.activity,
    blocking_tree =
        REPLICATE(' > ', kheb.blocking_level) +
        CASE kheb.activity
             WHEN 'blocking'
             THEN '(' + kheb.blocking_desc + ') is blocking (' + kheb.blocked_desc + ')'
             ELSE ' > (' + kheb.blocked_desc + ') is blocked by (' + kheb.blocking_desc + ')'
        END,
    spid =
        CASE kheb.activity
             WHEN 'blocking'
             THEN kheb.blocking_spid
             ELSE kheb.blocked_spid
        END,
    ecid =
        CASE kheb.activity
             WHEN 'blocking'
             THEN kheb.blocking_ecid
             ELSE kheb.blocked_ecid
        END,
    query_text =
        CONVERT(xml, NULL),
    query_text_pre = kheb.query_text,
    wait_time_ms =
        kheb.wait_time,
    kheb.status,
    kheb.isolation_level,
    kheb.lock_mode,
    kheb.resource_owner_type,
    kheb.transaction_count,
    kheb.transaction_name,
    kheb.last_transaction_started,
    kheb.last_transaction_completed,
    client_option_1 =
        SUBSTRING
        (
            CASE WHEN kheb.clientoption1 & 1 = 1 THEN ', DISABLE_DEF_CNST_CHECK' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 2 = 2 THEN ', IMPLICIT_TRANSACTIONS' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 4 = 4 THEN ', CURSOR_CLOSE_ON_COMMIT' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 8 = 8 THEN ', ANSI_WARNINGS' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 16 = 16 THEN ', ANSI_PADDING' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 32 = 32 THEN ', ANSI_NULLS' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 64 = 64 THEN ', ARITHABORT' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 128 = 128 THEN ', ARITHIGNORE' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 256 = 256 THEN ', QUOTED_IDENTIFIER' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 512 = 512 THEN ', NOCOUNT' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 1024 = 1024 THEN ', ANSI_NULL_DFLT_ON' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 2048 = 2048 THEN ', ANSI_NULL_DFLT_OFF' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 4096 = 4096 THEN ', CONCAT_NULL_YIELDS_NULL' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 8192 = 8192 THEN ', NUMERIC_ROUNDABORT' ELSE '' END +
            CASE WHEN kheb.clientoption1 & 16384 = 16384 THEN ', XACT_ABORT' ELSE '' END,
            3,
            8000
        ),
    client_option_2 =
        SUBSTRING
        (
            CASE WHEN kheb.clientoption2 & 1024 = 1024 THEN ', DB CHAINING' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 2048 = 2048 THEN ', NUMERIC ROUNDABORT' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 4096 = 4096 THEN ', ARITHABORT' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 8192 = 8192 THEN ', ANSI PADDING' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 16384 = 16384 THEN ', ANSI NULL DEFAULT' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 65536 = 65536 THEN ', CONCAT NULL YIELDS NULL' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 131072 = 131072 THEN ', RECURSIVE TRIGGERS' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 1048576 = 1048576 THEN ', DEFAULT TO LOCAL CURSOR' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 8388608 = 8388608 THEN ', QUOTED IDENTIFIER' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 16777216 = 16777216 THEN ', AUTO CREATE STATISTICS' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 33554432 = 33554432 THEN ', CURSOR CLOSE ON COMMIT' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 67108864 = 67108864 THEN ', ANSI NULLS' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 268435456 = 268435456 THEN ', ANSI WARNINGS' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 536870912 = 536870912 THEN ', FULL TEXT ENABLED' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 1073741824 = 1073741824 THEN ', AUTO UPDATE STATISTICS' ELSE '' END +
            CASE WHEN kheb.clientoption2 & 1469283328 = 1469283328 THEN ', ALL SETTABLE OPTIONS' ELSE '' END,
            3,
            8000
        ),
    kheb.wait_resource,
    kheb.priority,
    kheb.log_used,
    kheb.client_app,
    kheb.host_name,
    kheb.login_name,
    kheb.transaction_id,
    kheb.database_id,
    kheb.currentdbname,
    kheb.currentdbid,
    kheb.blocked_process_report,
    kheb.sort_order
INTO #blocks
FROM
(
    SELECT
        bg.*
    FROM #blocking AS bg
    WHERE
    (
         @database_name IS NULL
      OR bg.database_name = @database_name
      OR bg.currentdbname = @database_name
    )

    UNION ALL

    SELECT
        bd.*
    FROM #blocked AS bd
    WHERE
    (
         @database_name IS NULL
      OR bd.database_name = @database_name
      OR bd.currentdbname = @database_name
    )
) AS kheb
OPTION(RECOMPILE);

IF @debug = 1
BEGIN
    RAISERROR('Updating #blocks query_text column', 0, 1) WITH NOWAIT;
END;

UPDATE
    kheb
SET
    kheb.query_text = qt.query_text
FROM #blocks AS kheb
CROSS APPLY
(
    SELECT
        query_text =
        CASE
            WHEN kheb.query_text_pre LIKE @inputbuf_bom + N'Proc |[Database Id = %' ESCAPE N'|'
            THEN
                (
                    SELECT
                        [processing-instruction(query)] =
                            OBJECT_SCHEMA_NAME
                            (
                                    SUBSTRING
                                    (
                                        kheb.query_text_pre,
                                        CHARINDEX(N'Object Id = ', kheb.query_text_pre) + 12,
                                        LEN(kheb.query_text_pre) - (CHARINDEX(N'Object Id = ', kheb.query_text_pre) + 12)
                                    )
                                    ,
                                    SUBSTRING
                                    (
                                        kheb.query_text_pre,
                                        CHARINDEX(N'Database Id = ', kheb.query_text_pre) + 14,
                                        CHARINDEX(N'Object Id', kheb.query_text_pre) - (CHARINDEX(N'Database Id = ', kheb.query_text_pre) + 14)
                                    )
                            ) +
                            N'.' +
                            OBJECT_NAME
                            (
                                 SUBSTRING
                                 (
                                     kheb.query_text_pre,
                                     CHARINDEX(N'Object Id = ', kheb.query_text_pre) + 12,
                                     LEN(kheb.query_text_pre) - (CHARINDEX(N'Object Id = ', kheb.query_text_pre) + 12)
                                 )
                                 ,
                                 SUBSTRING
                                 (
                                     kheb.query_text_pre,
                                     CHARINDEX(N'Database Id = ', kheb.query_text_pre) + 14,
                                     CHARINDEX(N'Object Id', kheb.query_text_pre) - (CHARINDEX(N'Database Id = ', kheb.query_text_pre) + 14)
                                 )
                            )
                    FOR XML
                        PATH(N''),
                        TYPE
                )
            ELSE
                (
                    SELECT
                        [processing-instruction(query)] =
                            kheb.query_text_pre
                    FOR XML
                        PATH(N''),
                        TYPE
                )
        END
) AS qt
OPTION(RECOMPILE);

IF @debug = 1
BEGIN
    RAISERROR('Updating #blocks contentious_object column', 0, 1) WITH NOWAIT;
END;

UPDATE
    b
SET
    b.contentious_object =
    ISNULL
    (
        co.contentious_object,
        N'Unresolved: ' +
        N'database: ' +
        ISNULL(b.database_name, N'unknown') +
        N' object_id: ' +
        ISNULL(RTRIM(b.object_id), N'unknown')
    )
FROM #blocks AS b
CROSS APPLY
(
    SELECT
        contentious_object =
            OBJECT_SCHEMA_NAME
            (
                b.object_id,
                b.database_id
            ) +
            N'.' +
            OBJECT_NAME
            (
                b.object_id,
                b.database_id
            )
) AS co
OPTION(RECOMPILE);

/*Either return results or log to a table*/
SET @dsql = N'
SELECT
    blocked_process_report =
        ''blocked_process_report'',
    b.event_time,
    b.database_name,
    b.currentdbname,
    b.contentious_object,
    b.activity,
    b.blocking_tree,
    b.spid,
    b.ecid,
    b.query_text,
    b.wait_time_ms,
    b.status,
    b.isolation_level,
    b.lock_mode,
    b.resource_owner_type,
    b.transaction_count,
    b.transaction_name,
    b.last_transaction_started,
    b.last_transaction_completed,
    b.client_option_1,
    b.client_option_2,
    b.wait_resource,
    b.priority,
    b.log_used,
    b.client_app,
    b.host_name,
    b.login_name,
    b.transaction_id,
    blocked_process_report_xml =
        b.blocked_process_report
FROM
(
    SELECT
        b.*,
        n =
            ROW_NUMBER() OVER
            (
                PARTITION BY
                    b.transaction_id,
                    b.spid,
                    b.ecid
                ORDER BY
                    b.event_time DESC
            )
    FROM #blocks AS b
) AS b
WHERE b.n = 1
AND  (b.contentious_object = @object_name
      OR @object_name IS NULL)';

/* Add the WHERE clause only for table logging */
IF @log_to_table = 1
BEGIN
    SET @mdsql =
        REPLACE
        (
            REPLACE
            (
                @mdsql,
                '{table_check}',
                @log_table_blocking
            ),
            '{date_column}',
            'event_time'
        );

    IF @debug = 1 BEGIN PRINT @mdsql; END;

    EXECUTE sys.sp_executesql
        @mdsql,
      N'@max_event_time datetime2(7) OUTPUT',
        @max_event_time OUTPUT;

    SET @mdsql =
        REPLACE
        (
            REPLACE
            (
                @mdsql,
                @log_table_blocking,
                '{table_check}'
            ),
            'event_time',
            '{date_column}'
        );

    SET @dsql += N'
AND   b.event_time > @max_event_time';
END;

/* Add the ORDER BY clause */
SET @dsql += N'
ORDER BY
    b.event_time,
    b.sort_order,
    CASE
        WHEN b.activity = ''blocking''
        THEN -1
        ELSE +1
    END
OPTION(RECOMPILE);';

/* Handle table logging */
IF @log_to_table = 1
BEGIN
    SET @insert_sql = N'
INSERT INTO
    ' + @log_table_blocking + N'
(
    blocked_process_report,
    event_time,
    database_name,
    currentdbname,
    contentious_object,
    activity,
    blocking_tree,
    spid,
    ecid,
    query_text,
    wait_time_ms,
    status,
    isolation_level,
    lock_mode,
    resource_owner_type,
    transaction_count,
    transaction_name,
    last_transaction_started,
    last_transaction_completed,
    client_option_1,
    client_option_2,
    wait_resource,
    priority,
    log_used,
    client_app,
    host_name,
    login_name,
    transaction_id,
    blocked_process_report_xml
)' +
    @dsql;

    IF @debug = 1 BEGIN PRINT @insert_sql; END;

    EXECUTE sys.sp_executesql
        @insert_sql,
      N'@max_event_time datetime2(7),
        @object_name sysname',
        @max_event_time,
        @object_name;
END;

/* Execute the query for client results */
IF @log_to_table = 0
BEGIN

    IF @debug = 1 BEGIN PRINT @dsql; END;

    EXECUTE sys.sp_executesql
        @dsql,
      N'@object_name sysname',
        @object_name;
END;

/*
Only run query plan and check stuff
when not logging to a table
*/
IF @log_to_table = 0
BEGIN
    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #available_plans', 0, 1) WITH NOWAIT;
    END;

    SELECT DISTINCT
        b.*
    INTO #available_plans
    FROM
    (
        SELECT
            available_plans =
                'available_plans',
            b.database_name,
            b.database_id,
            b.currentdbname,
            b.currentdbid,
            b.contentious_object,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks AS b
        CROSS APPLY b.blocked_process_report.nodes('/event/data/value/blocked-process-report/blocking-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE
        (
            (b.database_name = @database_name
                OR @database_name IS NULL)
         OR (b.currentdbname = @database_name
                OR @database_name IS NULL)
        )
        AND  (b.contentious_object = @object_name
                OR @object_name IS NULL)

        UNION ALL

        SELECT
            available_plans =
                'available_plans',
            b.database_name,
            b.database_id,
            b.currentdbname,
            b.currentdbid,
            b.contentious_object,
            query_text =
                TRY_CAST(b.query_text AS nvarchar(max)),
            sql_handle =
                CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
            stmtstart =
                ISNULL(n.c.value('@stmtstart', 'integer'), 0),
            stmtend =
                ISNULL(n.c.value('@stmtend', 'integer'), -1)
        FROM #blocks AS b
        CROSS APPLY b.blocked_process_report.nodes('/event/data/value/blocked-process-report/blocked-process/process/executionStack/frame[not(@sqlhandle = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")]') AS n(c)
        WHERE
        (
            (b.database_name = @database_name
                OR @database_name IS NULL)
         OR (b.currentdbname = @database_name
                OR @database_name IS NULL)
        )
        AND  (b.contentious_object = @object_name
                OR @object_name IS NULL)
    ) AS b
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            '#available_plans' AS table_name,
            ap.*
        FROM #available_plans AS ap
        OPTION(RECOMPILE);

        RAISERROR('Inserting #dm_exec_query_stats', 0, 1) WITH NOWAIT;
    END;

    SELECT
        deqs.sql_handle,
        deqs.plan_handle,
        deqs.statement_start_offset,
        deqs.statement_end_offset,
        deqs.creation_time,
        deqs.last_execution_time,
        deqs.execution_count,
        total_worker_time_ms =
            deqs.total_worker_time / 1000.,
        avg_worker_time_ms =
            CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / NULLIF(deqs.execution_count, 0)),
        total_elapsed_time_ms =
            deqs.total_elapsed_time / 1000.,
        avg_elapsed_time_ms =
            CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
        executions_per_second =
            ISNULL
            (
                deqs.execution_count /
                    NULLIF
                    (
                        DATEDIFF
                        (
                            SECOND,
                            deqs.creation_time,
                            NULLIF(deqs.last_execution_time, '19000101')
                        ),
                        0
                    ),
                    0
            ),
        total_physical_reads_mb =
            deqs.total_physical_reads * 8. / 1024.,
        total_logical_writes_mb =
            deqs.total_logical_writes * 8. / 1024.,
        total_logical_reads_mb =
            deqs.total_logical_reads * 8. / 1024.,
        min_grant_mb =
            deqs.min_grant_kb / 1024.,
        max_grant_mb =
            deqs.max_grant_kb / 1024.,
        min_used_grant_mb =
            deqs.min_used_grant_kb / 1024.,
        max_used_grant_mb =
            deqs.max_used_grant_kb / 1024.,
        deqs.min_reserved_threads,
        deqs.max_reserved_threads,
        deqs.min_used_threads,
        deqs.max_used_threads,
        deqs.total_rows,
        max_worker_time_ms =
            deqs.max_worker_time / 1000.,
        max_elapsed_time_ms =
            deqs.max_elapsed_time / 1000.
    INTO #dm_exec_query_stats
    FROM sys.dm_exec_query_stats AS deqs
    WHERE EXISTS
    (
       SELECT
           1/0
       FROM #available_plans AS ap
       WHERE ap.sql_handle = deqs.sql_handle
    )
    AND deqs.query_hash IS NOT NULL
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Creating index on #dm_exec_query_stats', 0, 1) WITH NOWAIT;
    END;

    CREATE CLUSTERED INDEX
        deqs
    ON #dm_exec_query_stats
    (
        sql_handle,
        plan_handle
    );

    SELECT
        ap.available_plans,
        ap.database_name,
        ap.currentdbname,
        query_text =
            TRY_CAST(ap.query_text AS xml),
        ap.query_plan,
        ap.creation_time,
        ap.last_execution_time,
        ap.execution_count,
        ap.executions_per_second,
        ap.total_worker_time_ms,
        ap.avg_worker_time_ms,
        ap.max_worker_time_ms,
        ap.total_elapsed_time_ms,
        ap.avg_elapsed_time_ms,
        ap.max_elapsed_time_ms,
        ap.total_logical_reads_mb,
        ap.total_physical_reads_mb,
        ap.total_logical_writes_mb,
        ap.min_grant_mb,
        ap.max_grant_mb,
        ap.min_used_grant_mb,
        ap.max_used_grant_mb,
        ap.min_reserved_threads,
        ap.max_reserved_threads,
        ap.min_used_threads,
        ap.max_used_threads,
        ap.total_rows,
        ap.sql_handle,
        ap.statement_start_offset,
        ap.statement_end_offset
    FROM
    (

        SELECT
            ap.*,
            c.statement_start_offset,
            c.statement_end_offset,
            c.creation_time,
            c.last_execution_time,
            c.execution_count,
            c.total_worker_time_ms,
            c.avg_worker_time_ms,
            c.total_elapsed_time_ms,
            c.avg_elapsed_time_ms,
            c.executions_per_second,
            c.total_physical_reads_mb,
            c.total_logical_writes_mb,
            c.total_logical_reads_mb,
            c.min_grant_mb,
            c.max_grant_mb,
            c.min_used_grant_mb,
            c.max_used_grant_mb,
            c.min_reserved_threads,
            c.max_reserved_threads,
            c.min_used_threads,
            c.max_used_threads,
            c.total_rows,
            c.query_plan,
            c.max_worker_time_ms,
            c.max_elapsed_time_ms
        FROM #available_plans AS ap
        OUTER APPLY
        (
            SELECT
                deqs.*,
                query_plan =
                    TRY_CAST(deps.query_plan AS xml)
            FROM #dm_exec_query_stats AS deqs
            OUTER APPLY sys.dm_exec_text_query_plan
            (
                deqs.plan_handle,
                deqs.statement_start_offset,
                deqs.statement_end_offset
            ) AS deps
            WHERE deqs.sql_handle = ap.sql_handle
            AND   deps.dbid IN (ap.database_id, ap.currentdbid)
        ) AS c
    ) AS ap
    WHERE ap.query_plan IS NOT NULL
    ORDER BY
        ap.avg_worker_time_ms DESC
    OPTION(RECOMPILE, LOOP JOIN, HASH JOIN);

    /* Capture actual date range and event count from the data */
    SELECT
        @actual_start_date = MIN(event_time),
        @actual_end_date = MAX(event_time),
        @actual_event_count = COUNT_BIG(DISTINCT monitor_loop)
    FROM #blocked
    WHERE event_time IS NOT NULL;

    /* Use original dates if no data found */
    SELECT
        @actual_start_date = ISNULL(@actual_start_date, @start_date_original),
        @actual_end_date = ISNULL(@actual_end_date, @end_date_original),
        @actual_event_count = ISNULL(@actual_event_count, 0);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id -1', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id = -1,
        database_name = N'erikdarling.com',
        object_name =
            N'sp_HumanEventsBlockViewer version ' +
            CONVERT(nvarchar(30), @version) +
            N'.',
        finding_group = N'https://code.erikdarling.com',
        finding =
            N'blocking events from ' +
            CONVERT(nvarchar(30), @actual_start_date, 126) +
            N' to ' +
            CONVERT(nvarchar(30), @actual_end_date, 126) +
            N' (' + CONVERT(nvarchar(30), @actual_event_count) +
            N' total events' +
            CASE
                WHEN @max_blocking_events > 0
                AND  @actual_event_count >= @max_blocking_events
                THEN N', limited to most recent ' +
                     CONVERT(nvarchar(30), @max_blocking_events) +
                     N')'
                ELSE N')'
            END +
            N'.',
        1;

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 1', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            1,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Database Locks',
        finding =
            N'The database ' +
            b.database_name +
            N' has been involved in ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' blocking sessions.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 2', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            2,
        database_name =
            b.database_name,
        object_name =
            b.contentious_object,
        finding_group =
            N'Object Locks',
        finding =
            N'The object ' +
            b.contentious_object +
            CASE
                WHEN b.contentious_object LIKE N'Unresolved%'
                THEN N''
                ELSE N' in database ' +
                     b.database_name
            END +
            N' has been involved in ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' blocking sessions.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name,
        b.contentious_object
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 3', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            3,
        database_name =
            b.database_name,
        object_name =
            CASE
                WHEN EXISTS
                     (
                         SELECT
                             1/0
                         FROM sys.databases AS d
                         WHERE d.name COLLATE DATABASE_DEFAULT = b.database_name COLLATE DATABASE_DEFAULT
                         AND   d.is_read_committed_snapshot_on = 1
                     )
                THEN N'You already enabled RCSI, but...'
                ELSE N'You Might Need RCSI'
            END,
        finding_group =
            N'Blocking Involving Selects',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' select queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.lock_mode IN
          (
              N'S',
              N'IS'
          )
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    HAVING
        COUNT_BIG(DISTINCT b.transaction_id) > 1
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 4', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            4,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Repeatable Read Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' repeatable read queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.isolation_level LIKE N'repeatable%'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 5', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            5,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Serializable Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' serializable queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.isolation_level LIKE N'serializable%'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 6.1', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            6,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Sleeping Query Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' sleeping queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.status = N'sleeping'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 6.2', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            6,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Background Query Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' background queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.status = N'background'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 6.3', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            6,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Done Query Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' done queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.status = N'done'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 6.4', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            6,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Compile Lock Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' compile locks blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.wait_resource LIKE N'%COMPILE%'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 6.5', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            6,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Application Lock Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' application locks blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.wait_resource LIKE N'APPLICATION%'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 7.1', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            7,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Implicit Transaction Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' implicit transaction queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.transaction_name = N'implicit_transaction'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 7.2', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            7,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'User Transaction Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' user transaction queries involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.transaction_name = N'user_transaction'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 7.3', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            7,
        database_name =
            b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Auto-Stats Update Blocking',
        finding =
            N'There have been ' +
            CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
            N' auto stats updates involved in blocking sessions in ' +
            b.database_name +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE b.transaction_name = N'sqlsource_transform'
    AND   (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 8', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id = 8,
        b.database_name,
        object_name = N'-',
        finding_group = N'Login, App, and Host blocking',
        finding =
            N'This database has had ' +
            CONVERT
            (
                nvarchar(20),
                COUNT_BIG(DISTINCT b.transaction_id)
            ) +
            N' instances of blocking involving the login ' +
            ISNULL
            (
                b.login_name,
                N'UNKNOWN'
            ) +
            N' from the application ' +
            ISNULL
            (
                b.client_app,
                N'UNKNOWN'
            ) +
            N' on host ' +
            ISNULL
            (
                b.host_name,
                N'UNKNOWN'
            ) +
            N'.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY COUNT_BIG(DISTINCT b.transaction_id) DESC)
    FROM #blocks AS b
    WHERE (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name,
        b.login_name,
        b.client_app,
        b.host_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 1000', 0, 1) WITH NOWAIT;
    END;

    WITH
        b AS
    (
        SELECT
            b.database_name,
            b.transaction_id,
            wait_time_ms =
                MAX(b.wait_time_ms)
        FROM #blocks AS b
        WHERE (b.database_name = @database_name
               OR @database_name IS NULL)
        AND   (b.contentious_object = @object_name
               OR @object_name IS NULL)
        GROUP BY
            b.database_name,
            b.transaction_id
    )
    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            1000,
        b.database_name,
        object_name =
            N'-',
        finding_group =
            N'Total database block wait time',
        finding =
            N'This database has had ' +
            CONVERT
            (
                nvarchar(30),
                (
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            b.wait_time_ms
                        )
                    ) / 1000 / 86400
                )
            ) +
            N' ' +
            CONVERT
              (
                  nvarchar(30),
                  DATEADD
                  (
                      SECOND,
                      (
                          SUM
                          (
                              CONVERT
                              (
                                  bigint,
                                  b.wait_time_ms
                              )
                          ) / 1000 % 86400
                      ),
                      '19000101'
                  ),
                  14
              ) +
            N' [dd hh:mm:ss] of lock wait time.',
       sort_order =
           ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, b.wait_time_ms)) DESC)
    FROM b AS b
    WHERE (b.database_name = @database_name
           OR @database_name IS NULL)
    GROUP BY
        b.database_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 1001', 0, 1) WITH NOWAIT;
    END;

    WITH
        b AS
    (
        SELECT
            b.database_name,
            b.transaction_id,
            b.contentious_object,
            wait_time_ms =
                MAX(b.wait_time_ms)
        FROM #blocks AS b
        WHERE (b.database_name = @database_name
               OR @database_name IS NULL)
        AND   (b.contentious_object = @object_name
               OR @object_name IS NULL)
        GROUP BY
            b.database_name,
            b.contentious_object,
            b.transaction_id
    )
    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id =
            1001,
        b.database_name,
        object_name =
            b.contentious_object,
        finding_group =
            N'Total database and object block wait time',
        finding =
            N'This object has had ' +
            CONVERT
            (
                nvarchar(30),
                (
                    SUM
                    (
                        CONVERT
                        (
                            bigint,
                            b.wait_time_ms
                        )
                    ) / 1000 / 86400
                )
            ) +
            N' ' +
            CONVERT
              (
                  nvarchar(30),
                  DATEADD
                  (
                      SECOND,
                      (
                          SUM
                          (
                              CONVERT
                              (
                                  bigint,
                                  b.wait_time_ms
                              )
                          ) / 1000 % 86400
                      ),
                      '19000101'
                  ),
                  14
              ) +
            N' [dd hh:mm:ss] of lock wait time in database ' +
            b.database_name,
       sort_order =
           ROW_NUMBER() OVER (ORDER BY SUM(CONVERT(bigint, b.wait_time_ms)) DESC)
    FROM b AS b
    WHERE (b.database_name = @database_name
           OR @database_name IS NULL)
    AND   (b.contentious_object = @object_name
           OR @object_name IS NULL)
    GROUP BY
        b.database_name,
        b.contentious_object
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('Inserting #block_findings, check_id 2147483647', 0, 1) WITH NOWAIT;
    END;

    INSERT
        #block_findings
    (
        check_id,
        database_name,
        object_name,
        finding_group,
        finding,
        sort_order
    )
    SELECT
        check_id = 2147483647,
        database_name = N'erikdarling.com',
        object_name =
            N'sp_HumanEventsBlockViewer version ' +
            CONVERT(nvarchar(30), @version) + N'.',
        finding_group = N'https://code.erikdarling.com',
        finding = N'thanks for using me!',
        2147483647;

    SELECT
        findings =
             'findings',
        bf.check_id,
        bf.database_name,
        bf.object_name,
        bf.finding_group,
        bf.finding
    FROM #block_findings AS bf
    ORDER BY
        bf.check_id,
        bf.finding_group,
        bf.sort_order
    OPTION(RECOMPILE);
END;
END; --Final End
GO
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO


/*
██╗███╗   ██╗██████╗ ███████╗██╗  ██╗
██║████╗  ██║██╔══██╗██╔════╝╚██╗██╔╝
██║██╔██╗ ██║██║  ██║█████╗   ╚███╔╝
██║██║╚██╗██║██║  ██║██╔══╝   ██╔██╗
██║██║ ╚████║██████╔╝███████╗██╔╝ ██╗
╚═╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚═╝  ╚═╝

 ██████╗██╗     ███████╗ █████╗ ███╗   ██╗██╗   ██╗██████╗
██╔════╝██║     ██╔════╝██╔══██╗████╗  ██║██║   ██║██╔══██╗
██║     ██║     █████╗  ███████║██╔██╗ ██║██║   ██║██████╔╝
██║     ██║     ██╔══╝  ██╔══██║██║╚██╗██║██║   ██║██╔═══╝
╚██████╗███████╗███████╗██║  ██║██║ ╚████║╚██████╔╝██║
 ╚═════╝╚══════╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚═╝


Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_IndexCleanup
    @help = 1;

For working through errors:
EXECUTE sp_IndexCleanup
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/


IF OBJECT_ID('dbo.sp_IndexCleanup', 'P') IS NULL
BEGIN
    EXECUTE ('CREATE PROCEDURE dbo.sp_IndexCleanup AS RETURN 138;');
END;
GO

ALTER PROCEDURE
    dbo.sp_IndexCleanup
(
    @database_name sysname = NULL, /*focus on a single database*/
    @schema_name sysname = NULL, /*use when focusing on a single table/view, or to a single schema with no table name*/
    @table_name sysname = NULL, /*use when focusing on a single table or view*/
    @min_reads bigint = 0, /*only look at indexes with a minimum number of reads*/
    @min_writes bigint = 0, /*only look at indexes with a minimum number of writes*/
    @min_size_gb decimal(10,2) = 0, /*only look at indexes with a minimum size*/
    @min_rows bigint = 0, /*only look at indexes with a minimum number of rows*/
    @dedupe_only bit = 'false', /*only perform deduplication, don't mark unused indexes for removal*/
    @get_all_databases bit = 'false', /*looks for all accessible user databases and returns combined results*/
    @include_databases nvarchar(MAX) = NULL, /*comma-separated list of databases to include (only when @get_all_databases = 1)*/
    @exclude_databases nvarchar(MAX) = NULL, /*comma-separated list of databases to exclude (only when @get_all_databases = 1)*/
    @help bit = 'false', /*learn about the procedure and parameters*/
    @debug bit = 'false', /*print dynamic sql, show temp table contents*/
    @version varchar(20) = NULL OUTPUT, /*script version number*/
    @version_date datetime = NULL OUTPUT /*script version date*/
)
WITH RECOMPILE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
    SELECT
        @version = '2.3',
        @version_date = '20260301';

    IF
    /* Check SQL Server 2012+ for FORMAT and CONCAT functions */
    (
        CONVERT
        (
            integer,
            SERVERPROPERTY('EngineEdition')
        ) NOT IN (5, 8) /* Not Azure SQL DB or Managed Instance */
    AND CONVERT
        (
            integer,
            SUBSTRING
            (
                CONVERT
                (
                    varchar(20),
                    SERVERPROPERTY('ProductVersion')
                ),
                1,
                2
            )
        ) < 11) /* Pre-2012 */
    BEGIN
        RAISERROR('This procedure requires SQL Server 2012 (11.0) or later due to the use of FORMAT and CONCAT functions.', 11, 1);
        RETURN;
    END;

    /*
    Help section, for help.
    Will become more helpful when out of beta.
    */
    IF @help = 1
    BEGIN
        SELECT
            help = N'hello, i am sp_IndexCleanup'
          UNION ALL
        SELECT
            help = N'this is a script to help clean up unused and duplicate indexes.'
          UNION ALL
        SELECT
            help = N'it will also help you add page compression to uncompressed indexes.'
          UNION ALL
        SELECT
            help = N'always validate all changes against a non-production environment!'
          UNION ALL
        SELECT
            help = N'please test carefully.'
          UNION ALL
        SELECT
            help = N'brought to you by erikdarling.com / code.erikdarling.com';

        /*
        Parameters
        */
        SELECT
            parameter_name =
                ap.name,
            data_type =
                t.name,
            description =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'the name of the database you wish to analyze'
                    WHEN N'@schema_name' THEN 'limits analysis to tables in the specified schema when used without @table_name'
                    WHEN N'@table_name' THEN 'the table or view name to filter indexes by, requires @schema_name if not dbo'
                    WHEN N'@min_reads' THEN 'minimum number of reads for an index to be considered used'
                    WHEN N'@min_writes' THEN 'minimum number of writes for an index to be considered used'
                    WHEN N'@min_size_gb' THEN 'minimum size in GB for an index to be analyzed'
                    WHEN N'@min_rows' THEN 'minimum number of rows for a table to be analyzed'
                    WHEN N'@dedupe_only' THEN 'only perform index deduplication, do not mark unused indexes for removal'
                    WHEN N'@get_all_databases' THEN 'set to 1 to analyze all accessible user databases'
                    WHEN N'@include_databases' THEN 'comma-separated list of databases to include when @get_all_databases = 1'
                    WHEN N'@exclude_databases' THEN 'comma-separated list of databases to exclude when @get_all_databases = 1'
                    WHEN N'@help' THEN 'displays this help information'
                    WHEN N'@debug' THEN 'prints debug information during execution'
                    WHEN N'@version' THEN 'returns the version number of the procedure'
                    WHEN N'@version_date' THEN 'returns the date this version was released'
                    ELSE NULL
                END,
            valid_inputs =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'the name of a database you care about indexes in'
                    WHEN N'@schema_name' THEN 'schema name or NULL for all schemas'
                    WHEN N'@table_name' THEN 'table (or view) name or NULL for all tables'
                    WHEN N'@min_reads' THEN 'any positive integer or 0'
                    WHEN N'@min_writes' THEN 'any positive integer or 0'
                    WHEN N'@min_size_gb' THEN 'any positive decimal or 0'
                    WHEN N'@min_rows' THEN 'any positive integer or 0'
                    WHEN N'@dedupe_only' THEN '0 or 1 - only perform index deduplication, do not mark unused indexes for removal'
                    WHEN N'@get_all_databases' THEN '0 or 1'
                    WHEN N'@include_databases' THEN 'comma-separated list of database names'
                    WHEN N'@exclude_databases' THEN 'comma-separated list of database names'
                    WHEN N'@help' THEN '0 or 1'
                    WHEN N'@debug' THEN '0 or 1'
                    WHEN N'@version' THEN 'OUTPUT parameter'
                    WHEN N'@version_date' THEN 'OUTPUT parameter'
                    ELSE NULL
                END,
            defaults =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'NULL'
                    WHEN N'@schema_name' THEN 'NULL'
                    WHEN N'@table_name' THEN 'NULL'
                    WHEN N'@min_reads' THEN '0'
                    WHEN N'@min_writes' THEN '0'
                    WHEN N'@min_size_gb' THEN '0'
                    WHEN N'@min_rows' THEN '0'
                    WHEN N'@dedupe_only' THEN 'false'
                    WHEN N'@get_all_databases' THEN 'false'
                    WHEN N'@include_databases' THEN 'NULL'
                    WHEN N'@exclude_databases' THEN 'NULL'
                    WHEN N'@help' THEN 'false'
                    WHEN N'@debug' THEN 'false'
                    WHEN N'@version' THEN 'NULL'
                    WHEN N'@version_date' THEN 'NULL'
                    ELSE NULL
                END
        FROM sys.all_parameters AS ap
        JOIN sys.all_objects AS o
          ON ap.object_id = o.object_id
        JOIN sys.types AS t
          ON  ap.system_type_id = t.system_type_id
          AND ap.user_type_id = t.user_type_id
        WHERE o.name = N'sp_IndexCleanup'
        OPTION(MAXDOP 1, RECOMPILE);

        SELECT
            mit_license_yo = 'i am MIT licensed, so like, do whatever'

        UNION ALL

        SELECT
            mit_license_yo = 'see printed messages for full license';

        RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;

        RETURN;
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('Declaring variables', 0, 0) WITH NOWAIT;
    END;

    DECLARE
        /*general script variables*/
        @sql nvarchar(MAX) = N'',
        @object_id integer = NULL,
        @full_object_name nvarchar(768) = NULL,
        @uptime_warning bit = 0, /* Will set after @uptime_days is calculated */
        /*print variables*/
        @online bit =
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        SERVERPROPERTY('EngineEdition')
                    ) IN (3, 5, 8)
                THEN 'true' /* Enterprise, Azure SQL DB, Managed Instance */
                ELSE 'false'
            END,
        /* Compression variables */
        @can_compress bit =
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        SERVERPROPERTY('EngineEdition')
                    ) IN (3, 5, 8)
                    OR
                    (
                      CONVERT
                      (
                          integer,
                          SERVERPROPERTY('EngineEdition')
                      ) = 2
                      AND CONVERT
                          (
                              integer,
                              SUBSTRING
                              (
                                  CONVERT
                                  (
                                      varchar(20),
                                      SERVERPROPERTY('ProductVersion')
                                  ),
                                  1,
                                  2
                              )
                          ) >= 13
                    )
                THEN 1
                ELSE 0
            END,
        /* OPTIMIZE_FOR_SEQUENTIAL_KEY variables (SQL 2019+, Azure SQL DB, and Managed Instance) */
        @supports_optimize_for_sequential_key bit =
            CASE
                /* Azure SQL DB or Managed Instance */
                WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (5, 8)
                THEN 1
                /* SQL Server 2019+ (Enterprise or Standard) */
                WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (2, 3)
                     AND CONVERT
                         (
                             integer,
                             SUBSTRING
                             (
                                 CONVERT
                                 (
                                     varchar(20),
                                     SERVERPROPERTY('ProductVersion')
                                 ),
                                 1,
                                 2
                             )
                         ) >= 15
                THEN 1
                ELSE 0
            END,
        /* Temporal tables support (SQL 2016+, Azure SQL DB, and Managed Instance) */
        @supports_temporal_tables bit =
            CASE
                /* Azure SQL DB or Managed Instance */
                WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (5, 8)
                THEN 1
                /* SQL Server 2016+ */
                WHEN CONVERT
                     (
                         integer,
                         SUBSTRING
                         (
                             CONVERT
                             (
                                 varchar(20),
                                 SERVERPROPERTY('ProductVersion')
                             ),
                             1,
                             2
                         )
                     ) >= 13
                THEN 1
                ELSE 0
            END,
        @is_azure_sql_db bit =
            CASE
                WHEN CONVERT
                     (
                         integer,
                         SERVERPROPERTY('EngineEdition')
                     ) = 5
                THEN 1
                ELSE 0
            END,
        @uptime_days nvarchar(10) =
        (
            SELECT
                DATEDIFF
                (
                    DAY,
                    osi.sqlserver_start_time,
                    SYSDATETIME()
                )
            FROM sys.dm_os_sys_info AS osi
        ),
        @database_cursor CURSOR,
        @current_database_name sysname,
        @current_database_id integer,
        @error_msg nvarchar(2048),
        @conflict_list nvarchar(MAX) = N'',
        @rc bigint;

    /* Set uptime warning flag after @uptime_days is calculated */
    SELECT
        @uptime_warning =
            CASE
                WHEN CONVERT(integer, @uptime_days) < 14
                THEN 1
                ELSE 0
            END;

    /* Auto-enable dedupe_only mode if server uptime is low */
    IF CONVERT(integer, @uptime_days) <= 7 AND @dedupe_only = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Server uptime is less than 7 days. Automatically enabling @dedupe_only mode.', 0, 1) WITH NOWAIT;
        END;

        SET @dedupe_only = 1;
    END;

    /*
    Initial checks for object validity
    */
    IF @debug = 1
    BEGIN
        RAISERROR('Checking parameters...', 0, 0) WITH NOWAIT;
    END;

    IF  @schema_name IS NULL
    AND @table_name IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parameter @schema_name cannot be NULL when specifying a table, defaulting to dbo', 10, 1) WITH NOWAIT;
        END;

        SET @schema_name = N'dbo';
    END;

    IF @min_reads < 0
    OR @min_reads IS NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parameter @min_reads cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
        END;

        SET @min_reads = 0;
    END;

    IF @min_writes < 0
    OR @min_writes IS NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parameter @min_writes cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
        END;

        SET @min_writes = 0;
    END;

    IF @min_size_gb < 0
    OR @min_size_gb IS NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parameter @min_size_gb cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
        END;

        SET @min_size_gb = 0;
    END;

    IF @min_rows < 0
    OR @min_rows IS NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Parameter @min_rows cannot be NULL or negative. Setting to 0.', 10, 1) WITH NOWAIT;
        END;

        SET @min_rows = 0;
    END;

    /*
    Temp tables!
    */

    IF @debug = 1
    BEGIN
        RAISERROR('Creating temp tables', 0, 0) WITH NOWAIT;
    END;

    CREATE TABLE
        #filtered_objects
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NOT NULL,
        can_compress bit NOT NULL
    );

    CREATE CLUSTERED INDEX
        filtered_objects
    ON #filtered_objects
        (database_id, schema_id, object_id, index_id);

    CREATE TABLE
        #operational_stats
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NOT NULL,
        range_scan_count bigint NULL,
        singleton_lookup_count bigint NULL,
        forwarded_fetch_count bigint NULL,
        lob_fetch_in_pages bigint NULL,
        row_overflow_fetch_in_pages bigint NULL,
        leaf_insert_count bigint NULL,
        leaf_update_count bigint NULL,
        leaf_delete_count bigint NULL,
        leaf_ghost_count bigint NULL,
        nonleaf_insert_count bigint NULL,
        nonleaf_update_count bigint NULL,
        nonleaf_delete_count bigint NULL,
        leaf_allocation_count bigint NULL,
        nonleaf_allocation_count bigint NULL,
        row_lock_count bigint NULL,
        row_lock_wait_count bigint NULL,
        row_lock_wait_in_ms bigint NULL,
        page_lock_count bigint NULL,
        page_lock_wait_count bigint NULL,
        page_lock_wait_in_ms bigint NULL,
        index_lock_promotion_attempt_count bigint NULL,
        index_lock_promotion_count bigint NULL,
        page_latch_wait_count bigint NULL,
        page_latch_wait_in_ms bigint NULL,
        tree_page_latch_wait_count bigint NULL,
        tree_page_latch_wait_in_ms bigint NULL,
        page_io_latch_wait_count bigint NULL,
        page_io_latch_wait_in_ms bigint NULL,
        page_compression_attempt_count bigint NULL,
        page_compression_success_count bigint NULL,
        /* Hash column for optimized matching */
        index_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id) +
                    CONVERT(varbinary(8), index_id)
                )
            ) PERSISTED
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, index_id)
    );

    CREATE TABLE
        #partition_stats
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NULL,
        partition_id bigint NOT NULL,
        partition_number integer NOT NULL,
        total_rows bigint NULL,
        total_space_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
        reserved_lob_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
        reserved_row_overflow_gb decimal(38, 4) NULL, /* Using 4 decimal places for GB to maintain precision */
        data_compression_desc nvarchar(60) NULL,
        built_on sysname NULL,
        partition_function_name sysname NULL,
        partition_columns nvarchar(max),
        /* Hash column for optimized matching */
        index_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id) +
                    CONVERT(varbinary(8), index_id)
                )
            ) PERSISTED
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, index_id, partition_id)
    );

    CREATE TABLE
        #index_details
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NULL,
        column_name sysname NOT NULL,
        column_id integer NOT NULL,
        is_primary_key bit NULL,
        is_unique bit NULL,
        is_unique_constraint bit NULL,
        is_indexed_view integer NOT NULL,
        is_foreign_key bit NULL,
        is_foreign_key_reference bit NULL,
        key_ordinal tinyint NOT NULL,
        index_column_id integer NOT NULL,
        is_descending_key bit NOT NULL,
        is_included_column bit NULL,
        filter_definition nvarchar(max) NULL,
        is_max_length integer NOT NULL,
        optimize_for_sequential_key bit NOT NULL,
        user_seeks bigint NOT NULL,
        user_scans bigint NOT NULL,
        user_lookups bigint NOT NULL,
        user_updates bigint NOT NULL,
        last_user_seek datetime NULL,
        last_user_scan datetime NULL,
        last_user_lookup datetime NULL,
        last_user_update datetime NULL,
        is_eligible_for_dedupe bit NOT NULL,
        /* Hash columns for optimized matching */
        index_hash AS
            HASHBYTES
            (
                'SHA2_256',
                CONVERT(varbinary(8), CONVERT(integer, database_id)) +
                CONVERT(varbinary(8), CONVERT(integer, object_id)) +
                CONVERT(varbinary(8), CONVERT(integer, index_id))
            ) PERSISTED,
        column_position_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id) +
                    CONVERT(varbinary(max), column_name) +
                    CONVERT(varbinary(8), key_ordinal)
                )
            ) PERSISTED,
        scope_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id)
                )
            ) PERSISTED
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, index_id, column_id)
    );

    CREATE TABLE
        #index_analysis
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NOT NULL,
        is_unique bit NULL,
        key_columns nvarchar(MAX) NULL,
        included_columns nvarchar(MAX) NULL,
        filter_definition nvarchar(MAX) NULL,
        /* Query plan for original CREATE INDEX statement */
        original_index_definition nvarchar(MAX) NULL,
        /*
        Consolidation rule that matched (e.g., Key Duplicate, Key Subset, etc)
        For exact duplicates, use one of: Exact Duplicate, Reverse Duplicate, or Equal Except For Filter
        */
        consolidation_rule nvarchar(256) NULL,
        /*
        Action to take (e.g., DISABLE, MERGE INCLUDES, KEEP)
        If NULL, no action to be taken
        */
        action nvarchar(100) NULL,
        /* Target index to merge with or use instead of this one */
        target_index_name sysname NULL,
        /* When this is a target, the index which points to it as a supersedes in consolidation */
        superseded_by nvarchar(4000) NULL,
        /* Priority score from 0-1 to determine which index to keep (higher is better) */
        index_priority decimal(10,6) NULL,
        /* Hash columns for optimized matching */
        scope_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id)
                )
            ) PERSISTED,
        exact_match_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    ISNULL(key_columns, N'') + N'|' +
                    ISNULL(included_columns, N'') + N'|' +
                    ISNULL(filter_definition, N'')
                )
            ) PERSISTED,
        key_filter_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    ISNULL(key_columns, N'') + N'|' +
                    ISNULL(filter_definition, N'')
                )
            ) PERSISTED,
        index_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id) +
                    CONVERT(varbinary(8), index_id)
                )
            ) PERSISTED
    );

    CREATE CLUSTERED INDEX
        index_analysis
    ON #index_analysis
        (database_id, schema_id, object_id, index_id);

    /*
    Nonclustered indexes on hash columns for optimized matching.
    These support faster joins in consolidation rules by reducing
    multi-column comparisons to single hash comparisons.
    */
    CREATE INDEX
        scope_hash
    ON #index_analysis
        (scope_hash)
    INCLUDE
        (index_name);

    CREATE INDEX
        exact_match_hash
    ON #index_analysis
        (exact_match_hash);

    CREATE INDEX
        key_filter_hash
    ON #index_analysis
        (key_filter_hash);

    /*
    Nonclustered indexes on #index_details hash columns.
    These support faster correlation in EXISTS clauses by reducing
    multi-column comparisons to single hash comparisons.
    */
    CREATE INDEX
        index_hash
    ON #index_details
        (index_hash);

    CREATE INDEX
        column_position_hash
    ON #index_details
        (column_position_hash);

    CREATE INDEX
        scope_hash
    ON #index_details
        (scope_hash);

    CREATE TABLE
        #compression_eligibility
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NOT NULL,
        can_compress bit NOT NULL,
        reason nvarchar(200) NULL,
        /* Hash column for optimized matching */
        index_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id) +
                    CONVERT(varbinary(8), index_id)
                )
            ) PERSISTED
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, index_id, can_compress)
    );

    CREATE TABLE
        #index_cleanup_results
    (
        result_type varchar(100) NOT NULL,
        sort_order integer NOT NULL,
        database_name nvarchar(max) NULL,
        schema_name nvarchar(max) NULL,
        table_name sysname NULL,
        index_name sysname NULL,
        script_type nvarchar(60) NULL, /* Type of script (e.g., MERGE SCRIPT, DISABLE SCRIPT, etc.) */
        consolidation_rule nvarchar(256) NULL, /* Reason for action (e.g., Exact Duplicate, Key Subset) */
        target_index_name sysname NULL, /* If this index is a duplicate, indicates which index is the preferred one */
        superseded_info nvarchar(4000) NULL, /* If this is a kept index, indicates which indexes it supersedes */
        additional_info nvarchar(max) NULL, /* Additional information about the action */
        original_index_definition nvarchar(max) NULL, /* Original statement to create the index */
        index_size_gb decimal(38, 4) NULL, /* Size of the index in GB */
        index_rows bigint NULL, /* Number of rows in the index */
        index_reads bigint NULL, /* Total reads (seeks + scans + lookups) */
        index_writes bigint NULL, /* Total writes */
        script nvarchar(max) NULL /* Script to execute the action */
    );

    CREATE TABLE
        #key_duplicate_dedupe
    (
        database_id integer NOT NULL,
        object_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_name sysname NOT NULL,
        table_name sysname NOT NULL,
        base_key_columns nvarchar(max) NULL,
        filter_definition nvarchar(max) NULL,
        winning_index_name sysname NULL,
        index_list nvarchar(max) NULL,
        /* Hash columns for optimized matching */
        scope_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id)
                )
            ) PERSISTED,
        key_filter_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    ISNULL(base_key_columns, N'') + N'|' +
                    ISNULL(filter_definition, N'')
                )
            ) PERSISTED
    );

    CREATE TABLE
        #include_subset_dedupe
    (
        database_id integer NOT NULL,
        object_id integer NOT NULL,
        subset_index_name sysname NULL,
        superset_index_name sysname NULL,
        subset_included_columns nvarchar(max) NULL,
        superset_included_columns nvarchar(max) NULL,
        /* Hash column for optimized matching */
        scope_hash AS
            CONVERT
            (
                varbinary(32),
                HASHBYTES
                (
                    'SHA2_256',
                    CONVERT(varbinary(8), database_id) +
                    CONVERT(varbinary(8), object_id)
                )
            ) PERSISTED
    );

    /* Create a new temp table for detailed reporting statistics */
    CREATE TABLE
        #index_reporting_stats
    (
        summary_level varchar(20) NOT NULL, /* 'DATABASE', 'TABLE', 'INDEX', 'SUMMARY' */
        database_name sysname NULL,
        schema_name sysname NULL,
        table_name sysname NULL,
        index_name sysname NULL,
        server_uptime_days integer NULL,
        uptime_warning bit NULL,
        tables_analyzed integer NULL,
        index_count integer NULL,
        total_size_gb decimal(38, 4) NULL,
        total_rows bigint NULL,
        unused_indexes integer NULL,
        unused_size_gb decimal(38, 4) NULL,
        indexes_to_disable integer NULL,
        indexes_to_merge integer NULL,
        compressable_indexes integer NULL,
        avg_indexes_per_table decimal(10, 2) NULL,
        space_saved_gb decimal(10, 4) NULL,
        compression_min_savings_gb decimal(10, 4) NULL,
        compression_max_savings_gb decimal(10, 4) NULL,
        total_min_savings_gb decimal(10, 4) NULL,
        total_max_savings_gb decimal(10, 4) NULL,
        /* Index usage metrics */
        total_reads bigint NULL,
        total_writes bigint NULL,
        user_seeks bigint NULL,
        user_scans bigint NULL,
        user_lookups bigint NULL,
        user_updates bigint NULL,
        /* Operational stats */
        range_scan_count bigint NULL,
        singleton_lookup_count bigint NULL,
        /* Lock stats */
        row_lock_count bigint NULL,
        row_lock_wait_count bigint NULL,
        row_lock_wait_in_ms bigint NULL,
        page_lock_count bigint NULL,
        page_lock_wait_count bigint NULL,
        page_lock_wait_in_ms bigint NULL,
        /* Latch stats */
        page_latch_wait_count bigint NULL,
        page_latch_wait_in_ms bigint NULL,
        page_io_latch_wait_count bigint NULL,
        page_io_latch_wait_in_ms bigint NULL,
        /* Misc stats */
        forwarded_fetch_count bigint NULL,
        leaf_insert_count bigint NULL,
        leaf_update_count bigint NULL,
        leaf_delete_count bigint NULL
    );

    /* Create temp tables for database filtering */
    CREATE TABLE
        #include_databases
    (
        database_name sysname NOT NULL PRIMARY KEY CLUSTERED
    );

    CREATE TABLE
        #exclude_databases
    (
        database_name sysname NOT NULL PRIMARY KEY CLUSTERED
    );

    CREATE TABLE
        #databases
    (
        database_name sysname NOT NULL PRIMARY KEY CLUSTERED,
        database_id integer NOT NULL
    );

    CREATE TABLE
        #requested_but_skipped_databases
    (
        database_name sysname NOT NULL PRIMARY KEY CLUSTERED,
        reason nvarchar(100) NOT NULL
    );

    CREATE TABLE
        #computed_columns_analysis
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        column_id integer NOT NULL,
        column_name sysname NOT NULL,
        definition nvarchar(max) NULL,
        contains_udf bit NOT NULL,
        udf_names nvarchar(max) NULL,
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, column_id)
    );

    CREATE TABLE
        #check_constraints_analysis
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        constraint_id integer NOT NULL,
        constraint_name sysname NOT NULL,
        definition nvarchar(max) NULL,
        contains_udf bit NOT NULL,
        udf_names nvarchar(max) NULL,
        PRIMARY KEY CLUSTERED
            (database_id, schema_id, object_id, constraint_id)
    );

    CREATE TABLE
        #filtered_index_columns_analysis
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        schema_id integer NOT NULL,
        schema_name sysname NOT NULL,
        object_id integer NOT NULL,
        table_name sysname NOT NULL,
        index_id integer NOT NULL,
        index_name sysname NULL,
        filter_definition nvarchar(max) NULL,
        missing_included_columns nvarchar(max) NULL,
        should_include_filter_columns bit NOT NULL
    );

    CREATE CLUSTERED INDEX
        c
    ON #filtered_index_columns_analysis
        (database_id, schema_id, object_id, index_id);

    CREATE TABLE
        #merged_includes
    (
        scope_hash varbinary(32) NOT NULL,
        index_name sysname NOT NULL,
        key_columns nvarchar(max) NOT NULL,
        merged_includes nvarchar(max) NULL,
        PRIMARY KEY (scope_hash, index_name)
    );

    /* Parse @include_databases comma-separated list */
    IF  @get_all_databases = 1
    AND @include_databases IS NOT NULL
    BEGIN
        INSERT INTO
            #include_databases
        WITH
            (TABLOCK)
        (
            database_name
        )
        SELECT DISTINCT
            database_name =
                LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname')))
        FROM
        (
            SELECT
                x = CONVERT
                    (
                        xml,
                        N'<i>' +
                        REPLACE
                        (
                            @include_databases,
                            N',',
                            N'</i><i>'
                        ) +
                        N'</i>'
                    )
        ) AS a
        CROSS APPLY x.nodes(N'//i') AS t(c)
        WHERE LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname'))) <> N''
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT
                table_name = '#include_databases',
                id.*
            FROM #include_databases AS id
            OPTION(RECOMPILE);
        END;
    END;

    /* Parse @exclude_databases comma-separated list */
    IF  @get_all_databases = 1
    AND @exclude_databases IS NOT NULL
    BEGIN
        INSERT INTO
            #exclude_databases
        WITH
            (TABLOCK)
        (
            database_name
        )
        SELECT DISTINCT
            database_name =
                LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname')))
        FROM
        (
            SELECT
                x = CONVERT
                    (
                        xml,
                        N'<i>' +
                        REPLACE
                        (
                            @exclude_databases,
                            N',',
                            N'</i><i>'
                        ) +
                        N'</i>'
                    )
        ) AS a
        CROSS APPLY x.nodes(N'//i') AS t(c)
        WHERE LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname'))) <> N''
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT
                table_name = '#exclude_databases',
                ed.*
            FROM #exclude_databases AS ed
            OPTION(RECOMPILE);
        END;
    END;

    /* Check for conflicts between include and exclude lists */
    IF  @get_all_databases = 1
    AND @include_databases IS NOT NULL
    AND @exclude_databases IS NOT NULL
    BEGIN
        SELECT
            @conflict_list =
                @conflict_list +
                ed.database_name + N', '
        FROM #exclude_databases AS ed
        WHERE EXISTS
            (
                SELECT
                    1/0
                FROM #include_databases AS id
                WHERE id.database_name = ed.database_name
            )
        OPTION(RECOMPILE);

        /* If we found any conflicts, raise an error */
        IF DATALENGTH(@conflict_list) > 0
        BEGIN
            /* Remove trailing comma and space */
            SET @conflict_list = LEFT(@conflict_list, DATALENGTH(@conflict_list) / 2 - 2);

            SET @error_msg =
                N'The following databases appear in both @include_databases and @exclude_databases, which creates ambiguity: ' +
                @conflict_list + N'. Please remove these databases from one of the lists.';

            RAISERROR(@error_msg, 16, 1);
            RETURN;
        END;
    END;

    /* Handle contradictory parameters */
    IF  @get_all_databases = 1
    AND @database_name IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR(N'@database name being ignored since @get_all_databases is set to 1', 0, 0) WITH NOWAIT;
        END;
        SET @database_name = NULL;
    END;

    /* Azure SQL DB does not support @get_all_databases */
    IF  @get_all_databases = 1
    AND @is_azure_sql_db = 1
    BEGIN
        RAISERROR('@get_all_databases is not supported on Azure SQL Database. Please specify a @database_name instead.', 16, 1);
        RETURN;
    END;

    /* Build the #databases table */
    IF @get_all_databases = 0
    BEGIN
        /* Default to current database if not system db */
        IF @database_name IS NULL
        AND DB_NAME() NOT IN
            (
                N'master',
                N'model',
                N'msdb',
                N'tempdb',
                N'rdsadmin'
            )
        BEGIN
            SET @database_name = DB_NAME();
        END;

        /* Strip brackets if user supplied them */
        IF @database_name IS NOT NULL
        BEGIN
            SET @database_name = PARSENAME(@database_name, 1);
        END;

        /* Single database mode */
        IF @database_name IS NOT NULL
        BEGIN
            INSERT INTO
                #databases
            WITH
                (TABLOCK)
            (
                database_name,
                database_id
            )
            SELECT
                d.name,
                d.database_id
            FROM sys.databases AS d
            WHERE d.name = @database_name
            AND   d.state = 0
            AND   d.is_in_standby = 0
            AND   d.is_read_only = 0
            OPTION(RECOMPILE);

            /* Get the database_id for backwards compatibility */
            SELECT
                @current_database_id = d.database_id
            FROM #databases AS d
            OPTION(RECOMPILE);
        END;
    END
    ELSE
    BEGIN
        /* Multi-database mode */
        INSERT INTO
            #databases
        WITH
            (TABLOCK)
        (
            database_name,
            database_id
        )
        SELECT
            d.name,
            d.database_id
        FROM sys.databases AS d
        WHERE d.database_id > 4 /* Skip system databases */
        AND   d.state = 0
        AND   d.is_in_standby = 0
        AND   d.is_read_only = 0
        AND   (
                @include_databases IS NULL
                OR EXISTS (SELECT 1/0 FROM #include_databases AS id WHERE id.database_name = d.name)
              )
        AND   (
                @exclude_databases IS NULL
                OR NOT EXISTS (SELECT 1/0 FROM #exclude_databases AS ed WHERE ed.database_name = d.name)
              )
        OPTION(RECOMPILE);
    END;

    /* Check for empty database list */
    IF (SELECT COUNT_BIG(*) FROM #databases AS d) = 0
    BEGIN
        RAISERROR('No valid databases found to process.', 16, 1);
        RETURN;
    END;

    /* Show database list in debug mode */
    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#databases',
            d.*
        FROM #databases AS d
        OPTION(RECOMPILE);
    END;

    /*
    Identify databases that were requested but couldn't be processed.
    This must happen AFTER #databases is populated.
    */
    IF  @get_all_databases = 1
    AND @include_databases IS NOT NULL
    BEGIN
        INSERT INTO
            #requested_but_skipped_databases
        WITH
            (TABLOCK)
        (
            database_name,
            reason
        )
        SELECT
            id.database_name,
            reason =
                CASE
                    WHEN d.name IS NULL
                    THEN 'Database does not exist'
                    WHEN d.state <> 0
                    THEN 'Database not online'
                    WHEN d.is_in_standby = 1
                    THEN 'Database is in standby'
                    WHEN d.is_read_only = 1
                    THEN 'Database is read-only'
                    WHEN d.database_id <= 4
                    THEN 'System database'
                    ELSE 'Other issue'
                END
        FROM #include_databases AS id
        LEFT JOIN sys.databases AS d
          ON id.database_name = d.name
        WHERE NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #databases AS db
                  WHERE db.database_name = id.database_name
              )
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            SELECT
                table_name = '#requested_but_skipped_databases',
                rbsd.*
            FROM #requested_but_skipped_databases AS rbsd
            OPTION(RECOMPILE);
        END;
    END;

    /*Set up database cursor processing*/

    /* Create a cursor to process each database */
    SET @database_cursor =
        CURSOR
        LOCAL
        SCROLL
        DYNAMIC
        READ_ONLY
    FOR
    SELECT
        d.database_name,
        d.database_id
    FROM #databases AS d
    ORDER BY
        d.database_id;

    OPEN @database_cursor;

    FETCH FIRST
    FROM @database_cursor
    INTO
        @current_database_name,
        @current_database_id;

    /*
    Start insert queries
    */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #filtered_object insert', 0, 0) WITH NOWAIT;
    END;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        /*Truncate temp tables between database iterations*/
        IF @debug = 1
        BEGIN
            RAISERROR('Truncating per-database temp tables for the next iteration', 0, 0) WITH NOWAIT;
        END;

        TRUNCATE TABLE
            #filtered_objects;
        TRUNCATE TABLE
            #operational_stats;
        TRUNCATE TABLE
            #partition_stats;
        TRUNCATE TABLE
            #index_details;
        TRUNCATE TABLE
            #compression_eligibility;
        TRUNCATE TABLE
            #key_duplicate_dedupe;
        TRUNCATE TABLE
            #include_subset_dedupe;
        TRUNCATE TABLE
            #computed_columns_analysis;
        TRUNCATE TABLE
            #check_constraints_analysis;
        TRUNCATE TABLE
            #filtered_index_columns_analysis;
        TRUNCATE TABLE
            #merged_includes;

         /*Validate searched objects per-database*/
         IF  @schema_name IS NOT NULL
         AND @table_name IS NOT NULL
         BEGIN
             IF @debug = 1
             BEGIN
                 RAISERROR('validating object existence for %s.%s.%s.', 0, 0, @current_database_name, @schema_name, @table_name) WITH NOWAIT;
             END;

             SELECT
                 @full_object_name =
                     QUOTENAME(@current_database_name) +
                     N'.' +
                     QUOTENAME(@schema_name) +
                     N'.' +
                     QUOTENAME(@table_name);

             SET @object_id = OBJECT_ID(@full_object_name);

             IF @object_id IS NULL
             BEGIN
                 RAISERROR('The object %s doesn''t seem to exist', 10, 1, @full_object_name) WITH NOWAIT;

                 IF @get_all_databases = 0
                 BEGIN
                     RETURN;
                 END;

                 /* Get the next database and continue the loop */
                 FETCH NEXT
                 FROM @database_cursor
                 INTO
                     @current_database_name,
                     @current_database_id;
                 CONTINUE;
             END;
         END;

        /* Process current database */
        IF @debug = 1
        BEGIN
            RAISERROR('Processing @current_database_name: %s and @current_database_id: %d', 0, 0, @current_database_name, @current_database_id) WITH NOWAIT;
        END;

        SELECT
            @sql = N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        SELECT DISTINCT
            @database_id,
            database_name = DB_NAME(@database_id),
            schema_id = s.schema_id,
            schema_name = s.name,
            object_id = i.object_id,
            table_name = ISNULL(t.name, v.name),
            index_id = i.index_id,
            index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''),
            can_compress =
                CASE
                    WHEN p.index_id > 0
                    AND  p.data_compression = 0
                    THEN 1
                    ELSE 0
                END
        FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i
        LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t
          ON i.object_id = t.object_id
        LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v
          ON i.object_id = v.object_id
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s
          ON ISNULL(t.schema_id, v.schema_id) = s.schema_id
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partitions AS p
          ON  i.object_id = p.object_id
          AND i.index_id = p.index_id
        /* LEFT JOIN to dm_db_index_usage_stats removed 2026-01-15 - was dead code with no columns selected */
        WHERE (t.object_id IS NULL OR t.is_ms_shipped = 0)
        AND   (t.object_id IS NULL OR t.type <> N''TF'')
        AND    i.is_disabled = 0
        AND    i.is_hypothetical = 0';

    IF @supports_temporal_tables = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('adding temporal table screening', 0, 0) WITH NOWAIT;
        END;

        SET @sql += N'
        AND   NOT EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t
            WHERE t.object_id = i.object_id
            AND   t.temporal_type > 0
        )';
    END;

    IF @object_id IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('adding object_id filter', 0, 0) WITH NOWAIT;
        END;

        SET @sql += N'
        AND   i.object_id = @object_id';
    END;

    IF  @schema_name IS NOT NULL
    AND @object_id IS NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('adding schema_name filter', 0, 0) WITH NOWAIT;
        END;

        SET @sql += N'
        AND   s.name = @schema_name';
    END;

    SET @sql += N'
        AND EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps
            JOIN ' + QUOTENAME(@current_database_name) + N'.sys.allocation_units AS au
              ON ps.partition_id = au.container_id
            WHERE ps.object_id = i.object_id
            GROUP BY
                ps.object_id
            HAVING
                SUM(au.total_pages) * 8.0 / 1048576.0 >= @min_size_gb
        )
        AND EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps
            WHERE ps.object_id = i.object_id
            AND   ps.index_id IN (0, 1)
            GROUP BY
                ps.object_id
            HAVING
                SUM(ps.row_count) >= @min_rows
        )
        AND EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_usage_stats AS ius
            WHERE ius.object_id = i.object_id
            AND   ius.database_id = @database_id
            GROUP BY
                ius.object_id
            HAVING
                SUM(ius.user_seeks + ius.user_scans + ius.user_lookups) >= @min_reads
            OR
                SUM(ius.user_updates) >= @min_writes
        )
        OPTION(RECOMPILE);
    ';

    IF @debug = 1
    BEGIN
        PRINT @sql;
    END;

    INSERT INTO
        #filtered_objects
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        index_id,
        index_name,
        can_compress
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer,
        @min_reads bigint,
        @min_writes bigint,
        @min_size_gb decimal(10,2),
        @min_rows bigint,
        @object_id integer,
        @schema_name sysname',
        @current_database_id,
        @min_reads,
        @min_writes,
        @min_size_gb,
        @min_rows,
        @object_id,
        @schema_name;

    SET @rc = ROWCOUNT_BIG();

    IF @rc = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('No rows inserted into #filtered_objects from %s, continuing to next database...', 10, 0, @current_database_name) WITH NOWAIT;
        END;

        IF @get_all_databases = 0
        BEGIN
            RETURN;
        END;

        /* Get the next database and continue the loop */
        FETCH NEXT
        FROM @database_cursor
        INTO
            @current_database_name,
            @current_database_id;
        CONTINUE;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#filtered_objects',
            fo.*
        FROM #filtered_objects AS fo
        OPTION(RECOMPILE);

        RAISERROR('Generating #compression_eligibility insert', 0, 0) WITH NOWAIT;
    END;

    /* Populate compression eligibility table */
    INSERT INTO
        #compression_eligibility
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        index_id,
        index_name,
        can_compress,
        reason
    )
    SELECT
        fo.database_id,
        fo.database_name,
        fo.schema_id,
        fo.schema_name,
        fo.object_id,
        fo.table_name,
        fo.index_id,
        fo.index_name,
        can_compress =
            CASE
                 @can_compress
                 WHEN 0
                 THEN 0
                 ELSE 1
            END,
        reason =
            CASE
                 @can_compress
                 WHEN 0
                 THEN N'SQL Server edition or version does not support compression'
                 ELSE NULL
            END
    FROM #filtered_objects AS fo
    WHERE fo.can_compress = 1
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#compression_eligibility before update',
            ce.*
        FROM #compression_eligibility AS ce
        OPTION(RECOMPILE);
    END;

    /* Check for sparse columns or incompatible data types */
    IF @can_compress = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Updating #compression_eligibility', 0, 0) WITH NOWAIT;
        END;

        SELECT
            @sql = N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        UPDATE
            ce
        SET
            ce.can_compress = 0,
            ce.reason = ''Table contains sparse columns''
        FROM #compression_eligibility AS ce
        WHERE EXISTS
        (
            SELECT
                1/0
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
            WHERE c.object_id = ce.object_id
            AND   c.is_sparse = 1
        )
        OPTION(RECOMPILE);
        ';

        IF @debug = 1
        BEGIN
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql;

         SELECT
            @sql = N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        UPDATE
            ce
        SET
            ce.can_compress = 0,
            ce.reason = ''Index contains incompatible data types''
        FROM #compression_eligibility AS ce
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i
          ON i.object_id = ce.object_id AND i.index_id = ce.index_id
        WHERE ce.can_compress = 1
          AND i.type = 1
          AND EXISTS
          (
              SELECT
                  1/0
              FROM ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
              JOIN ' + QUOTENAME(@current_database_name) + N'.sys.types AS t
                ON c.user_type_id = t.user_type_id
              WHERE c.object_id = ce.object_id
              AND   t.name IN
                    (
                        N''text'',
                        N''ntext'',
                        N''image''
                    )
          )
          OPTION(RECOMPILE);
        ';

        IF @debug = 1
        BEGIN
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#compression_eligibility after update',
            ce.*
        FROM #compression_eligibility AS ce
        OPTION(RECOMPILE);

        RAISERROR('Analyzing computed columns for UDF references', 0, 0) WITH NOWAIT;
    END;

    /* Check for computed columns that potentially use UDFs */
    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT DISTINCT
        fo.database_id,
        fo.database_name,
        fo.schema_id,
        fo.schema_name,
        fo.object_id,
        fo.table_name,
        c.column_id,
        column_name = c.name,
        definition = cc.definition,
        /*
        UDF detection: Looks for schema-qualified object references like [schema].[function]
        Note: This is a heuristic check and may have rare false positives if ].[  appears
        in string literals or comments within the computed column definition
        */
        contains_udf =
            CASE
                WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|''
                AND  cc.definition LIKE ''%|].|[%(%'' ESCAPE ''|''
                THEN 1
                ELSE 0
            END,
        udf_names =
            CASE
                WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|''
                AND  cc.definition LIKE ''%|].|[%(%'' ESCAPE ''|''
                AND  CHARINDEX(N''['', cc.definition) > 0
                AND  CHARINDEX(N''].['', cc.definition) > 0
                AND  CHARINDEX(N'']'', cc.definition, CHARINDEX(N''].['', cc.definition) + 3) > 0
                THEN
                    SUBSTRING
                    (
                        cc.definition,
                        CHARINDEX(N''['', cc.definition),
                        CHARINDEX
                        (
                            N'']'',
                            cc.definition,
                            CHARINDEX
                            (
                                N''].['',
                                cc.definition
                            ) + 3
                        ) -
                        CHARINDEX(N''['', cc.definition) + 1
                    )
                ELSE NULL
            END
    FROM #filtered_objects AS fo
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
      ON fo.object_id = c.object_id
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.computed_columns AS cc
      ON  c.object_id = cc.object_id
      AND c.column_id = cc.column_id
    OPTION(RECOMPILE);';

    IF @debug = 1
    BEGIN
        PRINT @sql;
    END;

    INSERT INTO
        #computed_columns_analysis
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        column_id,
        column_name,
        definition,
        contains_udf,
        udf_names
    )
    EXECUTE sys.sp_executesql
        @sql;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#computed_columns_analysis',
            cca.*
        FROM #computed_columns_analysis AS cca
        OPTION(RECOMPILE);

        RAISERROR('Analyzing check constraints for UDF references', 0, 0) WITH NOWAIT;
    END;

    /* Check for check constraints that potentially use UDFs */
    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT DISTINCT
        fo.database_id,
        fo.database_name,
        fo.schema_id,
        fo.schema_name,
        fo.object_id,
        fo.table_name,
        cc.object_id AS constraint_id,
        constraint_name = cc.name,
        definition = cc.definition,
        /*
        UDF detection: Looks for schema-qualified object references like [schema].[function]
        Note: This is a heuristic check and may have rare false positives if ].[  appears
        in string literals or comments within the computed column definition
        */
        contains_udf =
            CASE
                WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|''
                AND  cc.definition LIKE ''%|].|[%(%'' ESCAPE ''|''
                THEN 1
                ELSE 0
            END,
        udf_names =
            CASE
                WHEN cc.definition LIKE ''%|].|[%'' ESCAPE ''|''
                AND  cc.definition LIKE ''%|].|[%(%'' ESCAPE ''|''
                AND  CHARINDEX(N''['', cc.definition) > 0
                AND  CHARINDEX(N''].['', cc.definition) > 0
                AND  CHARINDEX(N'']'', cc.definition, CHARINDEX(N''].['', cc.definition) + 3) > 0
                THEN
                    SUBSTRING
                    (
                        cc.definition,
                        CHARINDEX(N''['', cc.definition),
                        CHARINDEX
                        (
                            N'']'',
                            cc.definition,
                            CHARINDEX
                            (
                                N''].['',
                                cc.definition
                            ) + 3
                        ) -
                        CHARINDEX(N''['', cc.definition) + 1
                    )
                ELSE NULL
            END
    FROM #filtered_objects AS fo
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.check_constraints AS cc
      ON fo.object_id = cc.parent_object_id
    OPTION(RECOMPILE);';

    IF @debug = 1
    BEGIN
        PRINT @sql;
    END;

    INSERT INTO
        #check_constraints_analysis
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        constraint_id,
        constraint_name,
        definition,
        contains_udf,
        udf_names
    )
    EXECUTE sys.sp_executesql
        @sql;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#check_constraints_analysis',
            cca.*
        FROM #check_constraints_analysis AS cca
        OPTION(RECOMPILE);

        RAISERROR('Generating #operational_stats insert', 0, 0) WITH NOWAIT;
    END;

    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT
        os.database_id,
        database_name = DB_NAME(os.database_id),
        schema_id = s.schema_id,
        schema_name = s.name,
        os.object_id,
        table_name = ISNULL(t.name, v.name),
        os.index_id,
        index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''),
        range_scan_count = SUM(os.range_scan_count),
        singleton_lookup_count = SUM(os.singleton_lookup_count),
        forwarded_fetch_count = SUM(os.forwarded_fetch_count),
        lob_fetch_in_pages = SUM(os.lob_fetch_in_pages),
        row_overflow_fetch_in_pages = SUM(os.row_overflow_fetch_in_pages),
        leaf_insert_count = SUM(os.leaf_insert_count),
        leaf_update_count = SUM(os.leaf_update_count),
        leaf_delete_count = SUM(os.leaf_delete_count),
        leaf_ghost_count = SUM(os.leaf_ghost_count),
        nonleaf_insert_count = SUM(os.nonleaf_insert_count),
        nonleaf_update_count = SUM(os.nonleaf_update_count),
        nonleaf_delete_count = SUM(os.nonleaf_delete_count),
        leaf_allocation_count = SUM(os.leaf_allocation_count),
        nonleaf_allocation_count = SUM(os.nonleaf_allocation_count),
        row_lock_count = SUM(os.row_lock_count),
        row_lock_wait_count = SUM(os.row_lock_wait_count),
        row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
        page_lock_count = SUM(os.page_lock_count),
        page_lock_wait_count = SUM(os.page_lock_wait_count),
        page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
        index_lock_promotion_attempt_count = SUM(os.index_lock_promotion_attempt_count),
        index_lock_promotion_count = SUM(os.index_lock_promotion_count),
        page_latch_wait_count = SUM(os.page_latch_wait_count),
        page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
        tree_page_latch_wait_count = SUM(os.tree_page_latch_wait_count),
        tree_page_latch_wait_in_ms = SUM(os.tree_page_latch_wait_in_ms),
        page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
        page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
        page_compression_attempt_count = SUM(os.page_compression_attempt_count),
        page_compression_success_count = SUM(os.page_compression_success_count)
    FROM ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_operational_stats
    (
        @database_id,
        @object_id,
        NULL,
        NULL
    ) AS os
    LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t
      ON os.object_id = t.object_id
    LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v
      ON os.object_id = v.object_id
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s
      ON ISNULL(t.schema_id, v.schema_id) = s.schema_id
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i
      ON  os.object_id = i.object_id
      AND os.index_id = i.index_id
    WHERE EXISTS
    (
        SELECT
            1/0
        FROM #filtered_objects AS fo
        WHERE fo.database_id = os.database_id
        AND   fo.object_id = os.object_id
    )
    GROUP BY
        os.database_id,
        DB_NAME(os.database_id),
        s.schema_id,
        s.name,
        os.object_id,
        ISNULL(t.name, v.name),
        os.index_id,
        i.name
    OPTION(RECOMPILE);
    ';

    IF @debug = 1
    BEGIN
        PRINT @sql;
    END;

    INSERT INTO
        #operational_stats
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        index_id,
        index_name,
        range_scan_count,
        singleton_lookup_count,
        forwarded_fetch_count,
        lob_fetch_in_pages,
        row_overflow_fetch_in_pages,
        leaf_insert_count,
        leaf_update_count,
        leaf_delete_count,
        leaf_ghost_count,
        nonleaf_insert_count,
        nonleaf_update_count,
        nonleaf_delete_count,
        leaf_allocation_count,
        nonleaf_allocation_count,
        row_lock_count,
        row_lock_wait_count,
        row_lock_wait_in_ms,
        page_lock_count,
        page_lock_wait_count,
        page_lock_wait_in_ms,
        index_lock_promotion_attempt_count,
        index_lock_promotion_count,
        page_latch_wait_count,
        page_latch_wait_in_ms,
        tree_page_latch_wait_count,
        tree_page_latch_wait_in_ms,
        page_io_latch_wait_count,
        page_io_latch_wait_in_ms,
        page_compression_attempt_count,
        page_compression_success_count
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer,
        @object_id integer',
        @current_database_id,
        @object_id;

    SET @rc = ROWCOUNT_BIG();

    IF @rc = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('No rows inserted into #operational_stats', 0, 0) WITH NOWAIT;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#operational_stats',
            os.*
        FROM #operational_stats AS os
        OPTION(RECOMPILE);

        RAISERROR('Generating #index_details insert', 0, 0) WITH NOWAIT;
    END;

    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT
        database_id = @database_id,
        database_name = DB_NAME(@database_id),
        i.object_id,
        i.index_id,
        s.schema_id,
        schema_name = s.name,
        table_name = ISNULL(t.name, v.name),
        index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''),
        column_name = c.name,
        column_id = c.column_id,
        i.is_primary_key,
        i.is_unique,
        i.is_unique_constraint,
        is_indexed_view =
            CASE
                WHEN EXISTS
                (
                    SELECT
                        1/0
                    FROM ' + QUOTENAME(@current_database_name) + N'.sys.objects AS so
                    WHERE i.object_id = so.object_id
                    AND   so.is_ms_shipped = 0
                    AND   so.type = ''V''
                )
                THEN 1
                ELSE 0
            END,
        is_foreign_key =
            CASE
                WHEN EXISTS
                     (
                         SELECT
                             1/0
                         FROM ' + QUOTENAME(@current_database_name) + N'.sys.foreign_key_columns AS f
                         WHERE f.parent_column_id = c.column_id
                         AND   f.parent_object_id = c.object_id
                     )
                THEN 1
                ELSE 0
            END,
        is_foreign_key_reference =
            CASE
                WHEN EXISTS
                     (
                         SELECT
                             1/0
                         FROM ' + QUOTENAME(@current_database_name) + N'.sys.foreign_key_columns AS f
                         WHERE f.referenced_column_id = c.column_id
                         AND   f.referenced_object_id = c.object_id
                     )
                THEN 1
                ELSE 0
            END,
        ic.key_ordinal,
        ic.index_column_id,
        ic.is_descending_key,
        ic.is_included_column,
        i.filter_definition,
        is_max_length =
            CASE
                WHEN EXISTS
                     (
                         SELECT
                             1/0
                         FROM ' + QUOTENAME(@current_database_name) + N'.sys.types AS t
                         WHERE  c.system_type_id = t.system_type_id
                         AND    c.user_type_id = t.user_type_id
                         AND    t.name IN (N''varchar'', N''nvarchar'')
                         AND    c.max_length = -1
                     )
                THEN 1
                ELSE 0
            END,' +
        CASE
            WHEN @supports_optimize_for_sequential_key = 1
            THEN N'
        optimize_for_sequential_key = ISNULL(i.optimize_for_sequential_key, 0),'
            ELSE N'
        optimize_for_sequential_key = 0,'
        END + N'
        user_seeks = ISNULL(us.user_seeks, 0),
        user_scans = ISNULL(us.user_scans, 0),
        user_lookups = ISNULL(us.user_lookups, 0),
        user_updates = ISNULL(us.user_updates, 0),
        us.last_user_seek,
        us.last_user_scan,
        us.last_user_lookup,
        us.last_user_update,
        is_eligible_for_dedupe =
            CASE
                WHEN i.type = 2
                THEN 1
                WHEN
                (
                     i.type = 1
                  OR i.is_primary_key = 1
                )
                THEN 0
            END
    FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i
    LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t
      ON i.object_id = t.object_id
    LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v
      ON i.object_id = v.object_id
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s
      ON ISNULL(t.schema_id, v.schema_id) = s.schema_id
    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.index_columns AS ic
      ON  i.object_id = ic.object_id
      AND i.index_id = ic.index_id
    JOIN ' + QUOTENAME(@current_database_name) +
    CONVERT
    (
        nvarchar(MAX),
        N'.sys.columns AS c
      ON  ic.object_id = c.object_id
      AND ic.column_id = c.column_id
    LEFT JOIN sys.dm_db_index_usage_stats AS us
      ON  i.object_id = us.object_id
      AND i.index_id = us.index_id
      AND us.database_id = @database_id
    WHERE (t.object_id IS NULL OR t.is_ms_shipped = 0)
    AND   i.type IN (1, 2)
    AND   i.is_disabled = 0
    AND   i.is_hypothetical = 0
    AND   EXISTS
    (
        SELECT
            1/0
        FROM #filtered_objects AS fo
        WHERE fo.database_id = @database_id
        AND   fo.object_id = i.object_id
    )
    AND   EXISTS
    (
        SELECT
            1/0
        FROM '
    ) + QUOTENAME(@current_database_name) +
        CONVERT
        (
            nvarchar(MAX),
            N'.sys.dm_db_partition_stats ps
        WHERE ps.object_id = i.object_id
        AND   ps.index_id = 1
        AND   ps.row_count >= @min_rows
    )'
        );

    IF @object_id IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('adding object+id filter', 0, 0) WITH NOWAIT;
        END;

        SELECT @sql += N'
    AND   i.object_id = @object_id';
    END;

    SELECT
        @sql += CONVERT
        (
            nvarchar(max),
            N'
    AND   NOT EXISTS
    (
          SELECT
              1/0
          FROM ' + QUOTENAME(@current_database_name) + N'.sys.objects AS so
          WHERE i.object_id = so.object_id
          AND   so.is_ms_shipped = 0
          AND   so.type = N''TF''
    )
    OPTION(RECOMPILE);
    '
        );

    IF @debug = 1
    BEGIN
        PRINT SUBSTRING(@sql, 1, 4000);
        PRINT SUBSTRING(@sql, 4000, 8000);
    END;

    INSERT INTO
        #index_details
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        object_id,
        index_id,
        schema_id,
        schema_name,
        table_name,
        index_name,
        column_name,
        column_id,
        is_primary_key,
        is_unique,
        is_unique_constraint,
        is_indexed_view,
        is_foreign_key,
        is_foreign_key_reference,
        key_ordinal,
        index_column_id,
        is_descending_key,
        is_included_column,
        filter_definition,
        is_max_length,
        optimize_for_sequential_key,
        user_seeks,
        user_scans,
        user_lookups,
        user_updates,
        last_user_seek,
        last_user_scan,
        last_user_lookup,
        last_user_update,
        is_eligible_for_dedupe
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer,
        @object_id integer,
        @min_rows bigint',
        @current_database_id,
        @object_id,
        @min_rows;

    SET @rc = ROWCOUNT_BIG();

    IF @rc = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('No rows inserted into #index_details', 0, 0) WITH NOWAIT;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_details',
            *
        FROM #index_details AS id;

        RAISERROR('Generating #partition_stats insert', 0, 0) WITH NOWAIT;
    END;

    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT
        database_id = @database_id,
        database_name = DB_NAME(@database_id),
        x.object_id,
        x.index_id,
        x.schema_id,
        x.schema_name,
        x.table_name,
        x.index_name,
        x.partition_id,
        x.partition_number,
        x.total_rows,
        x.total_space_gb,
        x.reserved_lob_gb,
        x.reserved_row_overflow_gb,
        x.data_compression_desc,
        built_on =
            ISNULL
            (
                psfg.partition_scheme_name,
                psfg.filegroup_name
            ),
        psfg.partition_function_name,
        pc.partition_columns
    FROM
    (
        SELECT DISTINCT
            ps.object_id,
            ps.index_id,
            s.schema_id,
            schema_name = s.name,
            table_name = ISNULL(t.name, v.name),
            index_name = ISNULL(i.name, ISNULL(t.name, v.name) + N''.Heap''),
            ps.partition_id,
            p.partition_number,
            total_rows = ps.row_count,
            total_space_gb = SUM(a.total_pages) * 8 / 1024.0 / 1024.0, /* Convert directly to GB */
            reserved_lob_gb = SUM(ps.lob_reserved_page_count) * 8. / 1024. / 1024.0, /* Convert directly to GB */
            reserved_row_overflow_gb = SUM(ps.row_overflow_reserved_page_count) * 8. / 1024. / 1024.0, /* Convert directly to GB */
            p.data_compression_desc,
            i.data_space_id
        FROM ' + QUOTENAME(@current_database_name) + N'.sys.indexes AS i
        LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.tables AS t
          ON i.object_id = t.object_id
        LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.views AS v
          ON i.object_id = v.object_id
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.schemas AS s
          ON ISNULL(t.schema_id, v.schema_id) = s.schema_id
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partitions AS p
          ON  i.object_id = p.object_id
          AND i.index_id = p.index_id
        JOIN ' + QUOTENAME(@current_database_name) + N'.sys.allocation_units AS a
          ON p.partition_id = a.container_id
        LEFT HASH JOIN ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_partition_stats AS ps
          ON p.partition_id = ps.partition_id
        WHERE (t.object_id IS NULL OR t.type <> N''TF'')
        AND   i.type IN (1, 2)
        AND   EXISTS
        (
            SELECT
                1/0
            FROM #filtered_objects AS fo
            WHERE fo.database_id = @database_id
            AND   fo.object_id = i.object_id
        )';

    IF @object_id IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('adding in object_id filter', 0, 0) WITH NOWAIT;
        END;

        SELECT @sql += N'
        AND   i.object_id = @object_id';
    END;

    SELECT
        @sql += N'
        GROUP BY
            ps.object_id,
            ps.index_id,
            s.schema_id,
            s.name,
            ISNULL(t.name, v.name),
            i.name,
            ps.partition_id,
            p.partition_number,
            ps.row_count,
            p.data_compression_desc,
            i.data_space_id
    ) AS x
    OUTER APPLY
    (
        SELECT
            filegroup_name =
                fg.name,
            partition_scheme_name =
                ps.name,
            partition_function_name =
                pf.name
        FROM ' + QUOTENAME(@current_database_name) + N'.sys.filegroups AS fg
        FULL JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partition_schemes AS ps
          ON ps.data_space_id = fg.data_space_id
        LEFT JOIN ' + QUOTENAME(@current_database_name) + N'.sys.partition_functions AS pf
          ON pf.function_id = ps.function_id
        WHERE x.data_space_id = fg.data_space_id
        OR    x.data_space_id = ps.data_space_id
    ) AS psfg
    OUTER APPLY
    (
        SELECT
            partition_columns =
                STUFF
                (
                  (
                    SELECT
                        N'', '' +
                        c.name
                    FROM ' + QUOTENAME(@current_database_name) + N'.sys.index_columns AS ic
                    JOIN ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
                      ON c.object_id = ic.object_id
                     AND c.column_id = ic.column_id
                    WHERE ic.object_id = x.object_id
                    AND   ic.index_id = x.index_id
                    AND   ic.partition_ordinal > 0
                    ORDER BY
                        ic.partition_ordinal
                    FOR
                        XML
                        PATH(''''),
                        TYPE
                  ).value(''.'', ''nvarchar(max)''),
                  1,
                  2,
                  ''''
                )
    ) AS pc
    OPTION(RECOMPILE);
    ';

    IF @debug = 1
    BEGIN
        PRINT SUBSTRING(@sql, 1, 4000);
        PRINT SUBSTRING(@sql, 4000, 8000);
    END;

    INSERT INTO
        #partition_stats WITH(TABLOCK)
    (
        database_id,
        database_name,
        object_id,
        index_id,
        schema_id,
        schema_name,
        table_name,
        index_name,
        partition_id,
        partition_number,
        total_rows,
        total_space_gb,
        reserved_lob_gb,
        reserved_row_overflow_gb,
        data_compression_desc,
        built_on,
        partition_function_name,
        partition_columns
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer,
        @object_id integer',
        @current_database_id,
        @object_id;

    SET @rc = ROWCOUNT_BIG();

    IF @rc = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('No rows inserted into #partition_stats', 0, 0) WITH NOWAIT;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#partition_stats',
            *
        FROM #partition_stats AS ps
        OPTION(RECOMPILE);

        RAISERROR('Performing #index_analysis insert', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_analysis
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        table_name,
        object_id,
        index_id,
        index_name,
        is_unique,
        key_columns,
        included_columns,
        filter_definition,
        original_index_definition
    )
    SELECT
        @current_database_id,
        database_name = DB_NAME(@current_database_id),
        id1.schema_id,
        id1.schema_name,
        id1.table_name,
        id1.object_id,
        id1.index_id,
        id1.index_name,
        id1.is_unique,
        key_columns =
            STUFF
            (
              (
                SELECT
                    N', ' +
                    QUOTENAME(id2.column_name) +
                    CASE
                        WHEN id2.is_descending_key = 1
                        THEN N' DESC'
                        ELSE N''
                    END
                FROM #index_details id2
                WHERE id2.object_id = id1.object_id
                AND   id2.index_id = id1.index_id
                AND   id2.is_included_column = 0
                GROUP BY
                    id2.column_name,
                    id2.is_descending_key,
                    id2.key_ordinal
                ORDER BY
                    id2.key_ordinal
                FOR
                    XML
                    PATH(''),
                    TYPE
              ).value('text()[1]','nvarchar(max)'),
              1,
              2,
              ''
            ),
        included_columns =
            STUFF
            (
              (
                SELECT
                    N', ' +
                    QUOTENAME(id2.column_name)
                FROM #index_details id2
                WHERE id2.object_id = id1.object_id
                AND   id2.index_id = id1.index_id
                AND   id2.is_included_column = 1
                GROUP BY
                    id2.column_name
                ORDER BY
                    id2.column_name
                FOR
                    XML
                    PATH(''),
                    TYPE
              ).value('text()[1]','nvarchar(max)'),
              1,
              2,
              ''
            ),
        id1.filter_definition,
        /* Store the original index definition for validation */
        original_index_definition =
            CASE
                /* For unique constraints, use ALTER TABLE ADD CONSTRAINT syntax */
                WHEN id1.is_unique_constraint = 1
                THEN
                    N'ALTER TABLE ' +
                    QUOTENAME(DB_NAME(@current_database_id)) +
                    N'.' +
                    QUOTENAME(id1.schema_name) +
                    N'.' +
                    QUOTENAME(id1.table_name) +
                    N' ADD CONSTRAINT ' +
                    QUOTENAME(id1.index_name) +
                    N' UNIQUE ('
                /* For regular indexes, use CREATE INDEX syntax */
                ELSE
                    N'CREATE ' +
                    CASE
                        WHEN id1.is_unique = 1
                        THEN N'UNIQUE '
                    ELSE N''
                    END +
                    CASE
                        WHEN id1.index_id = 1
                        THEN N'CLUSTERED '
                        WHEN id1.index_id > 1
                        THEN N'NONCLUSTERED '
                        ELSE N''
                    END +
                    N'INDEX ' +
                    QUOTENAME(id1.index_name) +
                    N' ON ' +
                    QUOTENAME(DB_NAME(@current_database_id)) +
                    N'.' +
                    QUOTENAME(id1.schema_name) +
                    N'.' +
                    QUOTENAME(id1.table_name) +
                    N' ('
            END +
            STUFF
            (
                (
                    SELECT
                        N', ' +
                        QUOTENAME(id2.column_name) +
                        CASE
                            WHEN id2.is_descending_key = 1
                            THEN N' DESC'
                            ELSE N''
                        END
                    FROM #index_details id2
                    WHERE id2.object_id = id1.object_id
                    AND   id2.index_id = id1.index_id
                    AND   id2.is_included_column = 0
                    GROUP BY
                        id2.column_name,
                        id2.is_descending_key,
                        id2.key_ordinal
                    ORDER BY
                        id2.key_ordinal
                    FOR
                        XML
                        PATH(''),
                        TYPE
                ).value('text()[1]','nvarchar(max)'),
                1,
                2,
                ''
            ) +
            N')' +
            CASE
                WHEN EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details id3
                    WHERE id3.object_id = id1.object_id
                    AND   id3.index_id = id1.index_id
                    AND   id3.is_included_column = 1
                )
                THEN N' INCLUDE (' +
                    STUFF
                    (
                        (
                            SELECT
                                N', ' +
                                QUOTENAME(id4.column_name)
                            FROM #index_details id4
                            WHERE id4.object_id = id1.object_id
                            AND   id4.index_id = id1.index_id
                            AND   id4.is_included_column = 1
                            GROUP BY
                                id4.column_id,
                                id4.column_name
                            ORDER BY
                                id4.column_id,
                                id4.column_name
                            FOR
                                XML
                                PATH(''),
                                TYPE
                        ).value('text()[1]','nvarchar(max)'),
                        1,
                        2,
                        ''
                    ) +
                    N')'
                ELSE N''
            END +
            CASE
                WHEN id1.filter_definition IS NOT NULL
                THEN N' WHERE ' + id1.filter_definition
                ELSE N''
            END +
            N';'
    FROM #index_details id1
    WHERE id1.is_eligible_for_dedupe = 1
    GROUP BY
        id1.schema_name,
        id1.schema_id,
        id1.table_name,
        id1.index_name,
        id1.index_id,
        id1.is_unique,
        id1.object_id,
        id1.index_id,
        id1.filter_definition,
        id1.is_unique_constraint
    OPTION(RECOMPILE);

    SET @rc = ROWCOUNT_BIG();

    IF @rc = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('No rows inserted into #index_analysis', 0, 0) WITH NOWAIT;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);

        RAISERROR('Analyzing filtered indexes for columns to include', 0, 0) WITH NOWAIT;
    END;

    /* Analyze filtered indexes to identify columns used in filters that should be included */
    SELECT
        @sql = N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT DISTINCT
        ia.database_id,
        ia.database_name,
        ia.schema_id,
        ia.schema_name,
        ia.object_id,
        ia.table_name,
        ia.index_id,
        ia.index_name,
        ia.filter_definition,
        missing_included_columns =
            (
                SELECT
                    STUFF
                    (
                        (
                            /* Find column names mentioned in filter_definition that aren''t already key or included columns */
                            SELECT
                                N'', '' +
                                c.name
                            FROM ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
                            WHERE c.object_id = ia.object_id
                            AND   ia.filter_definition LIKE N''%'' + c.name + N''%'' COLLATE DATABASE_DEFAULT
                            AND   NOT EXISTS
                            (
                                SELECT
                                    1/0
                                FROM #index_details AS id
                                WHERE id.object_id = ia.object_id
                                AND   id.index_id = ia.index_id
                                AND   id.column_id = c.column_id
                            )
                            GROUP BY
                                c.name
                            FOR
                                XML
                                PATH(''''),
                                TYPE
                        ).value(''text()[1]'',''nvarchar(max)''),
                        1,
                        2,
                        N''''
                    )
            ),
        should_include_filter_columns =
            CASE
                WHEN EXISTS
                (
                    /* Check if any columns mentioned in filter_definition aren''t already in the index */
                    SELECT
                        1/0
                    FROM ' + QUOTENAME(@current_database_name) + N'.sys.columns AS c
                    WHERE c.object_id = ia.object_id
                    AND   ia.filter_definition LIKE N''%'' + c.name + N''%'' COLLATE DATABASE_DEFAULT
                    AND   NOT EXISTS
                    (
                        SELECT
                            1/0
                        FROM #index_details AS id
                        WHERE id.object_id = ia.object_id
                        AND   id.index_id = ia.index_id
                        AND   id.column_id = c.column_id
                    )
                )
                THEN 1
                ELSE 0
            END
    FROM #index_analysis AS ia
    WHERE ia.filter_definition IS NOT NULL
    AND   ia.database_id = @current_database_id
    OPTION(RECOMPILE);';

    IF @debug = 1
    BEGIN
        RAISERROR('Filtered index analysis SQL:', 0, 1) WITH NOWAIT;
        PRINT @sql;
    END;

    INSERT INTO
        #filtered_index_columns_analysis
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        object_id,
        table_name,
        index_id,
        index_name,
        filter_definition,
        missing_included_columns,
        should_include_filter_columns
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@current_database_id integer',
        @current_database_id;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#filtered_index_columns_analysis',
            fica.*
        FROM #filtered_index_columns_analysis AS fica
        OPTION(RECOMPILE);

        RAISERROR('Starting updates', 0, 0) WITH NOWAIT;
    END;

    /* Calculate index priority scores based on actual columns that exist */
    UPDATE
        #index_analysis
    SET
        #index_analysis.index_priority =
            CASE
                WHEN #index_analysis.index_id = 1
                THEN 1000  /* Clustered indexes get highest priority */
                ELSE 0
            END
            +
            CASE
                /* Unique indexes get high priority, but reduce priority for unique constraints */
                WHEN #index_analysis.is_unique = 1 AND NOT EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details AS id_uc
                    WHERE id_uc.index_hash = #index_analysis.index_hash
                    AND   id_uc.is_unique_constraint = 1
                ) THEN 500
                /* Unique constraints get lower priority */
                WHEN #index_analysis.is_unique = 1 AND EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details AS id_uc
                    WHERE id_uc.index_hash = #index_analysis.index_hash
                    AND   id_uc.is_unique_constraint = 1
                ) THEN 50
                ELSE 0
            END
            +
            CASE
                WHEN EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details AS id
                    WHERE id.index_hash = #index_analysis.index_hash
                    AND   id.user_seeks > 0
                ) THEN 200
                ELSE 0
            END  /* Indexes with seeks get priority */
            +
            CASE
                WHEN EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details AS id
                    WHERE id.index_hash = #index_analysis.index_hash
                    AND   id.user_scans > 0
                ) THEN 100 ELSE 0
            END /* Indexes with scans get some priority */
            +
            CASE
                WHEN #index_analysis.included_columns IS NOT NULL
                AND  LEN(#index_analysis.included_columns) > 0
                THEN 50  /* Indexes with includes get priority over those without */
                ELSE 0
            END /* Prefer indexes with included columns */
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after priority score',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 1: Identify unused indexes */
    IF @dedupe_only = 0
    BEGIN
        UPDATE
            #index_analysis
        SET
            #index_analysis.consolidation_rule =
                CASE
                    WHEN @uptime_warning = 1
                    THEN 'Unused Index (WARNING: Server uptime < 14 days - usage data may be incomplete)'
                    ELSE 'Unused Index'
                END,
            #index_analysis.action = N'DISABLE'
        WHERE EXISTS
        (
            SELECT
                1/0
            FROM #index_details id
            WHERE id.index_hash = #index_analysis.index_hash
            AND   id.user_seeks = 0
            AND   id.user_scans = 0
            AND   id.user_lookups = 0
            AND   id.is_primary_key = 0  /* Don't disable primary keys */
            AND   id.is_unique_constraint = 0  /* Don't disable unique constraints */
            AND   id.is_eligible_for_dedupe = 1 /* Only eligible indexes */
        )
        AND #index_analysis.index_id <> 1 /* Don't disable clustered indexes */
        OPTION(RECOMPILE);
    END;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 1',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 2: Exact duplicates - matching key columns and includes */
    UPDATE
        ia1
    SET
        ia1.consolidation_rule = N'Exact Duplicate',
        ia1.target_index_name =
            CASE
                WHEN ia1.index_priority > ia2.index_priority
                THEN NULL  /* This index is the keeper */
                WHEN ia1.index_priority = ia2.index_priority
                AND  ia1.index_name < ia2.index_name
                THEN NULL  /* When tied, use alphabetical ordering for consistency */
                ELSE ia2.index_name  /* Other index is the keeper */
            END,
        ia1.action =
            CASE
                WHEN ia1.index_priority > ia2.index_priority
                THEN N'KEEP'  /* This index is the keeper */
                WHEN ia1.index_priority = ia2.index_priority
                AND  ia1.index_name < ia2.index_name
                THEN N'KEEP'  /* When tied, use alphabetical ordering for consistency */
                ELSE N'DISABLE'  /* Other index gets disabled */
            END
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.index_name <> ia2.index_name
      AND ia1.exact_match_hash = ia2.exact_match_hash  /* Exact match: keys + includes + filter */
    WHERE ia1.consolidation_rule IS NULL  /* Not already processed */
    AND   ia2.consolidation_rule IS NULL  /* Not already processed */
    /* Exclude unique constraints - we'll handle those separately in Rule 7 */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1_uc
        WHERE id1_uc.index_hash = ia1.index_hash
        AND   id1_uc.is_unique_constraint = 1
    )
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2_uc
        WHERE id2_uc.index_hash = ia2.index_hash
        AND   id2_uc.is_unique_constraint = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1
        WHERE id1.index_hash = ia1.index_hash
        AND   id1.is_eligible_for_dedupe = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2
        WHERE id2.index_hash = ia2.index_hash
        AND   id2.is_eligible_for_dedupe = 1
    )
    AND NOT EXISTS
    (
         SELECT
             1/0
         FROM #index_details AS id1
         JOIN #index_details AS id2
           ON id2.column_position_hash = id1.column_position_hash  /* Same table + column + position */
         WHERE id1.index_hash = ia1.index_hash  /* Specific index from ia1 */
           AND id2.index_hash = ia2.index_hash  /* Specific index from ia2 */
           AND id1.is_descending_key <> id2.is_descending_key
           /* Different sort direction */
    )
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 2',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    UPDATE
        ia1
    SET
        ia1.consolidation_rule = N'Key Subset',
        ia1.target_index_name =
        (
            /* Select the closest superset (fewest extra columns) for deterministic results */
            SELECT TOP (1)
                ia2_inner.index_name
            FROM #index_analysis AS ia2_inner
            WHERE ia2_inner.scope_hash = ia1.scope_hash
            AND   ia2_inner.index_name <> ia1.index_name
            AND   ia2_inner.key_columns LIKE (REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]'), '_', '~_'), '%', '~%') + N', %') ESCAPE '~'
            AND   ISNULL(ia2_inner.filter_definition, N'') = ISNULL(ia1.filter_definition, N'')
            AND   NOT (ia1.is_unique = 1 AND ia2_inner.is_unique = 0)
            AND   ia2_inner.consolidation_rule IS NULL
            AND EXISTS
            (
                SELECT
                    1/0
                FROM #index_details AS id2_inner
                WHERE id2_inner.index_hash = ia2_inner.index_hash
                AND   id2_inner.is_eligible_for_dedupe = 1
            )
            AND NOT EXISTS
            (
                SELECT
                    1/0
                FROM #index_details AS id1_check
                JOIN #index_details AS id2_check
                  ON id2_check.column_position_hash = id1_check.column_position_hash  /* Same table + column + position */
                WHERE id1_check.index_hash = ia1.index_hash  /* Specific index from ia1 */
                AND   id2_check.index_hash = ia2_inner.index_hash  /* Specific index from ia2_inner */
                AND   id1_check.is_descending_key <> id2_check.is_descending_key  /* Different sort direction */
            )
            ORDER BY
                LEN(ia2_inner.key_columns),  /* Prefer shorter key columns (closest superset) */
                ia2_inner.index_name  /* Then alphabetically for stability */
        ),
        ia1.action = N'DISABLE'  /* The narrower index gets disabled */
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.index_name <> ia2.index_name
      AND ia2.key_columns LIKE (REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]'), '_', '~_'), '%', '~%') + N', %') ESCAPE '~'  /* ia2 has wider key that starts with ia1's key */
      AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '')  /* Matching filters */
      /* Exception: If narrower index is unique and wider is not, they should not be merged */
      AND NOT (ia1.is_unique = 1 AND ia2.is_unique = 0)
    WHERE ia1.consolidation_rule IS NULL  /* Not already processed */
    AND   ia2.consolidation_rule IS NULL  /* Not already processed */
    /* Exclude unique constraints - we'll handle those separately in Rule 7 */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1_uc
        WHERE id1_uc.index_hash = ia1.index_hash
        AND   id1_uc.is_unique_constraint = 1
    )
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2_uc
        WHERE id2_uc.index_hash = ia2.index_hash
        AND   id2_uc.is_unique_constraint = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1
        WHERE id1.index_hash = ia1.index_hash
        AND   id1.is_eligible_for_dedupe = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2
        WHERE id2.index_hash = ia2.index_hash
        AND   id2.is_eligible_for_dedupe = 1
    )
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1
        JOIN #index_details AS id2
          ON id2.column_position_hash = id1.column_position_hash  /* Same table + column + position */
        WHERE id1.index_hash = ia1.index_hash  /* Specific index from ia1 */
        AND   id2.index_hash = ia2.index_hash  /* Specific index from ia2 */
        AND   id1.is_descending_key <> id2.is_descending_key  /* Different sort direction */
    )
    OPTION(RECOMPILE);

    DECLARE @rule3_rowcount bigint = ROWCOUNT_BIG();

    IF @debug = 1
    BEGIN
        RAISERROR('Rule 3 UPDATE completed - rows affected: %I64d', 0, 0, @rule3_rowcount) WITH NOWAIT;

        SELECT
            table_name = '#index_analysis after rule 3',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 4: Mark superset indexes for merging with includes from subset */
    UPDATE
        ia2
    SET
        ia2.consolidation_rule = N'Key Superset',
        ia2.action = N'MERGE INCLUDES',  /* The wider index gets merged with includes */
        ia2.superseded_by =
            ISNULL
            (
                ia2.superseded_by +
                ', ',
                ''
            ) +
            N'Supersedes ' +
            ia1.index_name
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.target_index_name = ia2.index_name  /* Link from Rule 3 */
    WHERE ia1.consolidation_rule = N'Key Subset'
    AND   ia1.action = N'DISABLE'
    AND   ia2.consolidation_rule IS NULL  /* Not already processed */
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 4',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 5: Key duplicates - matching key columns, different includes */
    UPDATE
        ia1
    SET
        ia1.consolidation_rule = N'Key Duplicate',
        ia1.target_index_name =
            CASE
                /* If one is unique and the other isn't, prefer the unique one */
                WHEN ia1.is_unique = 1
                AND  ia2.is_unique = 0
                THEN NULL
                WHEN ia1.is_unique = 0
                AND  ia2.is_unique = 1
                THEN ia2.index_name
                /* Otherwise use priority */
                WHEN ia1.index_priority >= ia2.index_priority
                THEN NULL
                ELSE ia2.index_name
            END,
        ia1.action =
            CASE
                WHEN (ia1.is_unique = 1 AND ia2.is_unique = 0)
                OR
                (
                    ia1.index_priority >= ia2.index_priority
                  AND NOT (ia1.is_unique = 0 AND ia2.is_unique = 1)
                )
                AND ISNULL(ia1.included_columns, N'') <> ISNULL(ia2.included_columns, N'')
                THEN N'MERGE INCLUDES'  /* Keep this index but merge includes */
                ELSE N'DISABLE'  /* Other index is keeper, disable this one */
            END,
        /* For the winning index, set clear superseded_by text for the report */
        ia1.superseded_by =
            CASE
                WHEN (ia1.is_unique = 1 AND ia2.is_unique = 0)
                OR
                (
                    ia1.index_priority >= ia2.index_priority
                  AND NOT (ia1.is_unique = 0 AND ia2.is_unique = 1)
                )
                THEN N'Supersedes ' +
                     ia2.index_name
                ELSE NULL
            END
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.index_name <> ia2.index_name
      AND ia1.key_filter_hash = ia2.key_filter_hash  /* Same keys and filter */
      AND ISNULL(ia1.included_columns, '') <> ISNULL(ia2.included_columns, '')  /* Different includes */
    WHERE ia1.consolidation_rule IS NULL  /* Not already processed */
    AND   ia2.consolidation_rule IS NULL  /* Not already processed */
    /* Exclude pairs where either one is a unique constraint (we'll handle those separately in Rule 7) */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1_uc
        WHERE id1_uc.index_hash = ia1.index_hash
        AND   id1_uc.is_unique_constraint = 1
    )
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2_uc
        WHERE id2_uc.index_hash = ia2.index_hash
        AND   id2_uc.is_unique_constraint = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id1
        WHERE id1.index_hash = ia1.index_hash
        AND   id1.is_eligible_for_dedupe = 1
    )
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id2
        WHERE id2.index_hash = ia2.index_hash
        AND   id2.is_eligible_for_dedupe = 1
    )
    OPTION(RECOMPILE);

    DECLARE @rule5_rowcount bigint = ROWCOUNT_BIG();

    IF @debug = 1
    BEGIN
        RAISERROR('Rule 5 UPDATE completed - rows affected: %I64d', 0, 0, @rule5_rowcount) WITH NOWAIT;

        SELECT
            table_name = '#index_analysis after rule 5',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 6: Merge includes from subset to superset indexes */
    /* Pre-compute merged includes in a temp table to handle multiple subsets per superset */
    IF @debug = 1
    BEGIN
        SELECT
            debug_label = 'Subsets for Rule 6 merge',
            superset_name = superset.index_name,
            subset_name = subset.index_name,
            subset_includes = subset.included_columns
        FROM #index_analysis AS superset
        JOIN #index_analysis AS subset
          ON  subset.scope_hash = superset.scope_hash
          AND subset.target_index_name = superset.index_name
          AND subset.action = N'DISABLE'
          AND subset.consolidation_rule = N'Key Subset'
        WHERE superset.action = N'MERGE INCLUDES'
        AND   superset.consolidation_rule = N'Key Superset'
        OPTION(RECOMPILE);
    END;

    /* Gather all supersets that need include merging */
    INSERT INTO
        #merged_includes
    WITH
        (TABLOCK)
    (
        scope_hash,
        index_name,
        key_columns,
        merged_includes
    )
    SELECT
        superset.scope_hash,
        superset.index_name,
        superset.key_columns,
        merged_includes =
            STUFF
            (
                (
                    SELECT DISTINCT
                        N', ' +
                        t.c.value('.', 'sysname')
                    FROM
                    (
                        /* Superset's own includes */
                        SELECT
                            x = CONVERT
                            (
                                xml,
                                N'<c>' +
                                REPLACE(superset.included_columns, N', ', N'</c><c>') +
                                N'</c>'
                            )
                        WHERE superset.included_columns IS NOT NULL

                        UNION ALL

                        /* ALL subsets' includes */
                        SELECT
                            x = CONVERT
                            (
                                xml,
                                N'<c>' +
                                REPLACE(subset.included_columns, N', ', N'</c><c>') +
                                N'</c>'
                            )
                        FROM #index_analysis AS subset
                        WHERE subset.scope_hash = superset.scope_hash
                        AND   subset.target_index_name = superset.index_name
                        AND   subset.action = N'DISABLE'
                        AND   subset.consolidation_rule = N'Key Subset'
                        AND   subset.included_columns IS NOT NULL
                    ) AS a
                    CROSS APPLY a.x.nodes('/c') AS t(c)
                    /* Filter out columns already in superset's key */
                    WHERE CHARINDEX(t.c.value('.', 'sysname'), superset.key_columns) = 0
                    AND   LEN(t.c.value('.', 'sysname')) > 0
                    FOR
                        XML
                        PATH('')
                ),
                1,
                2,
                ''
            )
    FROM #index_analysis AS superset
    WHERE superset.action = N'MERGE INCLUDES'
    AND   superset.consolidation_rule = N'Key Superset'
    OPTION(RECOMPILE);

    /* Apply the pre-computed merged includes */
    UPDATE
        ia
    SET
        ia.included_columns = mi.merged_includes
    FROM #index_analysis AS ia
    JOIN #merged_includes AS mi
      ON  mi.scope_hash = ia.scope_hash
      AND mi.index_name = ia.index_name
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 6',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Update the superseded_by column for the wider index in a separate statement */
    UPDATE
        ia2
    SET
        ia2.superseded_by =
            N'Supersedes ' +
            ia1.index_name
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.index_name <> ia2.index_name
      AND ia2.key_columns LIKE (REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]'), '_', '~_'), '%', '~%') + N'%') ESCAPE '~'  /* ia2 has wider key that starts with ia1's key */
      AND ISNULL(ia1.filter_definition, '') = ISNULL(ia2.filter_definition, '')  /* Matching filters */
      /* Exception: If narrower index is unique and wider is not, they should not be merged */
      AND NOT (ia1.is_unique = 1 AND ia2.is_unique = 0)
    WHERE ia1.consolidation_rule = N'Key Subset'  /* Use records just processed in previous UPDATE */
    AND   ia1.target_index_name = ia2.index_name  /* Make sure we're updating the right wider index */
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after update superseded',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 7: Unique constraint vs. nonclustered index handling */
    UPDATE
        ia1
    SET
        ia1.consolidation_rule = N'Unique Constraint Replacement',
        ia1.action =
            CASE
                WHEN ia1.is_unique = 0
                THEN 'MAKE UNIQUE'  /* Convert to unique index */
                ELSE 'KEEP'  /* Already unique, so just keep it */
            END
    FROM #index_analysis AS ia1
    WHERE ia1.consolidation_rule IS NULL /* Not already processed */
    AND   ia1.action IS NULL /* Not already processed by earlier rules */
    AND EXISTS
    (
        /* Find nonclustered indexes */
        SELECT
            1/0
        FROM #index_details AS id1
        WHERE id1.index_hash = ia1.index_hash
        AND   id1.is_eligible_for_dedupe = 1
    )
    AND EXISTS
    (
        /* Find unique constraints with matching key columns */
        SELECT
            1/0
        FROM #index_details AS id2
        WHERE id2.scope_hash = ia1.scope_hash
        AND   id2.is_unique_constraint = 1
        AND NOT EXISTS
        (
            /* Verify key columns match between index and unique constraint */
            SELECT
                id2_inner.column_name
            FROM #index_details AS id2_inner
            WHERE id2_inner.index_hash = id2.index_hash
            AND   id2_inner.is_included_column = 0

            EXCEPT

            SELECT
                id1_inner.column_name
            FROM #index_details AS id1_inner
            WHERE id1_inner.index_hash = ia1.index_hash
            AND   id1_inner.is_included_column = 0
        )
    )
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 7',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 7.5: Mark unique constraints that have matching nonclustered indexes for disabling */
    /* First, mark unique constraints for disabling */
    UPDATE
        ia_uc
    SET
        ia_uc.consolidation_rule = N'Unique Constraint Replacement',
        ia_uc.action = N'DISABLE', /* Mark unique constraint for disabling */
        ia_uc.target_index_name = ia_nc.index_name /* Point to the nonclustered index that will replace it */
    FROM #index_analysis AS ia_uc /* Unique constraint */
    JOIN #index_details AS id_uc /* Join to get unique constraint details */
      ON  id_uc.index_hash = ia_uc.index_hash
      AND id_uc.is_unique_constraint = 1 /* This is a unique constraint */
    JOIN #index_analysis AS ia_nc /* Join to find nonclustered index */
      ON  ia_nc.scope_hash = ia_uc.scope_hash  /* Same database and object */
      AND ia_nc.index_name <> ia_uc.index_name /* Different index */
      AND ia_uc.key_columns = ia_nc.key_columns  /* Verify key columns EXACT match */
    OPTION(RECOMPILE);

    /* Second, mark nonclustered indexes to be made unique */
    UPDATE
        ia_nc
    SET
        ia_nc.consolidation_rule = N'Unique Constraint Replacement',
        ia_nc.action = N'MAKE UNIQUE', /* Mark nonclustered index to be made unique */
        /* CRITICAL: Set target_index_name to NULL to ensure it gets a MERGE script */
        ia_nc.target_index_name = NULL
    FROM #index_analysis AS ia_nc /* Nonclustered index */
    JOIN #index_details AS id_nc /* Join to get nonclustered index details */
      ON  id_nc.index_hash = ia_nc.index_hash
      AND id_nc.is_unique_constraint = 0 /* This is not a unique constraint */
    WHERE
        /* Two conditions for matching:
           1. Index key columns exactly match a unique constraint's key columns
           2. A unique constraint is already marked for DISABLE and has this index as target */
        EXISTS
        (
            /* Find unique constraint with matching keys that should be disabled */
            SELECT
                1/0
            FROM #index_analysis AS ia_uc
            JOIN #index_details AS id_uc
              ON  id_uc.index_hash = ia_uc.index_hash
              AND id_uc.is_unique_constraint = 1
            WHERE ia_uc.scope_hash = ia_nc.scope_hash
                  /* Check that both indexes have EXACTLY the same key columns */
            AND   ia_uc.key_columns = ia_nc.key_columns
        )
    OPTION(RECOMPILE);

    /* CRITICAL: Ensure that only the unique constraints that exactly match get this treatment */
    /* And remove any incorrect MAKE UNIQUE actions */
    UPDATE
        ia
    SET
        action = NULL,
        consolidation_rule = NULL,
        target_index_name = NULL
    FROM #index_analysis AS ia
    WHERE ia.action = N'MAKE UNIQUE'
    AND NOT EXISTS
    (
        /* Check if there's a unique constraint with matching keys that points to this index */
        SELECT
            1/0
        FROM #index_analysis AS ia_uc
        WHERE ia_uc.scope_hash = ia.scope_hash
        AND   ia_uc.key_columns = ia.key_columns
        AND   ia_uc.action = N'DISABLE'
        AND   ia_uc.target_index_name = ia.index_name
    )
    OPTION(RECOMPILE);

    /* Make sure the nonclustered index has the superseded_by field set correctly */
    UPDATE
        ia_nc
    SET
        ia_nc.superseded_by =
            CASE
                WHEN ia_nc.superseded_by IS NULL
                THEN N'Will replace constraint ' +
                     ia_uc.index_name
                ELSE ia_nc.superseded_by +
                     N', will replace constraint ' + ia_uc.index_name
            END
    FROM #index_analysis AS ia_nc
    JOIN #index_analysis AS ia_uc
      ON  ia_uc.scope_hash = ia_nc.scope_hash  /* Same database and object */
      AND ia_uc.action = N'DISABLE'
      AND ia_uc.target_index_name = ia_nc.index_name
    WHERE ia_nc.action = N'MAKE UNIQUE'
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 7.5',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 7.6: Handle Key Duplicates of MAKE UNIQUE indexes */
    /* After a unique constraint is replaced, other indexes with same keys should be disabled */
    /* and their includes should be merged into the MAKE UNIQUE index */
    UPDATE
        ia_dup
    SET
        ia_dup.consolidation_rule = N'Key Duplicate',
        ia_dup.action = N'DISABLE',
        ia_dup.target_index_name = ia_winner.index_name
    FROM #index_analysis AS ia_dup
    JOIN #index_analysis AS ia_winner
      ON  ia_winner.scope_hash = ia_dup.scope_hash
      AND ia_winner.key_filter_hash = ia_dup.key_filter_hash
      AND ia_winner.index_name <> ia_dup.index_name
      AND ia_winner.action = N'MAKE UNIQUE'
      AND ia_winner.consolidation_rule = N'Unique Constraint Replacement'
    WHERE ia_dup.consolidation_rule IS NULL
    AND   ia_dup.action IS NULL
    /* Exclude unique constraints (they're handled separately) */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id_uc
        WHERE id_uc.index_hash = ia_dup.index_hash
        AND   id_uc.is_unique_constraint = 1
    )
    OPTION(RECOMPILE);

    /* Merge includes from the disabled Key Duplicates into the MAKE UNIQUE index */
    UPDATE
        ia_winner
    SET
        ia_winner.included_columns =
        (
            SELECT
                combined_cols =
                    STUFF
                    (
                        (
                            SELECT DISTINCT
                                N', ' + t.c.value('.', 'sysname')
                            FROM
                            (
                                /* Winner's includes */
                                SELECT
                                    x = CONVERT
                                    (
                                        xml,
                                        N'<c>' +
                                        REPLACE(ISNULL(ia_winner.included_columns, N''), N', ', N'</c><c>') +
                                        N'</c>'
                                    )
                                WHERE ia_winner.included_columns IS NOT NULL

                                UNION ALL

                                /* Loser's includes */
                                SELECT
                                    x = CONVERT
                                    (
                                        xml,
                                        N'<c>' +
                                        REPLACE(ia_loser.included_columns, N', ', N'</c><c>') +
                                        N'</c>'
                                    )
                                FROM #index_analysis AS ia_loser
                                WHERE ia_loser.scope_hash = ia_winner.scope_hash
                                AND   ia_loser.key_filter_hash = ia_winner.key_filter_hash
                                AND   ia_loser.action = N'DISABLE'
                                AND   ia_loser.consolidation_rule = N'Key Duplicate'
                                AND   ia_loser.target_index_name = ia_winner.index_name
                                AND   ia_loser.included_columns IS NOT NULL
                            ) AS a
                            CROSS APPLY a.x.nodes('/c') AS t(c)
                            WHERE LEN(t.c.value('.', 'sysname')) > 0
                            FOR
                                XML
                                PATH('')
                        ),
                        1,
                        2,
                        ''
                    )
        ),
        ia_winner.superseded_by =
            CASE
                WHEN ia_winner.superseded_by IS NULL
                THEN N'Supersedes ' +
                     STUFF
                     (
                         (
                             SELECT
                                 N', ' + ia_loser.index_name
                             FROM #index_analysis AS ia_loser
                             WHERE ia_loser.scope_hash = ia_winner.scope_hash
                             AND   ia_loser.key_filter_hash = ia_winner.key_filter_hash
                             AND   ia_loser.action = N'DISABLE'
                             AND   ia_loser.consolidation_rule = N'Key Duplicate'
                             AND   ia_loser.target_index_name = ia_winner.index_name
                             FOR
                                 XML
                                 PATH('')
                         ),
                         1,
                         2,
                         ''
                     )
                ELSE ia_winner.superseded_by +
                     N', ' +
                     STUFF
                     (
                         (
                             SELECT
                                 N', ' + ia_loser.index_name
                             FROM #index_analysis AS ia_loser
                             WHERE ia_loser.scope_hash = ia_winner.scope_hash
                             AND   ia_loser.key_filter_hash = ia_winner.key_filter_hash
                             AND   ia_loser.action = N'DISABLE'
                             AND   ia_loser.consolidation_rule = N'Key Duplicate'
                             AND   ia_loser.target_index_name = ia_winner.index_name
                             FOR
                                 XML
                                 PATH('')
                         ),
                         1,
                         2,
                         ''
                     )
            END
    FROM #index_analysis AS ia_winner
    WHERE ia_winner.action = N'MAKE UNIQUE'
    AND   ia_winner.consolidation_rule = N'Unique Constraint Replacement'
    AND EXISTS
    (
        SELECT
            1/0
        FROM #index_analysis AS ia_loser
        WHERE ia_loser.scope_hash = ia_winner.scope_hash
        AND   ia_loser.key_filter_hash = ia_winner.key_filter_hash
        AND   ia_loser.action = N'DISABLE'
        AND   ia_loser.consolidation_rule = N'Key Duplicate'
        AND   ia_loser.target_index_name = ia_winner.index_name
    )
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 7.6',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Rule 8: Identify indexes with same keys but in different order after first column */
    /* This rule flags indexes that have the same set of key columns but ordered differently */
    /* These need manual review as they may be redundant depending on query patterns */
    UPDATE
        ia1
    SET
        ia1.consolidation_rule = N'Same Keys Different Order',
        ia1.action = N'REVIEW',  /* These need manual review */
        ia1.target_index_name = ia2.index_name  /* Reference the partner index */
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.index_name < ia2.index_name  /* Only process each pair once */
      AND ia1.consolidation_rule IS NULL  /* Not already processed */
      AND ia2.consolidation_rule IS NULL  /* Not already processed */
    WHERE
        /* Leading columns match */
        EXISTS
        (
            SELECT
                1/0
            FROM #index_details AS id1
            JOIN #index_details AS id2
              ON id2.column_position_hash = id1.column_position_hash
            WHERE id1.index_hash = ia1.index_hash
            AND   id2.index_hash = ia2.index_hash
            AND   id1.key_ordinal = 1
        )
        /* Same set of key columns but in different order */
        AND NOT EXISTS
        (
            /* Make sure the sets of key columns are exactly the same */
            SELECT
                id1.column_name
            FROM #index_details AS id1
            WHERE id1.index_hash = ia1.index_hash
            AND   id1.is_included_column = 0
            AND   id1.key_ordinal > 0

            EXCEPT

            SELECT
                id2.column_name
            FROM #index_details AS id2
            WHERE id2.index_hash = ia2.index_hash
            AND   id2.is_included_column = 0
            AND   id2.key_ordinal > 0
        )
        /* But the order is different (excluding the first column) */
        AND EXISTS
        (
            /* There's at least one column in a different position */
            SELECT
                1/0
            FROM #index_details AS id1
            JOIN #index_details AS id2
              ON  id2.scope_hash = id1.scope_hash
              AND id2.column_name = id1.column_name
              AND id2.key_ordinal <> id1.key_ordinal
              AND id1.key_ordinal > 1  /* After the first column */
              AND id2.key_ordinal > 1  /* After the first column */
            WHERE id1.index_hash = ia1.index_hash
            AND   id2.index_hash = ia2.index_hash
        )
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after rule 8',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);
    END;

    /* Create a reference to the detailed summary that will appear at the end */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        consolidation_rule,
        script_type,
        additional_info,
        target_index_name,
        superseded_info,
        original_index_definition,
        script,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT
        result_type = 'SUMMARY',
        sort_order = -1,
        database_name =
            N'processed databases: ' +
            CASE
                WHEN @get_all_databases = 0
                THEN ISNULL(@database_name, N'None')
                ELSE
                    ISNULL
                    (
                        STUFF
                        (
                            (
                                SELECT
                                    N', ' +
                                    d.database_name
                                FROM #databases AS d
                                ORDER BY
                                    d.database_name
                                FOR
                                    XML
                                    PATH(''),
                                    TYPE
                            ).value('.', 'nvarchar(max)'),
                            1,
                            2,
                            N''
                        ),
                        N'None'
                    )
            END,
        schema_name =
            N'skipped databases: ' +
            ISNULL
            (
                STUFF
                (
                    (
                        SELECT
                            N', ' +
                            rbs.database_name +
                            N' (' +
                            rbs.reason +
                            N')'
                        FROM #requested_but_skipped_databases AS rbs
                        ORDER BY
                            rbs.database_name
                        FOR
                            XML
                            PATH(''),
                            TYPE
                    ).value('.', 'nvarchar(MAX)'),
                    1,
                    2,
                    N''
                ),
                N'None'
            ),
        table_name = N'brought to you by erikdarling.com',
        index_name = N'for support: https://code.erikdarling.com/',
        consolidation_rule = N'run date: ' + CONVERT(nvarchar(30), SYSDATETIME(), 120),
        script_type = N'Index Cleanup Scripts',
        additional_info = N'A detailed index analysis report appears after these scripts',
        target_index_name = N'ALWAYS TEST THESE RECOMMENDATIONS',
        superseded_info = N'IN A NON-PRODUCTION ENVIRONMENT FIRST!',
        original_index_definition = N'please enjoy responsibly!',
        script = N'happy index cleaning!',
        index_size_gb = 0,
        index_rows = 0,
        index_reads = 0,
        index_writes = 0
    OPTION(RECOMPILE);


    /* Identify key duplicates where both indexes have MERGE INCLUDES action */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #key_duplicate_dedupe insert', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #key_duplicate_dedupe
    WITH
        (TABLOCK)
    (
        database_id,
        object_id,
        database_name,
        schema_name,
        table_name,
        base_key_columns,
        filter_definition,
        winning_index_name,
        index_list
    )
    SELECT
        ia.database_id,
        ia.object_id,
        database_name = MAX(ia.database_name),
        schema_name = MAX(ia.schema_name),
        table_name = MAX(ia.table_name),
        base_key_columns = ia.key_columns,
        filter_definition = ISNULL(ia.filter_definition, N''),
        /* Choose the index with most included columns as the winner (or first alphabetically if tied) */
        winning_index_name =
            (
                SELECT TOP (1)
                    candidate.index_name
                FROM #index_analysis AS candidate
                WHERE candidate.scope_hash = ia.scope_hash
                AND   candidate.key_filter_hash = ia.key_filter_hash
                AND   candidate.action = N'MERGE INCLUDES'
                AND   candidate.consolidation_rule = N'Key Duplicate'
                ORDER BY
                    /* Then prefer indexes with more included columns (by length as a proxy) */
                    LEN(ISNULL(candidate.included_columns, '')) DESC,
                    /* Then alphabetically for stability */
                    candidate.index_name
            ),
        /* Build a list of other indexes in this group */
        index_list =
            STUFF
            (
              (
                SELECT
                    N', ' +
                    inner_ia.index_name
                FROM #index_analysis AS inner_ia
                WHERE inner_ia.scope_hash = ia.scope_hash
                  AND inner_ia.key_filter_hash = ia.key_filter_hash
                  AND inner_ia.action = N'MERGE INCLUDES'
                  AND inner_ia.consolidation_rule = N'Key Duplicate'
                GROUP BY
                    inner_ia.index_name,
                    inner_ia.scope_hash,
                    inner_ia.key_filter_hash
                ORDER BY
                    inner_ia.index_name
                FOR
                    XML
                    PATH(''),
                    TYPE
              ).value('.', 'nvarchar(max)'),
              1,
              2,
              ''
            )
    FROM #index_analysis AS ia
    WHERE ia.action = N'MERGE INCLUDES'
      AND ia.consolidation_rule = N'Key Duplicate'
    GROUP BY
        ia.database_id,
        ia.object_id,
        ia.key_columns,
        ia.filter_definition,
        ia.scope_hash,
        ia.key_filter_hash
    HAVING
        COUNT_BIG(*) > 1
    OPTION(RECOMPILE); /* Only groups with multiple MERGE INCLUDES */

    /* Update the index_analysis table to make only one index the winner in each group */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_analysis updates', 0, 0) WITH NOWAIT;
    END;

    UPDATE
        ia
    SET
        ia.action = N'DISABLE',
        ia.target_index_name = kdd.winning_index_name,
        ia.superseded_by = NULL
    FROM #index_analysis AS ia
    JOIN #key_duplicate_dedupe AS kdd
      ON  ia.scope_hash = kdd.scope_hash
      AND ia.key_filter_hash = kdd.key_filter_hash
    WHERE ia.index_name <> kdd.winning_index_name
    AND   ia.action = N'MERGE INCLUDES'
    AND   ia.consolidation_rule = N'Key Duplicate'
    OPTION(RECOMPILE);

    /* Update the winning index's superseded_by to list all other indexes */
    UPDATE
        ia
    SET
        ia.superseded_by = N'Supersedes ' +
        REPLACE
        (
            kdd.index_list,
            ia.index_name + N', ',
            N''
        ) /* Remove self from list if present */
    FROM #index_analysis AS ia
    JOIN #key_duplicate_dedupe AS kdd
      ON  ia.scope_hash = kdd.scope_hash
      AND ia.key_filter_hash = kdd.key_filter_hash
    WHERE ia.index_name = kdd.winning_index_name
    OPTION(RECOMPILE);

    /* Merge all included columns from Key Duplicate indexes into the winning index */
    IF @debug = 1
    BEGIN
        RAISERROR('Merging included columns from Key Duplicate indexes', 0, 0) WITH NOWAIT;
    END;

    WITH
        KeyDuplicateIncludes AS
    (
        SELECT
            winner.database_id,
            winner.object_id,
            winner.index_id,
            winner.index_name,
            winner.index_hash,
            winner.included_columns AS winner_includes,
            loser.included_columns AS loser_includes
        FROM #index_analysis AS winner
        JOIN #key_duplicate_dedupe AS kdd
          ON  winner.scope_hash = kdd.scope_hash
          AND winner.key_filter_hash = kdd.key_filter_hash
          AND winner.index_name = kdd.winning_index_name
        JOIN #index_analysis AS loser
          ON  loser.scope_hash = kdd.scope_hash
          AND loser.key_filter_hash = kdd.key_filter_hash
          AND loser.index_name <> kdd.winning_index_name
          AND loser.action = N'DISABLE'
          AND loser.consolidation_rule = N'Key Duplicate'
        WHERE winner.action = N'MERGE INCLUDES'
        AND   winner.consolidation_rule = N'Key Duplicate'
    )
    UPDATE
        ia
    SET
        ia.included_columns =
        (
            SELECT
                /* Combine all includes from winner and all losers, removing duplicates */
                combined_cols =
                    STUFF
                    (
                        (
                            SELECT DISTINCT
                                N', ' +
                                t.c.value('.', 'sysname')
                            FROM
                            (
                                /* Create XML from winner's includes */
                                SELECT
                                    x = CONVERT
                                    (
                                        xml,
                                        N'<c>' +
                                        REPLACE(ISNULL(kdi.winner_includes, N''), N', ', N'</c><c>') +
                                        N'</c>'
                                    )
                                FROM KeyDuplicateIncludes AS kdi
                                WHERE kdi.index_hash = ia.index_hash
                                AND   kdi.winner_includes IS NOT NULL

                                UNION ALL

                                /* Create XML from each loser's includes */
                                SELECT
                                    x = CONVERT
                                    (
                                        xml,
                                        N'<c>' +
                                        REPLACE(kdi.loser_includes, N', ', N'</c><c>') +
                                        N'</c>'
                                    )
                                FROM KeyDuplicateIncludes AS kdi
                                WHERE kdi.index_hash = ia.index_hash
                                AND   kdi.loser_includes IS NOT NULL
                            ) AS a
                            /* Split XML into individual columns */
                            CROSS APPLY a.x.nodes('/c') AS t(c)
                            /* Filter out empty strings that can result from NULL handling */
                            WHERE LEN(t.c.value('.', 'sysname')) > 0
                            FOR
                                XML
                                PATH('')
                        ),
                        1,
                        2,
                        ''
                    )
        )
    FROM #index_analysis AS ia
    WHERE ia.action = N'MERGE INCLUDES'
    AND   ia.consolidation_rule = N'Key Duplicate'
    AND   EXISTS
    (
        SELECT
            1/0
        FROM #key_duplicate_dedupe AS kdd
        WHERE kdd.scope_hash = ia.scope_hash
        AND   kdd.winning_index_name = ia.index_name
    )
    OPTION(RECOMPILE);

    /* Insert Key Duplicate winners into #merged_includes so they get MERGE scripts */
    INSERT INTO
        #merged_includes
    WITH
        (TABLOCK)
    (
        scope_hash,
        index_name,
        key_columns,
        merged_includes
    )
    SELECT
        ia.scope_hash,
        ia.index_name,
        ia.key_columns,
        ia.included_columns
    FROM #index_analysis AS ia
    WHERE ia.action = N'MERGE INCLUDES'
    AND   ia.consolidation_rule = N'Key Duplicate'
    AND   EXISTS
    (
        SELECT
            1/0
        FROM #key_duplicate_dedupe AS kdd
        WHERE kdd.scope_hash = ia.scope_hash
        AND   kdd.winning_index_name = ia.index_name
    )
    /* Only insert if there were actually losers with different includes */
    AND   EXISTS
    (
        SELECT
            1/0
        FROM #index_analysis AS loser
        WHERE loser.scope_hash = ia.scope_hash
        AND   loser.key_filter_hash = ia.key_filter_hash
        AND   loser.action = N'DISABLE'
        AND   loser.consolidation_rule = N'Key Duplicate'
        AND   loser.included_columns IS NOT NULL
        AND   ISNULL(loser.included_columns, N'') <> ISNULL(ia.included_columns, N'')
    )
    OPTION(RECOMPILE);

    /* Find indexes with same key columns where one has includes that are a subset of another */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #include_subset_dedupe insert', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #include_subset_dedupe
    WITH
        (TABLOCK)
    (
        database_id,
        object_id,
        subset_index_name,
        superset_index_name,
        subset_included_columns,
        superset_included_columns
    )
    SELECT
        ia1.database_id,
        ia1.object_id,
        ia1.index_name AS subset_index_name,
        ia2.index_name AS superset_index_name,
        ia1.included_columns AS subset_included_columns,
        ia2.included_columns AS superset_included_columns
    FROM #index_analysis AS ia1
    JOIN #index_analysis AS ia2
      ON  ia1.scope_hash = ia2.scope_hash  /* Same database and object */
      AND ia1.key_filter_hash = ia2.key_filter_hash  /* Same keys and filter */
      AND ia1.index_name <> ia2.index_name
      AND ia1.action = N'MERGE INCLUDES'
      AND ia2.action = N'MERGE INCLUDES'
      AND ia1.consolidation_rule = N'Key Duplicate'
      AND ia2.consolidation_rule = N'Key Duplicate'
      /* Find where subset's includes are contained within superset's includes */
      AND
      (
           ia1.included_columns IS NULL
        OR CHARINDEX(ia1.included_columns, ia2.included_columns) > 0
      )
      /* Don't match if lengths are the same (would be exact duplicates) */
      AND
      (
           ia1.included_columns IS NULL
        OR ia2.included_columns IS NULL
        OR LEN(ia1.included_columns) < LEN(ia2.included_columns)
      )
    OPTION(RECOMPILE);

    /* Update the subset indexes to be disabled, since supersets already contain their columns */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_analysis updates', 0, 0) WITH NOWAIT;
    END;

    UPDATE
        ia
    SET
        ia.action = N'DISABLE',
        ia.target_index_name = isd.superset_index_name,
        ia.superseded_by = NULL
    FROM #index_analysis AS ia
    JOIN #include_subset_dedupe AS isd
      ON  ia.scope_hash = isd.scope_hash
      AND ia.index_name = isd.subset_index_name
    OPTION(RECOMPILE);

    /* Update the superset indexes to indicate they supersede the subset indexes */
    UPDATE
        ia
    SET
        ia.superseded_by =
            CASE
                WHEN ia.superseded_by IS NULL
                THEN N'Supersedes ' +
                     isd.subset_index_name
                ELSE ia.superseded_by +
                     N', ' +
                     isd.subset_index_name
            END
    FROM #index_analysis AS ia
    JOIN #include_subset_dedupe AS isd
      ON  ia.scope_hash = isd.scope_hash
      AND ia.index_name = isd.superset_index_name
    OPTION(RECOMPILE);

    /* Update winning indexes that don't actually need changes to have action = N'KEEP' */
    UPDATE
        ia
    SET
        /* Change action to 'KEEP' for indexes that don't need to be modified */
        ia.action = N'KEEP'
    FROM #index_analysis AS ia
    WHERE ia.action = N'MERGE INCLUDES'
    AND   ia.superseded_by IS NOT NULL
    /* Only change to KEEP if Rule 6 didn't compute merged includes for this index */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #merged_includes AS mi
        WHERE mi.scope_hash = ia.scope_hash
        AND   mi.index_name = ia.index_name
    )
    OPTION(RECOMPILE);

    /* Insert merge scripts for indexes */
    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after all updates',
            ia.*
        FROM #index_analysis AS ia
        OPTION(RECOMPILE);

        RAISERROR('Generating #index_cleanup_results insert, MERGE', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        consolidation_rule,
        target_index_name,
        script,
        additional_info,
        superseded_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'MERGE',
        /* Put merge target indexes higher in sort order (5) so they appear before
           indexes that will be disabled (20) */
        sort_order = 5,
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type = N'MERGE SCRIPT',
        ia.consolidation_rule,
        ia.target_index_name,
        script =
            CASE
                WHEN ia.action = N'MAKE UNIQUE'
                THEN N'CREATE UNIQUE '
                WHEN ia.action = N'MERGE INCLUDES'
                THEN N'CREATE '
                ELSE N'CREATE '
            END +
            N'INDEX ' +
            QUOTENAME(ia.index_name) +
            N' ON ' +
            QUOTENAME(ia.database_name) +
            N'.' +
            QUOTENAME(ia.schema_name) +
            N'.' +
            QUOTENAME(ia.table_name) +
            N' (' +
            ia.key_columns +
            N')' +
            CASE
                WHEN ia.included_columns IS NOT NULL
                AND  LEN(ia.included_columns) > 0
                AND  ia.action = N'MERGE INCLUDES'
                THEN N' INCLUDE (' +
                     ia.included_columns +
                     N')'
                WHEN ia.included_columns IS NOT NULL
                AND  LEN(ia.included_columns) > 0
                THEN N' INCLUDE (' +
                     ia.included_columns +
                     N')'
                ELSE N''
            END +
            CASE
                WHEN ia.filter_definition IS NOT NULL
                THEN N' WHERE ' +
                     ia.filter_definition
                ELSE N''
            END +
            N' WITH (DROP_EXISTING = ON, FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
            CASE
                WHEN @online = 1
                THEN N'ON'
                ELSE N'OFF'
            END +
            CASE
                WHEN ce.can_compress = 1
                THEN ', DATA_COMPRESSION = PAGE'
                ELSE N''
                END +
            N')' +
            CASE
                WHEN ps.partition_function_name IS NOT NULL
                THEN N' ON ' +
                     QUOTENAME(ps.built_on) +
                     N'(' +
                     ISNULL(ps.partition_columns, N'') +
                     N')'
                WHEN ps.built_on IS NOT NULL
                THEN N' ON ' +
                     QUOTENAME(ps.built_on)
                ELSE N''
            END + N';',
            /* Additional info about what this script does */
        additional_info =
            CASE
                WHEN ia.action = N'MERGE INCLUDES'
                THEN N'This index will absorb includes from duplicate indexes'
                WHEN ia.action = N'MAKE UNIQUE'
                THEN N'This index will replace a unique constraint'
                ELSE NULL
            END,
        /* Add superseded_by information if available */
        ia.superseded_by,
        /* Original index definition for validation */
        ia.original_index_definition,
        NULL,
        NULL,
        NULL,
        NULL
    FROM #index_analysis AS ia
    LEFT JOIN
    (
        /* Get the partition info for each index */
        SELECT
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.index_hash,
            ps.built_on,
            ps.partition_function_name,
            ps.partition_columns
        FROM #partition_stats ps
        GROUP BY
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.index_hash,
            ps.built_on,
            ps.partition_function_name,
            ps.partition_columns
    ) AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #compression_eligibility AS ce
      ON ia.index_hash = ce.index_hash
    WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
    /* Only create merge scripts for the indexes that should remain after merging */
    AND   ia.target_index_name IS NULL
    OPTION(RECOMPILE);

    /* Debug which indexes are getting MERGE scripts */
    IF @debug = 1
    BEGIN
        RAISERROR('Indexes getting MERGE scripts:', 0, 0) WITH NOWAIT;
        SELECT
            ia.index_name,
            ia.action,
            ia.consolidation_rule,
            ia.target_index_name,
            script_type = 'WILL GET MERGE SCRIPT',
            ia.included_columns
        FROM #index_analysis AS ia
        WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
        AND   ia.target_index_name IS NULL
        ORDER BY
            ia.index_name
        OPTION(RECOMPILE);
    END;

    /* Insert disable scripts for unneeded indexes */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, DISABLE', 0, 0) WITH NOWAIT;

        /* Debug for indexes that should get DISABLE scripts */
        RAISERROR('Indexes that should get DISABLE scripts:', 0, 0) WITH NOWAIT;
        SELECT
            ia.index_name,
            ia.consolidation_rule,
            ia.action,
            ia.target_index_name,
            ia.is_unique,
            ia.index_priority,
            is_unique_constraint =
                CASE
                    WHEN EXISTS
                    (
                        SELECT
                            1/0
                        FROM #index_details AS id
                        WHERE id.index_hash = ia.index_hash
                        AND   id.is_unique_constraint = 1
                    )
                    THEN 'YES'
                    ELSE 'NO'
                END,
            make_unique_target =
                CASE
                    WHEN EXISTS
                    (
                        SELECT
                            1/0
                        FROM #index_analysis AS ia_make
                        WHERE ia_make.scope_hash = ia.scope_hash
                        AND   ia_make.action = N'MAKE UNIQUE'
                        AND   ia_make.target_index_name = ia.index_name
                    )
                    THEN 'YES'
                    ELSE 'NO'
                END,
            will_get_script =
                CASE
                    WHEN ia.action = N'DISABLE'
                    AND NOT EXISTS
                    (
                        SELECT 1
                        FROM #index_details AS id_uc
                        WHERE id_uc.index_hash = ia.index_hash
                        AND id_uc.is_unique_constraint = 1
                    )
                    THEN 'YES'
                    ELSE 'NO'
                END
        FROM #index_analysis AS ia
        ORDER BY
            ia.index_name
        OPTION(RECOMPILE);

        /* Debug for all indexes marked with action = DISABLE */
        RAISERROR('All indexes with action = DISABLE:', 0, 0) WITH NOWAIT;
        SELECT
            ia.index_name,
            ia.consolidation_rule,
            ia.action,
            ia.target_index_name
        FROM #index_analysis AS ia
        WHERE ia.action = N'DISABLE'
        ORDER BY
            ia.index_name
        OPTION(RECOMPILE);
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        consolidation_rule,
        script,
        additional_info,
        target_index_name,
        superseded_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'DISABLE',
        /* Sort duplicate/subset indexes first (20), then unused indexes last (25) */
        sort_order =
            CASE
                WHEN ia.consolidation_rule LIKE 'Unused Index%'
                THEN 25
                ELSE 20
            END,
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type = 'DISABLE SCRIPT',
        ia.consolidation_rule,
        script =
            /* Use regular DISABLE syntax for indexes */
            N'ALTER INDEX ' +
            QUOTENAME(ia.index_name) +
            N' ON ' +
            QUOTENAME(ia.database_name) +
            N'.' +
            QUOTENAME(ia.schema_name) +
            N'.' +
            QUOTENAME(ia.table_name) +
            N' DISABLE;',
            CASE
                WHEN ia.consolidation_rule = N'Key Subset'
                THEN N'This index is superseded by a wider index: ' +
                     ISNULL(ia.target_index_name, N'(unknown)')
                WHEN ia.consolidation_rule = N'Exact Duplicate'
                THEN N'This index is an exact duplicate of: ' +
                     ISNULL(ia.target_index_name, N'(unknown)')
                WHEN ia.consolidation_rule = N'Key Duplicate'
                THEN N'This index has the same keys as: ' +
                     ISNULL(ia.target_index_name, N'(unknown)')
                WHEN ia.consolidation_rule LIKE 'Unused Index%'
                THEN ia.consolidation_rule
                WHEN ia.action = N'DISABLE'
                THEN N'This index is redundant and will be disabled'
                ELSE N'This index is redundant'
            END,
        ia.target_index_name,  /* Include the target index name */
        superseded_info = NULL,  /* Don't need superseded_by info for disabled indexes */
        /* Original index definition for validation */
        ia.original_index_definition,
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates
    FROM #index_analysis AS ia
    LEFT JOIN #partition_stats AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ia.index_hash
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    WHERE ia.action = N'DISABLE'
    /* Exclude unique constraints - they are handled by DISABLE CONSTRAINT scripts */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_details AS id_uc
        WHERE id_uc.index_hash = ia.index_hash
        AND   id_uc.is_unique_constraint = 1
    )
    /* Also exclude any index that is also going to be made unique in rule 7.5 */
    AND NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_analysis AS ia_unique
        WHERE ia_unique.scope_hash = ia.scope_hash
        AND   ia_unique.index_name = ia.index_name
        AND   ia_unique.action = N'MAKE UNIQUE'
    )
    OPTION(RECOMPILE);

    /* Add clustered indexes to #index_analysis specifically for compression purposes */
    IF @debug = 1
    BEGIN
        RAISERROR('Adding clustered indexes to #index_analysis for compression', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_analysis
    WITH
        (TABLOCK)
    (
        database_id,
        database_name,
        schema_id,
        schema_name,
        table_name,
        object_id,
        index_id,
        index_name,
        is_unique,
        key_columns,
        included_columns,
        filter_definition,
        original_index_definition
    )
    SELECT
        fo.database_id,
        fo.database_name,
        fo.schema_id,
        fo.schema_name,
        fo.table_name,
        fo.object_id,
        fo.index_id,
        fo.index_name,
        is_unique =
            CASE
                WHEN ce.can_compress = 1
                THEN id.is_unique
                ELSE NULL
            END,
        key_columns =
            STUFF
            (
              (
                SELECT
                    N', ' +
                    QUOTENAME(id2.column_name) +
                    CASE
                        WHEN id2.is_descending_key = 1
                        THEN N' DESC'
                        ELSE N''
                    END
                FROM #index_details id2
                WHERE id2.object_id = fo.object_id
                AND   id2.index_id = fo.index_id
                AND   id2.is_included_column = 0
                GROUP BY
                    id2.column_name,
                    id2.is_descending_key,
                    id2.key_ordinal
                ORDER BY
                    id2.key_ordinal
                FOR
                    XML
                    PATH(''),
                    TYPE
              ).value('text()[1]','nvarchar(max)'),
              1,
              2,
              ''
            ),
        included_columns = NULL, /* Clustered indexes cannot have included columns */
        filter_definition = NULL, /* Clustered indexes cannot have filters */
        original_index_definition =
            CASE
                WHEN id.is_primary_key = 1
                THEN
                    N'ALTER TABLE ' +
                    QUOTENAME(fo.database_name) +
                    N'.' +
                    QUOTENAME(fo.schema_name) +
                    N'.' +
                    QUOTENAME(fo.table_name) +
                    N' ADD CONSTRAINT ' +
                    QUOTENAME(fo.index_name) +
                    N' PRIMARY KEY ' +
                    CASE
                        WHEN ce.index_id = 1
                        THEN N'CLUSTERED'
                        ELSE N'NONCLUSTERED'
                    END
                    +
                    N' (' +
                    STUFF
                    (
                      (
                        SELECT
                            N', ' +
                            QUOTENAME(id2.column_name) +
                            CASE
                                WHEN id2.is_descending_key = 1
                                THEN N' DESC'
                                ELSE N''
                            END
                        FROM #index_details id2
                        WHERE id2.object_id = fo.object_id
                        AND   id2.index_id = fo.index_id
                        AND   id2.is_included_column = 0
                        GROUP BY
                            id2.column_name,
                            id2.is_descending_key,
                            id2.key_ordinal
                        ORDER BY
                            id2.key_ordinal
                        FOR
                            XML
                            PATH(''),
                            TYPE
                      ).value('text()[1]','nvarchar(max)'),
                      1,
                      2,
                      ''
                    ) +
                    N');'
                WHEN id.is_primary_key = 0
                THEN N'CREATE ' +
                    CASE
                        WHEN id.is_unique = 1
                        THEN N'UNIQUE '
                        ELSE N''
                    END +
                N'CLUSTERED INDEX ' +
                QUOTENAME(fo.index_name) +
                N' ON ' +
                QUOTENAME(fo.database_name) +
                N'.' +
                QUOTENAME(fo.schema_name) +
                N'.' +
                QUOTENAME(fo.table_name) +
                N' (' +
                STUFF
                (
                  (
                    SELECT
                        N', ' +
                        QUOTENAME(id2.column_name) +
                        CASE
                            WHEN id2.is_descending_key = 1
                            THEN N' DESC'
                            ELSE N''
                        END
                    FROM #index_details id2
                    WHERE id2.object_id = fo.object_id
                    AND   id2.index_id = fo.index_id
                    AND   id2.is_included_column = 0
                    GROUP BY
                        id2.column_name,
                        id2.is_descending_key,
                        id2.key_ordinal
                    ORDER BY
                        id2.key_ordinal
                    FOR
                        XML
                        PATH(''),
                        TYPE
                  ).value('text()[1]','nvarchar(max)'),
                  1,
                  2,
                  ''
                ) +
                N');'
            ELSE N''
        END
    FROM #filtered_objects AS fo
    JOIN #index_details AS id
      ON  id.database_id = fo.database_id
      AND id.object_id = fo.object_id
      AND id.index_id = fo.index_id
      AND id.key_ordinal = 1 /* Only need one row per index */
    JOIN #compression_eligibility AS ce
      ON  ce.database_id = fo.database_id
      AND ce.object_id = fo.object_id
      AND ce.index_id = fo.index_id
    WHERE
    (
         fo.index_id = 1 /* Clustered indexes only */
      OR id.is_primary_key = 1
    )
    AND   ce.can_compress = 1 /* Only those eligible for compression */
    /* Only add if not already in #index_analysis */
    AND   NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_analysis AS ia
        WHERE ia.database_id = fo.database_id
        AND   ia.object_id = fo.object_id
        AND   ia.index_id = fo.index_id
    )
    OPTION(RECOMPILE);

    /* If any clustered indexes were added, mark them as KEEP */
    UPDATE
        #index_analysis
    SET
        #index_analysis.action = N'KEEP'
    WHERE #index_analysis.index_id = 1 /* Clustered indexes */
    AND   #index_analysis.action IS NULL;

    /* Update index priority for clustered indexes to ensure they're not chosen for deduplication */
    UPDATE
        #index_analysis
    SET
        #index_analysis.index_priority = 1000 /* Maximum priority */
    WHERE #index_analysis.index_id = 1 /* Clustered indexes */
    AND   #index_analysis.index_priority IS NULL;

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_analysis after adding clustered indexes',
            *
        FROM #index_analysis AS ia
        WHERE ia.index_id = 1
        OPTION(RECOMPILE);
    END;

    /* Insert compression scripts for remaining indexes */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, COMPRESS', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        script,
        additional_info,
        target_index_name,
        superseded_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'COMPRESS',
        sort_order = 40,
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type = 'COMPRESSION SCRIPT',
        script =
            N'ALTER INDEX ' +
            QUOTENAME(ia.index_name) +
            N' ON ' +
            QUOTENAME(ia.database_name) +
            N'.' +
            QUOTENAME(ia.schema_name) +
            N'.' +
            QUOTENAME(ia.table_name) +
            CASE
                WHEN ps.partition_function_name IS NOT NULL
                THEN N' REBUILD PARTITION = ALL'
                ELSE N' REBUILD'
            END +
            N' WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
            CASE
                WHEN @online = 1
                THEN N'ON'
                ELSE N'OFF'
            END +
            CASE
                WHEN ce.can_compress = 1
                THEN ', DATA_COMPRESSION = PAGE'
                ELSE N''
            END +
            CASE
                WHEN @supports_optimize_for_sequential_key = 1
                AND EXISTS
                (
                    SELECT
                        1/0
                    FROM #index_details AS id_ofsk
                    WHERE id_ofsk.index_hash = ia.index_hash
                    AND   id_ofsk.optimize_for_sequential_key = 1
                )
                THEN N', OPTIMIZE_FOR_SEQUENTIAL_KEY = ON'
                ELSE N''
            END +
            N');',
        additional_info = N'Compression type: All Partitions',
        superseded_info = NULL, /* No target index for compression scripts */
        ia.superseded_by, /* Include superseded_by info for compression scripts */
        /* Original index definition for validation */
        ia.original_index_definition,
        ps_full.total_space_gb,
        ps_full.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates
    FROM #index_analysis AS ia
    LEFT JOIN
    (
        /* Get the partition info for each index */
        SELECT
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.index_hash,
            ps.built_on,
            ps.partition_function_name,
            ps.partition_columns
        FROM #partition_stats ps
        GROUP BY
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.index_hash,
            ps.built_on,
            ps.partition_function_name,
            ps.partition_columns
    ) AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #partition_stats AS ps_full
      ON ia.index_hash = ps_full.index_hash
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ia.index_hash
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    JOIN #compression_eligibility AS ce
      ON ia.index_hash = ce.index_hash
    WHERE
        /* Indexes that are not being disabled or merged */
        (ia.action IS NULL OR ia.action = N'KEEP')
        /* Only indexes eligible for compression */
    AND  ce.can_compress = 1
    OPTION(RECOMPILE);

    /* Insert disable scripts for unique constraints */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, CONSTRAINT', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        additional_info,
        script,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'CONSTRAINT',
        sort_order = 30,
        ia_uc.database_name,
        ia_uc.schema_name,
        ia_uc.table_name,
        ia_uc.index_name,
        script_type = 'DISABLE CONSTRAINT SCRIPT',
        additional_info =
            N'This constraint is being replaced by: ' +
            ISNULL(ia_uc.target_index_name, N'(unknown)'),
        script =
            N'ALTER TABLE ' +
            QUOTENAME(ia_uc.database_name) +
            N'.' +
            QUOTENAME(ia_uc.schema_name) +
            N'.' +
            QUOTENAME(ia_uc.table_name) +
            N' DROP CONSTRAINT ' +
            QUOTENAME(ia_uc.index_name) +
            N';',
        /* Original index definition for validation */
        original_index_definition = ia_uc.original_index_definition,
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id2.user_seeks + id2.user_scans + id2.user_lookups),
        id2.user_updates
    FROM #index_analysis AS ia_uc
    JOIN #index_details AS id
      ON  id.index_hash = ia_uc.index_hash
      AND id.is_unique_constraint = 1
    LEFT JOIN #index_details AS id2
      ON  id2.index_hash = ia_uc.index_hash
      AND id2.is_included_column = 0 /* Get only one row per index */
      AND id2.key_ordinal > 0
    LEFT JOIN #partition_stats AS ps
      ON ia_uc.index_hash = ps.index_hash
    WHERE
        /* Only constraints that are marked for disabling */
        ia_uc.action = N'DISABLE'
        /* That have consolidation_rule of 'Unique Constraint Replacement' */
        AND ia_uc.consolidation_rule = N'Unique Constraint Replacement'
    OPTION(RECOMPILE);

    /* Insert per-partition compression scripts */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, COMPRESS_PARTITION', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        script,
        additional_info,
        target_index_name,
        superseded_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'COMPRESS_PARTITION',
        sort_order = 50,
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type = 'COMPRESSION SCRIPT - PARTITION',
        script =
            N'ALTER INDEX ' +
            QUOTENAME(ia.index_name) +
            N' ON ' +
            QUOTENAME(ia.database_name) +
            N'.' +
            QUOTENAME(ia.schema_name) +
            N'.' +
            QUOTENAME(ia.table_name) +
            N' REBUILD PARTITION = ' +
            CONVERT
            (
                nvarchar(20),
                ps.partition_number
            ) +
            N' WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
            CASE
                WHEN @online = 1
                THEN N'ON'
                ELSE N'OFF'
            END +
                        CASE
                WHEN ce.can_compress = 1
                THEN ', DATA_COMPRESSION = PAGE'
                ELSE N''
                END +
                CASE
                    WHEN @supports_optimize_for_sequential_key = 1
                    AND EXISTS
                    (
                        SELECT
                            1/0
                        FROM #index_details AS id_ofsk
                        WHERE id_ofsk.index_hash = ia.index_hash
                        AND   id_ofsk.optimize_for_sequential_key = 1
                    )
                    THEN N', OPTIMIZE_FOR_SEQUENTIAL_KEY = ON'
                    ELSE N''
                END +
            N');',
            N'Compression type: Per Partition | Partition: ' +
            CONVERT
            (
                nvarchar(20),
                ps.partition_number
            ) +
            N' | Rows: ' +
            CONVERT
            (
                nvarchar(20),
                ps.total_rows
            ) +
            N' | Size: ' +
            CONVERT
            (
                nvarchar(20),
                CONVERT
                (
                    decimal(10,4),
                    ps.total_space_gb
                )
            ) +
            N' GB',
        target_index_name = NULL,
        superseded_info = NULL,
        ia.original_index_definition,
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates
    FROM #index_analysis AS ia
    JOIN #partition_stats AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ia.index_hash
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    JOIN #compression_eligibility AS ce
      ON ia.index_hash = ce.index_hash
    WHERE
        /* Only partitioned indexes */
        ps.partition_function_name IS NOT NULL
        /* Indexes that are not being disabled or merged */
    AND  (ia.action IS NULL OR ia.action = N'KEEP')
        /* Only indexes eligible for compression */
    AND   ce.can_compress = 1
    OPTION(RECOMPILE);

    /* Insert compression ineligible info */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, INELIGIBLE', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        additional_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'INELIGIBLE',
        sort_order = 90,
        ce.database_name,
        ce.schema_name,
        ce.table_name,
        ce.index_name,
        script_type = 'COMPRESSION INELIGIBLE',
        ce.reason,
        /* Original index definition for validation */
        original_index_definition =
            (
                SELECT TOP (1)
                    ia.original_index_definition
                FROM #index_analysis AS ia
                WHERE ia.database_id = ce.database_id
                AND   ia.object_id = ce.object_id
                AND   ia.index_id = ce.index_id
            ),
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates
    FROM #compression_eligibility AS ce
    LEFT JOIN #partition_stats AS ps
      ON  ce.database_id = ps.database_id
      AND ce.object_id = ps.object_id
      AND ce.index_id = ps.index_id
    LEFT JOIN #index_details AS id
      ON  id.database_id = ce.database_id
      AND id.object_id = ce.object_id
      AND id.index_id = ce.index_id
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    WHERE ce.can_compress = 0
    OPTION(RECOMPILE);


    /* Insert indexes identified for manual review */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, REVIEW', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        consolidation_rule,
        target_index_name,
        additional_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes
    )
    SELECT DISTINCT
        result_type = 'REVIEW',
        sort_order = 93, /* Just before KEPT indexes */
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type = 'NEEDS REVIEW',
        ia.consolidation_rule,
        ia.target_index_name,
        additional_info =
            CASE
                WHEN ia.consolidation_rule = N'Same Keys Different Order'
                THEN N'This index has the same key columns as ' +
                     ISNULL(ia.target_index_name, N'(unknown)') +
                     N' but in a different order. May be redundant depending on query patterns.'
                ELSE N'This index needs manual review'
            END,
        /* Original index definition for validation */
        ia.original_index_definition,
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates
    FROM #index_analysis AS ia
    LEFT JOIN #partition_stats AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ia.index_hash
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    WHERE ia.action = N'REVIEW'
    OPTION(RECOMPILE);


    /* Insert kept indexes into results - Consolidated all kept indexes logic in one place */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results insert, KEPT INDEXES', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_cleanup_results
    WITH
        (TABLOCK)
    (
        result_type,
        sort_order,
        database_name,
        schema_name,
        table_name,
        index_name,
        script_type,
        consolidation_rule,
        superseded_info,
        additional_info,
        original_index_definition,
        index_size_gb,
        index_rows,
        index_reads,
        index_writes,
        script
    )
    SELECT DISTINCT
        result_type = 'KEPT',
        sort_order = 95, /* Put kept indexes at the end */
        ia.database_name,
        ia.schema_name,
        ia.table_name,
        ia.index_name,
        script_type =
            CASE
                /* Add compression status to script_type */
                WHEN ce.can_compress = 1
                THEN 'KEPT - NEEDS COMPRESSION'
                ELSE 'KEPT'
            END,
        ia.consolidation_rule,
        ia.superseded_by,
        additional_info =
            CASE
                WHEN ia.superseded_by IS NOT NULL
                THEN N'This index supersedes other indexes and already has all needed columns'
                WHEN ia.action = N'KEEP'
                THEN N'This index is being kept'
                WHEN ia.action IS NULL
                THEN N'No consolidation or optimization opportunities found'
                ELSE NULL
            END,
        /* Original index definition for validation */
        ia.original_index_definition,
        ps.total_space_gb,
        ps.total_rows,
        index_reads =
            (id.user_seeks + id.user_scans + id.user_lookups),
        id.user_updates,
        /* Include compression script directly on KEPT records when needed */
        script =
            CASE
                WHEN ce.can_compress = 1
                THEN N'ALTER INDEX ' +
                    QUOTENAME(ia.index_name) +
                    N' ON ' +
                    QUOTENAME(ia.database_name) +
                    N'.' +
                    QUOTENAME(ia.schema_name) +
                    N'.' +
                    QUOTENAME(ia.table_name) +
                    CASE
                        WHEN ps_part.partition_function_name IS NOT NULL
                        THEN N' REBUILD PARTITION = ALL'
                        ELSE N' REBUILD'
                    END +
                    N' WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON, ONLINE = ' +
                    CASE
                        WHEN @online = 1
                        THEN N'ON'
                        ELSE N'OFF'
                    END +
                    N', DATA_COMPRESSION = PAGE);'
                ELSE NULL
            END
    FROM #index_analysis AS ia
    LEFT JOIN #partition_stats AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN
    (
        /* Get the partition info for each index */
        SELECT
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.partition_function_name
        FROM #partition_stats ps
        GROUP BY
            ps.database_id,
            ps.object_id,
            ps.index_id,
            ps.partition_function_name
    ) AS ps_part
      ON  ia.database_id = ps_part.database_id
      AND ia.object_id = ps_part.object_id
      AND ia.index_id = ps_part.index_id
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ia.index_hash
      AND id.is_included_column = 0 /* Get only one row per index */
      AND id.key_ordinal > 0
    LEFT JOIN #compression_eligibility AS ce
      ON  ia.database_id = ce.database_id
      AND ia.object_id = ce.object_id
      AND ia.index_id = ce.index_id
    /* Check that this index is not already in the results */
    WHERE NOT EXISTS
    (
        SELECT
            1/0
        FROM #index_cleanup_results AS ir
        WHERE ir.database_name = ia.database_name
        AND   ir.schema_name = ia.schema_name
        AND   ir.table_name = ia.table_name
        AND   ir.index_name = ia.index_name
        /* Exclude if already has a non-compression entry OR already has a compression script */
        AND   (ir.script_type NOT LIKE N'COMPRESSION%' OR ir.result_type = N'COMPRESS')
    )
    /* Include only indexes that should be kept */
    AND
    (
        ia.action = N'KEEP'
        OR
        (
          ia.action IS NULL
          AND ia.index_id > 0
        )
    )
    OPTION(RECOMPILE);

    /* Insert overall summary information */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_reporting_stats insert, SUMMARY', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_reporting_stats
    WITH
        (TABLOCK)
    (
        summary_level,
        server_uptime_days,
        uptime_warning,
        tables_analyzed,
        index_count,
        total_size_gb,
        indexes_to_disable,
        indexes_to_merge,
        compressable_indexes,
        avg_indexes_per_table,
        space_saved_gb,
        compression_min_savings_gb,
        compression_max_savings_gb,
        total_min_savings_gb,
        total_max_savings_gb,
        total_rows
    )
    SELECT
        summary_level = 'SUMMARY',
        server_uptime_days = @uptime_days,
        uptime_warning = @uptime_warning,
        tables_analyzed =
            COUNT_BIG(DISTINCT CONCAT(ia.database_id, N'.', ia.schema_id, N'.', ia.object_id)),
        index_count = COUNT_BIG(*),
        total_size_gb = SUM(ps.total_space_gb),
        indexes_to_disable =
            SUM
            (
                CASE
                    WHEN ia.action = N'DISABLE'
                    THEN 1
                    ELSE 0
                END
            ),
        indexes_to_merge =
            SUM
            (
                CASE
                    WHEN ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
                    THEN 1
                    ELSE 0
                END
            ),
        compressable_indexes =
            SUM
            (
                CASE
                    WHEN ce.can_compress = 1
                    THEN 1
                    ELSE 0
                END
            ),
        avg_indexes_per_table =
            COUNT_BIG(*) * 1.0 /
            NULLIF
            (
                COUNT_BIG(DISTINCT CONCAT(ia.database_id, N'.', ia.schema_id, N'.', ia.object_id)),
                0
            ),
        /* Space savings from cleanup - only count DISABLE actions */
        space_saved_gb =
            SUM
            (
                CASE
                    WHEN ia.action = N'DISABLE'
                    THEN ps.total_space_gb
                    ELSE 0
                END
            ),
        /* Conservative compression savings estimate (20%) */
        compression_min_savings_gb =
            SUM
            (
                CASE
                    WHEN (ia.action IS NULL OR ia.action = N'KEEP')
                    AND   ce.can_compress = 1
                    THEN ps.total_space_gb * 0.20
                    ELSE 0
                END
            ),
        /* Optimistic compression savings estimate (60%) */
        compression_max_savings_gb =
            SUM
            (
                CASE
                    WHEN (ia.action IS NULL OR ia.action = N'KEEP')
                    AND   ce.can_compress = 1
                    THEN ps.total_space_gb * 0.60
                    ELSE 0
                END
            ),
        /* Total conservative savings - only count DISABLE actions for space savings */
        total_min_savings_gb =
            SUM
            (
                CASE
                    WHEN ia.action = N'DISABLE'
                    THEN ps.total_space_gb
                    WHEN (ia.action IS NULL OR ia.action = N'KEEP')
                    AND   ce.can_compress = 1
                    THEN ps.total_space_gb * 0.20
                    ELSE 0
                END
            ),
        /* Total optimistic savings - only count DISABLE actions for space savings */
        total_max_savings_gb =
            SUM
            (
                CASE
                    WHEN ia.action = N'DISABLE'
                    THEN ps.total_space_gb
                    WHEN (ia.action IS NULL OR ia.action = N'KEEP')
                    AND   ce.can_compress = 1
                    THEN ps.total_space_gb * 0.60
                    ELSE 0
                END
            ),
        /* Get total rows from database unique tables */
        total_rows =
        (
            SELECT
                SUM(t.row_count)
            FROM
            (
                SELECT
                    ps_distinct.object_id,
                    row_count =
                        MAX
                        (
                            CASE
                                WHEN ps_distinct.index_id IN (0, 1)
                                THEN ps_distinct.total_rows
                                ELSE 0
                            END
                        )
                FROM #partition_stats AS ps_distinct
                WHERE ps_distinct.index_id IN (0, 1)
                GROUP BY
                    ps_distinct.object_id
            ) AS t
        )
    FROM #index_analysis AS ia
    LEFT JOIN #partition_stats AS ps
      ON ia.index_hash = ps.index_hash
    LEFT JOIN #compression_eligibility AS ce
      ON  ia.database_id = ce.database_id
      AND ia.object_id = ce.object_id
      AND ia.index_id = ce.index_id
    WHERE ia.index_id > 1
    OPTION(RECOMPILE);

    /* Return enhanced database impact summaries */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating enhanced summary reports', 0, 0) WITH NOWAIT;
    END;

    /* Insert database-level summaries */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_reporting_stats insert, DATABASE', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_reporting_stats
    WITH
        (TABLOCK)
    (
        summary_level,
        database_name,
        index_count,
        total_size_gb,
        total_rows,
        indexes_to_merge,
        compressable_indexes,
        unused_indexes,
        unused_size_gb,
        compression_min_savings_gb,
        compression_max_savings_gb,
        total_min_savings_gb,
        total_max_savings_gb,
        total_reads,
        total_writes,
        user_seeks,
        user_scans,
        user_lookups,
        user_updates,
        range_scan_count,
        singleton_lookup_count,
        row_lock_count,
        row_lock_wait_count,
        row_lock_wait_in_ms,
        page_lock_count,
        page_lock_wait_count,
        page_lock_wait_in_ms,
        page_latch_wait_count,
        page_latch_wait_in_ms,
        page_io_latch_wait_count,
        page_io_latch_wait_in_ms,
        forwarded_fetch_count,
        leaf_insert_count,
        leaf_update_count,
        leaf_delete_count
    )
    SELECT
        summary_level = 'DATABASE',
        ps.database_name,
        index_count =
            COUNT_BIG(DISTINCT CONCAT(ps.object_id, N'.', ps.index_id)),
        total_size_gb = SUM(DISTINCT ps.total_space_gb),
        /* Use a simple aggregation to avoid double-counting */
        /* Get actual row count by grabbing the real row count from clustered index/heap per table */
        total_rows = SUM(DISTINCT d.actual_rows),
        indexes_to_merge =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #index_analysis AS ia
                WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
                AND   ia.database_id = ps.database_id
            ),
        compressable_indexes =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #compression_eligibility AS ce
                WHERE ce.can_compress = 1
                AND   ce.database_id = ps.database_id
            ),
        /* Use count from analysis to keep consistent with SUMMARY level */
        unused_indexes =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #index_analysis AS ia
                WHERE ia.action = N'DISABLE'
                AND   ia.database_id = ps.database_id
            ),
        unused_size_gb =
            (
                SELECT
                    SUM(subps.total_space_gb)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                WHERE subia.action = N'DISABLE'
                AND   subia.database_id = ps.database_id
            ),
        /* Conservative compression savings estimate (20%) */
        compression_min_savings_gb =
            (
                SELECT
                    SUM(subps.total_space_gb * 0.20)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE (subia.action IS NULL OR subia.action = N'KEEP')
                AND   subce.can_compress = 1
                AND   subia.database_id = ps.database_id
            ),
        /* Optimistic compression savings estimate (60%) */
        compression_max_savings_gb =
            (
                SELECT
                    SUM(subps.total_space_gb * 0.60)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE (subia.action IS NULL OR subia.action = N'KEEP')
                AND   subce.can_compress = 1
                AND   subia.database_id = ps.database_id
            ),
        /* Total conservative savings */
        total_min_savings_gb =
            (
                SELECT
                    SUM
                    (
                        CASE
                            WHEN subia.action = N'DISABLE'
                            THEN subps.total_space_gb
                            WHEN (subia.action IS NULL OR subia.action = N'KEEP')
                            AND   subce.can_compress = 1
                            THEN subps.total_space_gb * 0.20
                            ELSE 0
                        END
                    )
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                LEFT JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE subia.database_id = ps.database_id
            ),
        /* Total optimistic savings */
        total_max_savings_gb =
            (
                SELECT
                    SUM
                    (
                        CASE
                            WHEN subia.action = N'DISABLE'
                            THEN subps.total_space_gb
                            WHEN (subia.action IS NULL OR subia.action = N'KEEP')
                            AND   subce.can_compress = 1
                            THEN subps.total_space_gb * 0.60
                            ELSE 0
                        END
                    )
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                LEFT JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE subia.database_id = ps.database_id
            ),
        total_reads = SUM(id.user_seeks + id.user_scans + id.user_lookups),
        total_writes = SUM(id.user_updates),
        user_seeks = SUM(id.user_seeks),
        user_scans = SUM(id.user_scans),
        user_lookups = SUM(id.user_lookups),
        user_updates = SUM(id.user_updates),
        range_scan_count = SUM(os.range_scan_count),
        singleton_lookup_count = SUM(os.singleton_lookup_count),
        row_lock_count = SUM(os.row_lock_count),
        row_lock_wait_count = SUM(os.row_lock_wait_count),
        row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
        page_lock_count = SUM(os.page_lock_count),
        page_lock_wait_count = SUM(os.page_lock_wait_count),
        page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
        page_latch_wait_count = SUM(os.page_latch_wait_count),
        page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
        page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
        page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
        forwarded_fetch_count = SUM(os.forwarded_fetch_count),
        leaf_insert_count = SUM(os.leaf_insert_count),
        leaf_update_count = SUM(os.leaf_update_count),
        leaf_delete_count = SUM(os.leaf_delete_count)
    FROM #partition_stats AS ps
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ps.index_hash
      AND id.is_included_column = 0
      AND id.key_ordinal > 0
    LEFT JOIN #operational_stats AS os
      ON os.index_hash = ps.index_hash
    OUTER APPLY
    (
        /* Get actual row count per table using MAX from clustered index/heap */
        SELECT
            actual_rows =
                MAX
                (
                    CASE
                        WHEN ps2.index_id IN (0, 1)
                        THEN ps2.total_rows
                        ELSE 0
                    END
                )
        FROM #partition_stats AS ps2
        WHERE ps2.database_id = ps.database_id
        AND   ps2.object_id = ps.object_id
        AND   ps2.index_id IN (0, 1)
        GROUP BY
            ps2.object_id
    ) AS d
    GROUP BY
        ps.database_name,
        ps.database_id
    OPTION(RECOMPILE);

    /* Insert table-level summaries */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_reporting_stats insert, TABLE', 0, 0) WITH NOWAIT;
    END;

    INSERT INTO
        #index_reporting_stats
    WITH
        (TABLOCK)
    (
        summary_level,
        database_name,
        schema_name,
        table_name,
        index_count,
        total_size_gb,
        total_rows,
        indexes_to_merge,
        compressable_indexes,
        unused_indexes,
        unused_size_gb,
        compression_min_savings_gb,
        compression_max_savings_gb,
        total_min_savings_gb,
        total_max_savings_gb,
        total_reads,
        total_writes,
        user_seeks,
        user_scans,
        user_lookups,
        user_updates,
        range_scan_count,
        singleton_lookup_count,
        row_lock_count,
        row_lock_wait_count,
        row_lock_wait_in_ms,
        page_lock_count,
        page_lock_wait_count,
        page_lock_wait_in_ms,
        page_latch_wait_count,
        page_latch_wait_in_ms,
        page_io_latch_wait_count,
        page_io_latch_wait_in_ms,
        forwarded_fetch_count,
        leaf_insert_count,
        leaf_update_count,
        leaf_delete_count
    )
    SELECT
        summary_level = 'TABLE',
        ps.database_name,
        ps.schema_name,
        ps.table_name,
        index_count = COUNT_BIG(DISTINCT ps.index_id),
        total_size_gb = SUM(DISTINCT ps.total_space_gb),
        /* Use MAX to get the row count from the clustered index or heap */
        total_rows =
            MAX
            (
                CASE
                    WHEN ps.index_id IN (0, 1)
                    THEN ps.total_rows
                    ELSE 0
                END
            ),
        indexes_to_merge =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #index_analysis AS ia
                WHERE ia.action IN (N'MERGE INCLUDES', N'MAKE UNIQUE')
                AND   ia.database_id = ps.database_id
                AND   ia.schema_id = ps.schema_id
                AND   ia.object_id = ps.object_id
            ),
        compressable_indexes =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #compression_eligibility AS ce
                WHERE ce.can_compress = 1
                AND   ce.database_id = ps.database_id
                AND   ce.schema_id = ps.schema_id
                AND   ce.object_id = ps.object_id
            ),
        /* Use count from analysis to keep consistent with SUMMARY level */
        unused_indexes =
            (
                SELECT
                    COUNT_BIG(*)
                FROM #index_analysis AS ia
                WHERE ia.action = N'DISABLE'
                AND   ia.database_id = ps.database_id
                AND   ia.schema_id = ps.schema_id
                AND   ia.object_id = ps.object_id
            ),
        unused_size_gb =
            (
                SELECT
                    SUM(subps.total_space_gb)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                WHERE subia.action = N'DISABLE'
                AND   subia.database_id = ps.database_id
                AND   subia.schema_id = ps.schema_id
                AND   subia.object_id = ps.object_id
            ),
        /* Conservative compression savings estimate (20%) */
        compression_min_savings_gb =
            (
                SELECT
                    SUM(subps.total_space_gb * 0.20)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE (subia.action IS NULL OR subia.action = N'KEEP')
                AND   subce.can_compress = 1
                AND   subia.database_id = ps.database_id
                AND   subia.schema_id = ps.schema_id
                AND   subia.object_id = ps.object_id
            ),
        /* Optimistic compression savings estimate (60%) */
        compression_max_savings_gb =
            (
                SELECT
                    SUM(subps.total_space_gb * 0.60)
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE (subia.action IS NULL OR subia.action = N'KEEP')
                AND   subce.can_compress = 1
                AND   subia.database_id = ps.database_id
                AND   subia.schema_id = ps.schema_id
                AND   subia.object_id = ps.object_id
            ),
        /* Total conservative savings */
        total_min_savings_gb =
            (
                SELECT
                    SUM
                    (
                        CASE
                            WHEN subia.action = N'DISABLE'
                            THEN subps.total_space_gb
                            WHEN (subia.action IS NULL OR subia.action = N'KEEP')
                            AND   subce.can_compress = 1
                            THEN subps.total_space_gb * 0.20
                            ELSE 0
                        END
                    )
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                LEFT JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE subia.database_id = ps.database_id
                AND   subia.schema_id = ps.schema_id
                AND   subia.object_id = ps.object_id
            ),
        /* Total optimistic savings */
        total_max_savings_gb =
            (
                SELECT
                    SUM
                    (
                        CASE
                            WHEN subia.action = N'DISABLE'
                            THEN subps.total_space_gb
                            WHEN (subia.action IS NULL OR subia.action = N'KEEP')
                            AND   subce.can_compress = 1
                            THEN subps.total_space_gb * 0.60
                            ELSE 0
                        END
                    )
                FROM #partition_stats AS subps
                JOIN #index_analysis AS subia
                  ON subps.index_hash = subia.index_hash
                LEFT JOIN #compression_eligibility AS subce
                  ON subce.index_hash = subia.index_hash
                WHERE subia.database_id = ps.database_id
                AND   subia.schema_id = ps.schema_id
                AND   subia.object_id = ps.object_id
            ),
        total_reads = SUM(id.user_seeks + id.user_scans + id.user_lookups),
        total_writes = SUM(id.user_updates),
        user_seeks = SUM(id.user_seeks),
        user_scans = SUM(id.user_scans),
        user_lookups = SUM(id.user_lookups),
        user_updates = SUM(id.user_updates),
        range_scan_count = SUM(os.range_scan_count),
        singleton_lookup_count = SUM(os.singleton_lookup_count),
        row_lock_count = SUM(os.row_lock_count),
        row_lock_wait_count = SUM(os.row_lock_wait_count),
        row_lock_wait_in_ms = SUM(os.row_lock_wait_in_ms),
        page_lock_count = SUM(os.page_lock_count),
        page_lock_wait_count = SUM(os.page_lock_wait_count),
        page_lock_wait_in_ms = SUM(os.page_lock_wait_in_ms),
        page_latch_wait_count = SUM(os.page_latch_wait_count),
        page_latch_wait_in_ms = SUM(os.page_latch_wait_in_ms),
        page_io_latch_wait_count = SUM(os.page_io_latch_wait_count),
        page_io_latch_wait_in_ms = SUM(os.page_io_latch_wait_in_ms),
        forwarded_fetch_count = SUM(os.forwarded_fetch_count),
        leaf_insert_count = SUM(os.leaf_insert_count),
        leaf_update_count = SUM(os.leaf_update_count),
        leaf_delete_count = SUM(os.leaf_delete_count)
    FROM #partition_stats AS ps
    LEFT JOIN #index_details AS id
      ON  id.index_hash = ps.index_hash
      AND id.is_included_column = 0
      AND id.key_ordinal > 0
    LEFT JOIN #operational_stats AS os
      ON os.index_hash = ps.index_hash
    GROUP BY
        ps.database_name,
        ps.database_id,
        ps.schema_name,
        ps.schema_id,
        ps.table_name,
        ps.object_id
    OPTION(RECOMPILE);

    /* We're not doing index-level summaries - focusing on database and table level reports */

    /*
    Return the consolidated results in a single result set
    Results are ordered by:
    1. Summary information (overall stats, savings estimates)
    2. Merge scripts (includes merges and unique conversions) - sort_order 5
    3. Disable scripts (for redundant indexes) - sort_order 20
    4. Constraint scripts (for unique constraints to disable)
    5. Compression scripts (for tables eligible for compression)
    6. Partition-specific compression scripts
    7. Ineligible objects (tables that can't be compressed)
    8. Kept indexes - sort_order 95

    Note: Merge target scripts are sorted higher in the results (sort_order 5)
    so that new merged indexes are created before subset indexes are disabled.

    Within each category, indexes are sorted by size and impact for better prioritization.
    */
    IF @debug = 1
    BEGIN
        SELECT
            table_name = '#index_reporting_stats',
            irs.*
        FROM #index_reporting_stats AS irs
        OPTION(RECOMPILE);

        SELECT
            table_name = '#index_cleanup_results',
            icr.*
        FROM #index_cleanup_results AS icr
        OPTION(RECOMPILE);
    END;

        /* Get the next database */
        FETCH NEXT
        FROM @database_cursor
        INTO
            @current_database_name,
            @current_database_id;
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_cleanup_results, RESULTS', 0, 0) WITH NOWAIT;
    END;

    SELECT
        /* First, show the information needed to understand the script */
        script_type =
            CASE
                WHEN ir.result_type = 'KEPT'
                AND  ir.script_type IS NULL
                THEN 'KEPT'
                ELSE ir.script_type
            END,
        ir.additional_info,
        /* Then show identifying information for the index */
        ir.database_name,
        ir.schema_name,
        ir.table_name,
        ir.index_name,
        /* Then show relationship information */
        consolidation_rule = ISNULL(ir.consolidation_rule, N'N/A'),
        target_index_name = ISNULL(ir.target_index_name, N'N/A'),
        /* Include superseded_by info for winning indexes */
        superseded_info =
            CASE
                WHEN ia.superseded_by IS NOT NULL
                THEN ia.superseded_by
                ELSE ISNULL(ir.superseded_info, N'N/A')
            END,
        /* Add size and usage metrics */
        index_size_gb =
            CASE
                WHEN ir.result_type = 'SUMMARY'
                THEN '0.0000'
                ELSE FORMAT(ISNULL(ir.index_size_gb, 0), 'N4')
            END,
        index_rows =
            CASE
                WHEN ir.result_type = 'SUMMARY'
                THEN '0'
                ELSE FORMAT(ISNULL(ir.index_rows, 0), 'N0')
            END,
        index_reads =
            CASE
                WHEN ir.result_type = 'SUMMARY'
                THEN '0'
                ELSE FORMAT(ISNULL(ir.index_reads, 0), 'N0')
            END,
        index_writes =
            CASE
                WHEN ir.result_type = 'SUMMARY'
                THEN '0'
                ELSE FORMAT(ISNULL(ir.index_writes, 0), 'N0')
            END,
        original_index_definition =
            CASE
                WHEN ir.result_type = 'SUMMARY'
                THEN N'please enjoy responsibly!'
                ELSE ia.original_index_definition
            END,
        /* Finally show the actual script */
        ir.script
    FROM
    (
        /* Use a subquery with ROW_NUMBER to ensure we only get one row per index */
        SELECT
            irs.*,
            ROW_NUMBER() OVER
            (
                PARTITION BY
                    irs.database_name,
                    irs.schema_name,
                    irs.table_name,
                    irs.index_name,
                    irs.script_type
                ORDER BY
                    irs.result_type DESC
            ) AS rn
        FROM #index_cleanup_results AS irs
    ) AS ir
    LEFT JOIN #index_analysis AS ia
      ON  ir.database_name = ia.database_name
      AND ir.schema_name = ia.schema_name
      AND ir.table_name = ia.table_name
      AND ir.index_name = ia.index_name
    WHERE ir.rn = 1 /* Take only the first row for each index */
    ORDER BY
        ir.database_name,
        ir.sort_order,
        /* Within each sort_order group, prioritize by size and usage */
        CASE
            /* For SUMMARY, keep the original order */
            WHEN ir.result_type = 'SUMMARY'
            THEN 0
            /* For script categories, order by size and impact */
            ELSE ISNULL(ir.index_size_gb, 0)
        END DESC,
        CASE
            /* For SUMMARY, keep the original order */
            WHEN ir.result_type = 'SUMMARY'
            THEN 0
            /* For script categories, consider rows as secondary sort */
            ELSE ISNULL(ir.index_rows, 0)
        END DESC,
        /* Then by database, schema, table, index name for consistent ordering */
        ir.schema_name,
        ir.table_name,
        ir.index_name
    OPTION(RECOMPILE);

    /*
    This section now REPLACES the existing summary view rather than supplementing it
    We'll modify the existing query below rather than creating new output panes
    */

    /* Return streamlined reporting statistics focused on key metrics */
    IF @debug = 1
    BEGIN
        RAISERROR('Generating #index_reporting_stats, REPORT', 0, 0) WITH NOWAIT;
    END;

    /*
    Pre-calculate server uptime for use in daily calculations
    */
    DECLARE
        @server_uptime_days decimal(38,2) =
        (
            SELECT TOP (1)
                irs.server_uptime_days
            FROM #index_reporting_stats AS irs
            WHERE irs.summary_level = 'SUMMARY'
        );

    /*
    UNION ALL approach: Three separate queries for each summary level
    This eliminates complex CASE logic and makes each level's reporting clear
    */
    SELECT
        x.level,
        x.database_info,
        x.schema_name,
        x.table_name,
        x.tables_analyzed,
        x.total_indexes,
        x.removable_indexes,
        x.mergeable_indexes,
        x.compressable_indexes,
        x.percent_removable,
        x.current_size_gb,
        x.size_after_cleanup_gb,
        x.space_saved_gb,
        x.space_reduction_percent,
        x.compression_savings_potential,
        x.compression_savings_potential_total,
        x.computed_columns_with_udfs,
        x.check_constraints_with_udfs,
        x.filtered_indexes_needing_includes,
        x.total_rows,
        x.reads_breakdown,
        x.writes,
        x.daily_write_ops_saved,
        x.lock_wait_count,
        x.daily_lock_waits_saved,
        x.avg_lock_wait_ms,
        x.latch_wait_count,
        x.daily_latch_waits_saved,
        x.avg_latch_wait_ms
    FROM
    (
        /* ===== SUMMARY LEVEL ===== */
        SELECT
            level = 'ANALYZED OBJECT DETAILS',
            database_info =
                CASE
                    WHEN irs.uptime_warning = 1
                    THEN 'WARNING: Server uptime only ' +
                         CONVERT(varchar(10), irs.server_uptime_days) +
                         ' days - usage data may be incomplete!'
                    ELSE 'Server uptime: ' +
                         CONVERT(varchar(10), irs.server_uptime_days) +
                         ' days'
                END,
            schema_name = 'ALWAYS TEST THESE RECOMMENDATIONS',
            table_name = 'IN A NON-PRODUCTION ENVIRONMENT FIRST!',
            tables_analyzed = FORMAT(irs.tables_analyzed, 'N0'),
            total_indexes = FORMAT(ISNULL(irs.index_count, 0), 'N0'),
            removable_indexes = FORMAT(ISNULL(irs.indexes_to_disable, 0), 'N0'),
            mergeable_indexes = FORMAT(ISNULL(irs.indexes_to_merge, 0), 'N0'),
            compressable_indexes = FORMAT(ISNULL(irs.compressable_indexes, 0), 'N0'),
            percent_removable =
                CASE
                    WHEN irs.index_count > 0
                    THEN FORMAT(100.0 * ISNULL(irs.indexes_to_disable, 0) / NULLIF(irs.index_count, 0), 'N1') + '%'
                    ELSE '0.0%'
                END,
            current_size_gb = FORMAT(ISNULL(irs.total_size_gb, 0), 'N2'),
            size_after_cleanup_gb = FORMAT(ISNULL(irs.total_size_gb, 0) - ISNULL(irs.space_saved_gb, 0), 'N2'),
            space_saved_gb = FORMAT(ISNULL(irs.space_saved_gb, 0), 'N2'),
            space_reduction_percent =
                CASE
                    WHEN ISNULL(irs.total_size_gb, 0) > 0
                    THEN FORMAT((ISNULL(irs.space_saved_gb, 0) / NULLIF(irs.total_size_gb, 0)) * 100, 'N1') + '%'
                    ELSE '0.0%'
                END,
            compression_savings_potential =
                N'minimum: ' +
                FORMAT(ISNULL(irs.compression_min_savings_gb, 0), 'N2') +
                N' GB maximum ' +
                FORMAT(ISNULL(irs.compression_max_savings_gb, 0), 'N2') +
                N'GB',
            compression_savings_potential_total =
                N'total minimum: ' +
                FORMAT(ISNULL(irs.total_min_savings_gb, 0), 'N2') +
                N' GB total maximum: ' +
                FORMAT(ISNULL(irs.total_max_savings_gb, 0), 'N2') +
                N'GB',
            computed_columns_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #computed_columns_analysis AS cca
                        WHERE cca.contains_udf = 1
                    )
                ),
            check_constraints_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #check_constraints_analysis AS cca
                        WHERE cca.contains_udf = 1
                    )
                ),
            filtered_indexes_needing_includes =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #filtered_index_columns_analysis AS fica
                        WHERE fica.should_include_filter_columns = 1
                    )
                ),
            total_rows = FORMAT(ISNULL(irs.total_rows, 0), 'N0'),
            reads_breakdown = 'N/A',
            writes = 'N/A',
            daily_write_ops_saved = 'N/A',
            lock_wait_count = 'N/A',
            daily_lock_waits_saved = 'N/A',
            avg_lock_wait_ms = 'N/A',
            latch_wait_count = 'N/A',
            daily_latch_waits_saved = 'N/A',
            avg_latch_wait_ms = 'N/A',
            /* Hidden sort columns */
            sort_database = irs.database_name,
            sort_level = 0,
            sort_unused_size = 0.0,
            sort_total_size = 0.0
        FROM #index_reporting_stats AS irs
        WHERE irs.summary_level = 'SUMMARY'

        UNION ALL

        /* ===== DATABASE LEVEL ===== */
        SELECT
            level = 'DATABASE',
            database_info = irs.database_name,
            schema_name = N'N/A',
            table_name = N'N/A',
            tables_analyzed =
                FORMAT
                (
                    (
                        SELECT
                            COUNT_BIG(DISTINCT CONCAT(ia.schema_id, N'.', ia.object_id))
                        FROM #index_analysis AS ia
                        WHERE ia.database_name = irs.database_name
                    ),
                    'N0'
                ),
            total_indexes = FORMAT(ISNULL(irs.index_count, 0), 'N0'),
            removable_indexes = FORMAT(ISNULL(irs.unused_indexes, 0), 'N0'),
            mergeable_indexes = FORMAT(ISNULL(irs.indexes_to_merge, 0), 'N0'),
            compressable_indexes = FORMAT(ISNULL(irs.compressable_indexes, 0), 'N0'),
            percent_removable =
                CASE
                    WHEN irs.index_count > 0
                    THEN FORMAT(100.0 * ISNULL(irs.unused_indexes, 0) / NULLIF(irs.index_count, 0), 'N1') + '%'
                    ELSE '0.0%'
                END,
            current_size_gb = FORMAT(ISNULL(irs.total_size_gb, 0), 'N2'),
            size_after_cleanup_gb = FORMAT(ISNULL(irs.total_size_gb, 0) - ISNULL(irs.unused_size_gb, 0), 'N2'),
            space_saved_gb = FORMAT(ISNULL(irs.unused_size_gb, 0), 'N2'),
            space_reduction_percent =
                CASE
                    WHEN ISNULL(irs.total_size_gb, 0) > 0
                    THEN FORMAT((ISNULL(irs.unused_size_gb, 0) / NULLIF(irs.total_size_gb, 0)) * 100, 'N1') + '%'
                    ELSE '0.0%'
                END,
            compression_savings_potential =
                N'minimum: ' +
                FORMAT(ISNULL(irs.compression_min_savings_gb, 0), 'N2') +
                N' GB maximum ' +
                FORMAT(ISNULL(irs.compression_max_savings_gb, 0), 'N2') +
                N'GB',
            compression_savings_potential_total =
                N'total minimum: ' +
                FORMAT(ISNULL(irs.total_min_savings_gb, 0), 'N2') +
                N' GB total maximum: ' +
                FORMAT(ISNULL(irs.total_max_savings_gb, 0), 'N2') +
                N'GB',
            computed_columns_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #computed_columns_analysis AS cca
                        WHERE cca.database_name = irs.database_name
                        AND   cca.contains_udf = 1
                    )
                ),
            check_constraints_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #check_constraints_analysis AS cca
                        WHERE cca.database_name = irs.database_name
                        AND   cca.contains_udf = 1
                    )
                ),
            filtered_indexes_needing_includes =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #filtered_index_columns_analysis AS fica
                        WHERE fica.database_name = irs.database_name
                        AND   fica.should_include_filter_columns = 1
                    )
                ),
            total_rows = FORMAT(ISNULL(irs.total_rows, 0), 'N0'),
            reads_breakdown =
                FORMAT(ISNULL(irs.total_reads, 0), 'N0') +
                ' (' +
                FORMAT(ISNULL(irs.user_seeks, 0), 'N0') +
                ' seeks, ' +
                FORMAT(ISNULL(irs.user_scans, 0), 'N0') +
                ' scans, ' +
                FORMAT(ISNULL(irs.user_lookups, 0), 'N0') +
                ' lookups)',
            writes = FORMAT(ISNULL(irs.total_writes, 0), 'N0'),
            daily_write_ops_saved = 'N/A',
            lock_wait_count =
                FORMAT(ISNULL(irs.row_lock_wait_count, 0) +
                       ISNULL(irs.page_lock_wait_count, 0), 'N0'),
            daily_lock_waits_saved = 'N/A',
            avg_lock_wait_ms =
                CASE
                    WHEN (ISNULL(irs.row_lock_wait_count, 0) + ISNULL(irs.page_lock_wait_count, 0)) > 0
                    THEN FORMAT(1.0 * (ISNULL(irs.row_lock_wait_in_ms, 0) +
                         ISNULL(irs.page_lock_wait_in_ms, 0)) /
                         NULLIF(ISNULL(irs.row_lock_wait_count, 0) +
                         ISNULL(irs.page_lock_wait_count, 0), 0), 'N2')
                    ELSE '0'
                END,
            latch_wait_count =
                FORMAT(ISNULL(irs.page_latch_wait_count, 0) +
                       ISNULL(irs.page_io_latch_wait_count, 0), 'N0'),
            daily_latch_waits_saved = 'N/A',
            avg_latch_wait_ms =
                CASE
                    WHEN (ISNULL(irs.page_latch_wait_count, 0) + ISNULL(irs.page_io_latch_wait_count, 0)) > 0
                    THEN FORMAT(1.0 * (ISNULL(irs.page_latch_wait_in_ms, 0) +
                         ISNULL(irs.page_io_latch_wait_in_ms, 0)) /
                         NULLIF(ISNULL(irs.page_latch_wait_count, 0) +
                         ISNULL(irs.page_io_latch_wait_count, 0), 0), 'N2')
                    ELSE '0'
                END,
            /* Hidden sort columns */
            sort_database = irs.database_name,
            sort_level = 1,
            sort_unused_size = 0.0,
            sort_total_size = 0.0
        FROM #index_reporting_stats AS irs
        WHERE irs.summary_level = 'DATABASE'

        UNION ALL

        /* ===== TABLE LEVEL ===== */
        SELECT
            level = 'TABLE',
            database_info = irs.database_name,
            schema_name = irs.schema_name,
            table_name = irs.table_name,
            tables_analyzed = FORMAT(1, 'N0'),
            total_indexes = FORMAT(ISNULL(irs.index_count, 0), 'N0'),
            removable_indexes = FORMAT(ISNULL(irs.unused_indexes, 0), 'N0'),
            mergeable_indexes = FORMAT(ISNULL(irs.indexes_to_merge, 0), 'N0'),
            compressable_indexes = FORMAT(ISNULL(irs.compressable_indexes, 0), 'N0'),
            percent_removable =
                CASE
                    WHEN irs.index_count > 0
                    THEN FORMAT(100.0 * ISNULL(irs.unused_indexes, 0) / NULLIF(irs.index_count, 0), 'N1') + '%'
                    ELSE '0.0%'
                END,
            current_size_gb = FORMAT(ISNULL(irs.total_size_gb, 0), 'N2'),
            size_after_cleanup_gb = FORMAT(ISNULL(irs.total_size_gb, 0) - ISNULL(irs.unused_size_gb, 0), 'N2'),
            space_saved_gb = FORMAT(ISNULL(irs.unused_size_gb, 0), 'N2'),
            space_reduction_percent =
                CASE
                    WHEN ISNULL(irs.total_size_gb, 0) > 0
                    THEN FORMAT((ISNULL(irs.unused_size_gb, 0) / NULLIF(irs.total_size_gb, 0)) * 100, 'N1') + '%'
                    ELSE '0.0%'
                END,
            compression_savings_potential =
                N'minimum: ' +
                FORMAT(ISNULL(irs.compression_min_savings_gb, 0), 'N2') +
                N' GB maximum ' +
                FORMAT(ISNULL(irs.compression_max_savings_gb, 0), 'N2') +
                N'GB',
            compression_savings_potential_total =
                N'total minimum: ' +
                FORMAT(ISNULL(irs.total_min_savings_gb, 0), 'N2') +
                N' GB total maximum: ' +
                FORMAT(ISNULL(irs.total_max_savings_gb, 0), 'N2') +
                N'GB',
            computed_columns_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #computed_columns_analysis AS cca
                        WHERE cca.database_name = irs.database_name
                        AND   cca.schema_name = irs.schema_name
                        AND   cca.table_name = irs.table_name
                        AND   cca.contains_udf = 1
                    )
                ),
            check_constraints_with_udfs =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #check_constraints_analysis AS cca
                        WHERE cca.database_name = irs.database_name
                        AND   cca.schema_name = irs.schema_name
                        AND   cca.table_name = irs.table_name
                        AND   cca.contains_udf = 1
                    )
                ),
            filtered_indexes_needing_includes =
                CONVERT
                (
                    nvarchar(20),
                    (
                        SELECT
                            COUNT_BIG(*)
                        FROM #filtered_index_columns_analysis AS fica
                        WHERE fica.database_name = irs.database_name
                        AND   fica.schema_name = irs.schema_name
                        AND   fica.table_name = irs.table_name
                        AND   fica.should_include_filter_columns = 1
                    )
                ),
            total_rows = FORMAT(ISNULL(irs.total_rows, 0), 'N0'),
            reads_breakdown =
                FORMAT(ISNULL(irs.total_reads, 0), 'N0') +
                ' (' +
                FORMAT(ISNULL(irs.user_seeks, 0), 'N0') +
                ' seeks, ' +
                FORMAT(ISNULL(irs.user_scans, 0), 'N0') +
                ' scans, ' +
                FORMAT(ISNULL(irs.user_lookups, 0), 'N0') +
                ' lookups)',
            writes = FORMAT(ISNULL(irs.total_writes, 0), 'N0'),
            daily_write_ops_saved =
                CASE
                    WHEN ISNULL(irs.unused_indexes, 0) > 0
                    THEN FORMAT
                         (
                             CONVERT
                             (
                                 decimal(38,2),
                                 ISNULL
                                 (
                                     irs.user_updates /
                                     NULLIF
                                     (
                                         CONVERT
                                         (
                                             decimal(38,2),
                                             @server_uptime_days
                                         ),
                                         0
                                     ) *
                                     (
                                         ISNULL
                                         (
                                             irs.unused_indexes,
                                             0
                                         ) /
                                         NULLIF
                                         (
                                             CONVERT
                                             (
                                                 decimal(38,2),
                                                 irs.index_count
                                             ),
                                             0
                                         )
                                     ),
                                     0
                                 )
                             ),
                             'N0'
                         )
                    ELSE '0'
                END,
            lock_wait_count =
                FORMAT
                (
                    ISNULL(irs.row_lock_wait_count,  0) +
                    ISNULL(irs.page_lock_wait_count, 0),
                    'N0'
                ),
            daily_lock_waits_saved =
                CASE
                    WHEN ISNULL(irs.unused_indexes, 0) > 0
                    THEN FORMAT
                         (
                             CONVERT
                             (
                                 decimal(38,2),
                                 ISNULL
                                 (
                                     (irs.row_lock_wait_count + irs.page_lock_wait_count) /
                                     NULLIF
                                     (
                                         CONVERT
                                         (
                                             decimal(38,2),
                                             @server_uptime_days
                                         ),
                                         0
                                     ) *
                                     (
                                         ISNULL
                                         (
                                             irs.unused_indexes,
                                             0
                                         ) /
                                         NULLIF
                                         (
                                             CONVERT
                                             (
                                                 decimal(38,2),
                                                 irs.index_count
                                             ),
                                             0
                                         )
                                     ),
                                     0
                                 )
                             ),
                             'N0'
                         )
                    ELSE '0'
                END,
            avg_lock_wait_ms =
                CASE
                    WHEN (ISNULL(irs.row_lock_wait_count, 0) + ISNULL(irs.page_lock_wait_count, 0)) > 0
                    THEN FORMAT(1.0 * (ISNULL(irs.row_lock_wait_in_ms, 0) +
                         ISNULL(irs.page_lock_wait_in_ms, 0)) /
                         NULLIF(ISNULL(irs.row_lock_wait_count, 0) +
                         ISNULL(irs.page_lock_wait_count, 0), 0), 'N2')
                    ELSE '0'
                END,
            latch_wait_count =
                FORMAT(ISNULL(irs.page_latch_wait_count, 0) +
                       ISNULL(irs.page_io_latch_wait_count, 0), 'N0'),
            daily_latch_waits_saved =
                CASE
                    WHEN ISNULL(irs.unused_indexes, 0) > 0
                    THEN FORMAT
                         (
                             CONVERT
                             (
                                 decimal(38,2),
                                 ISNULL
                                 (
                                     (irs.page_latch_wait_count + irs.page_io_latch_wait_count) /
                                     NULLIF
                                     (
                                         CONVERT
                                         (
                                             decimal(38,2),
                                             @server_uptime_days
                                         ),
                                         0
                                     ) *
                                     (
                                         ISNULL
                                         (
                                             irs.unused_indexes,
                                             0
                                         ) /
                                         NULLIF
                                         (
                                             CONVERT
                                             (
                                                 decimal(38,2),
                                                 irs.index_count
                                             ),
                                             0
                                         )
                                     ),
                                     0
                                 )
                             ),
                             'N0'
                         )
                    ELSE '0'
                END,
            avg_latch_wait_ms =
                CASE
                    WHEN (ISNULL(irs.page_latch_wait_count, 0) + ISNULL(irs.page_io_latch_wait_count, 0)) > 0
                    THEN FORMAT(1.0 * (ISNULL(irs.page_latch_wait_in_ms, 0) +
                         ISNULL(irs.page_io_latch_wait_in_ms, 0)) /
                         NULLIF(ISNULL(irs.page_latch_wait_count, 0) +
                         ISNULL(irs.page_io_latch_wait_count, 0), 0), 'N2')
                    ELSE '0'
                END,
            /* Hidden sort columns */
            sort_database = irs.database_name,
            sort_level = 2,
            sort_unused_size = ISNULL(irs.unused_size_gb, 0),
            sort_total_size = ISNULL(irs.total_size_gb, 0)
        FROM #index_reporting_stats AS irs
        WHERE irs.summary_level = 'TABLE'
    ) AS x
    ORDER BY
        x.sort_database,
        x.sort_level,
        x.sort_unused_size DESC,
        x.sort_total_size DESC,
        x.schema_name,
        x.table_name
    OPTION(RECOMPILE);

    /* Output message for dedupe_only mode */
    IF @dedupe_only = 1
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Note: Operating in dedupe_only mode. Unused indexes were considered for deduplication only, not for removal.', 0, 1) WITH NOWAIT;
        END;
    END;

    /* Display detailed reports for computed columns with UDFs */
    IF EXISTS
    (
        SELECT
            1/0
        FROM #computed_columns_analysis  AS cca
        WHERE cca.contains_udf = 1
    )
    BEGIN
        SELECT
            finding_type = 'COMPUTED COLUMNS WITH UDF REFERENCES',
            cca.database_name,
            cca.schema_name,
            cca.table_name,
            cca.column_name,
            cca.definition,
            recommendation = 'Consider replacing UDF with inline logic to improve performance'
        FROM #computed_columns_analysis AS cca
        WHERE cca.contains_udf = 1
        ORDER BY
            cca.database_name,
            cca.schema_name,
            cca.table_name,
            cca.column_name;
    END;

    /* Display detailed reports for check constraints with UDFs */
    IF EXISTS
    (
        SELECT
            1/0
        FROM #check_constraints_analysis AS cca
        WHERE cca.contains_udf = 1
    )
    BEGIN
        SELECT
            finding_type = 'CHECK CONSTRAINTS WITH UDF REFERENCES',
            cca.database_name,
            cca.schema_name,
            cca.table_name,
            cca.constraint_name,
            cca.definition,
            recommendation = 'Consider replacing UDF with inline logic to improve performance'
        FROM #check_constraints_analysis AS cca
        WHERE cca.contains_udf = 1
        ORDER BY
            cca.database_name,
            cca.schema_name,
            cca.table_name,
            cca.constraint_name;
    END;

    /* Display detailed reports for filtered indexes that need column optimization */
    IF EXISTS
    (
        SELECT
            1/0
        FROM #filtered_index_columns_analysis AS fica
        WHERE fica.should_include_filter_columns = 1
    )
    BEGIN
        SELECT
            finding_type = 'FILTERED INDEXES NEEDING INCLUDED COLUMNS',
            fica.database_name,
            fica.schema_name,
            fica.table_name,
            fica.index_name,
            fica.filter_definition,
            ia.original_index_definition,
            fica.missing_included_columns,
            recommendation = 'Add filter columns to INCLUDE list to improve performance and avoid key lookups'
        FROM #filtered_index_columns_analysis AS fica
        JOIN #index_analysis AS ia
          ON  ia.database_id = fica.database_id
          AND ia.schema_id = fica.schema_id
          AND ia.object_id = fica.object_id
          AND ia.index_id = fica.index_id
        WHERE fica.should_include_filter_columns = 1
        ORDER BY
            fica.database_name,
            fica.schema_name,
            fica.table_name,
            fica.index_name;
    END;

    /* Check for databases that were processed but had no objects to analyze */
    IF EXISTS
    (
        SELECT
            1/0
        FROM #databases AS d
        WHERE NOT EXISTS
        (
            SELECT
                1/0
            FROM #index_reporting_stats AS irs
            WHERE irs.database_name = d.database_name
        )
    )
    BEGIN
        WITH
            empty_databases AS
        (
            SELECT
                database_name
            FROM #databases AS d
            WHERE NOT EXISTS
            (
                SELECT
                    1/0
                FROM #index_reporting_stats AS irs
                WHERE irs.database_name = d.database_name
            )
        )
        SELECT
            finding_type = 'DATABASES WITH NO QUALIFYING OBJECTS',
            database_name = d.database_name + N' - Nothing Found',
            recommendation = 'Database was processed but no objects met the analysis criteria'
        FROM empty_databases AS d
        ORDER BY
            database_name
        OPTION(RECOMPILE);
    END;

END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
    BEGIN
        ROLLBACK;
    END;

    THROW;
END CATCH;
END; /*Final End*/
GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
██╗      ██████╗  ██████╗
██║     ██╔═══██╗██╔════╝
██║     ██║   ██║██║  ███╗
██║     ██║   ██║██║   ██║
███████╗╚██████╔╝╚██████╔╝
╚══════╝ ╚═════╝  ╚═════╝

██╗  ██╗██╗   ██╗███╗   ██╗████████╗███████╗██████╗
██║  ██║██║   ██║████╗  ██║╚══██╔══╝██╔════╝██╔══██╗
███████║██║   ██║██╔██╗ ██║   ██║   █████╗  ██████╔╝
██╔══██║██║   ██║██║╚██╗██║   ██║   ██╔══╝  ██╔══██╗
██║  ██║╚██████╔╝██║ ╚████║   ██║   ███████╗██║  ██║
╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚═╝  ╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_LogHunter
    @help = 1;

For working through errors:
EXECUTE sp_LogHunter
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

EXECUTE sp_LogHunter;

*/

IF OBJECT_ID(N'dbo.sp_LogHunter', N'P') IS NULL
   BEGIN
       EXECUTE (N'CREATE PROCEDURE dbo.sp_LogHunter AS RETURN 138;');
   END;
GO

ALTER PROCEDURE
    dbo.sp_LogHunter
(
    @days_back integer = -7, /*How many days back you want to look in the error logs*/
    @start_date datetime = NULL, /*If you want to search a specific time frame*/
    @end_date datetime = NULL, /*If you want to search a specific time frame*/
    @custom_message nvarchar(4000) = NULL, /*If there's something you specifically want to search for*/
    @custom_message_only bit = 0, /*If you only want to search for this specific thing*/
    @first_log_only bit = 0, /*If you only want to search the first log file*/
    @language_id integer = 1033, /*If you want to use a language other than English*/
    @help bit = 0, /*Get help*/
    @debug bit = 0, /*Prints messages and selects from temp tables*/
    @version varchar(30) = NULL OUTPUT,
    @version_date datetime = NULL OUTPUT
)
WITH RECOMPILE
AS
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET DATEFORMAT MDY;

BEGIN
    SELECT
        @version = '3.3',
        @version_date = '20260301';

    IF @help = 1
    BEGIN
        SELECT
            introduction =
                'hi, i''m sp_LogHunter!' UNION ALL
        SELECT  'you can use me to look through your error logs for bad stuff' UNION ALL
        SELECT  'all scripts and documentation are available here: https://code.erikdarling.com' UNION ALL
        SELECT  'from your loving sql server consultant, erik darling: https://erikdarling.com';

        SELECT
            parameter_name =
                ap.name,
            data_type = t.name,
            description =
                CASE ap.name
                     WHEN N'@days_back' THEN 'how many days back you want to search the logs'
                     WHEN N'@start_date' THEN 'if you want to search a specific time frame'
                     WHEN N'@end_date' THEN 'if you want to search a specific time frame'
                     WHEN N'@custom_message' THEN 'if you want to search for a custom string'
                     WHEN N'@custom_message_only' THEN 'only search for the custom string'
                     WHEN N'@first_log_only' THEN 'only search through the first error log'
                     WHEN N'@language_id' THEN 'to use something other than English'
                     WHEN N'@help' THEN 'how you got here'
                     WHEN N'@debug' THEN 'dumps raw temp table contents'
                     WHEN N'@version' THEN 'OUTPUT; for support'
                     WHEN N'@version_date' THEN 'OUTPUT; for support'
                END,
            valid_inputs =
                CASE ap.name
                     WHEN N'@days_back' THEN 'an integer; will be converted to a negative number automatically'
                     WHEN N'@start_date' THEN 'a datetime value'
                     WHEN N'@end_date' THEN 'a datetime value'
                     WHEN N'@custom_message' THEN 'something specific you want to search for. no wildcards or substitutions.'
                     WHEN N'@custom_message_only' THEN 'NULL, 0, 1'
                     WHEN N'@first_log_only' THEN 'NULL, 0, 1'
                     WHEN N'@language_id' THEN 'SELECT DISTINCT m.language_id FROM sys.messages AS m ORDER BY m.language_id;'
                     WHEN N'@help' THEN 'NULL, 0, 1'
                     WHEN N'@debug' THEN 'NULL, 0, 1'
                     WHEN N'@version' THEN 'OUTPUT; for support'
                     WHEN N'@version_date' THEN 'OUTPUT; for support'
                END,
            defaults =
                CASE ap.name
                     WHEN N'@days_back' THEN '-7'
                     WHEN N'@start_date' THEN 'NULL'
                     WHEN N'@end_date' THEN 'NULL'
                     WHEN N'@custom_message' THEN 'NULL'
                     WHEN N'@custom_message_only' THEN '0'
                     WHEN N'@first_log_only' THEN '0'
                     WHEN N'@language_id' THEN '1033'
                     WHEN N'@help' THEN '0'
                     WHEN N'@debug' THEN '0'
                     WHEN N'@version' THEN 'none; OUTPUT'
                     WHEN N'@version_date' THEN 'none; OUTPUT'
                END
        FROM sys.all_parameters AS ap
        JOIN sys.all_objects AS o
          ON ap.object_id = o.object_id
        JOIN sys.types AS t
          ON  ap.system_type_id = t.system_type_id
          AND ap.user_type_id = t.user_type_id
        WHERE o.name = N'sp_LogHunter'
        OPTION(RECOMPILE);

        SELECT
            mit_license_yo = 'i am MIT licensed, so like, do whatever'

        UNION ALL

        SELECT
            mit_license_yo = 'see printed messages for full license';

        RAISERROR('
    MIT License

    Copyright 2026 Darling Data, LLC

    https://www.erikdarling.com/

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
    sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
    following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    ', 0, 1) WITH NOWAIT;

        RETURN;
    END;

    /*Check if we have sa permissisions, but not care in RDS*/
    IF
    (
        SELECT
            sa = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)
    ) = 0
    AND OBJECT_ID(N'rdsadmin.dbo.rds_read_error_log', N'P') IS NULL
    BEGIN
       RAISERROR(N'Current user is not a member of sysadmin, so we can''t read the error log', 11, 1) WITH NOWAIT;
       RETURN;
    END;

    /*Check if we're unfortunate*/
    IF
    (
        SELECT
            CONVERT
            (
                integer,
                SERVERPROPERTY('EngineEdition')
            )
    ) = 5
    BEGIN
       RAISERROR(N'This will not run on Azure SQL DB because it''s horrible.', 11, 1) WITH NOWAIT;
       RETURN;
    END;

    /*Validate the language id*/
    IF NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.messages AS m
        WHERE m.language_id = @language_id
    )
    BEGIN
       RAISERROR(N'%i is not a valid language_id in sys.messages.', 11, 1, @language_id) WITH NOWAIT;
       RETURN;
    END;

    /*Fix days back a little bit*/
    IF @days_back = 0
    BEGIN
        SELECT
            @days_back = -1;
    END;

    IF @days_back > 0
    BEGIN
        SELECT
            @days_back *= -1;
    END;

    IF  @start_date IS NOT NULL
    AND @end_date   IS NOT NULL
    AND @days_back  IS NOT NULL
    BEGIN
        SELECT
            @days_back = NULL;
    END;

    /*Fix custom message only if NULL*/
    IF @custom_message_only IS NULL
    BEGIN
        SELECT
            @custom_message_only = 0;
    END;

    /*Fix @end_date*/
    IF  @start_date IS NOT NULL
    AND @end_date IS NULL
    BEGIN
        SELECT
             @end_date = SYSDATETIME();
    END;

    /*Fix @start_date*/
    IF  @start_date IS NULL
    AND @end_date IS NOT NULL
    BEGIN
        SELECT
             @start_date = DATEADD(DAY, -7, @end_date);
    END;

    /*Debuggo*/
    IF @debug = 1
    BEGIN
        SELECT
            days_back = @days_back,
            start_date = @start_date,
            end_date = @end_date;
    END;

    /*variables for the variable gods*/
    DECLARE
        @c nvarchar(4000) /*holds the command to execute*/,
        @l_log integer = 0 /*low log file id*/,
        @h_log integer = 0 /*high log file id*/,
        @t_searches bigint = 0 /*total number of searches to run*/,
        @l_count integer = 1 /*loop count*/,
        @is_rds bit =
            CASE
                WHEN OBJECT_ID(N'rdsadmin.dbo.rds_read_error_log', N'P') IS NOT NULL
                THEN 1
                ELSE 0
            END;

    /*temp tables for holding temporary things*/
    CREATE TABLE
        #error_log
    (
        log_date datetime,
        process_info nvarchar(100),
        text nvarchar(4000)
    );

    CREATE TABLE
        #enum
    (
        archive integer
          PRIMARY KEY CLUSTERED,
        log_date date,
        log_size bigint
    );

    CREATE TABLE
        #search
    (
        id integer
           IDENTITY
           PRIMARY KEY CLUSTERED,
        search_string nvarchar(4000) DEFAULT N'""',
        days_back nvarchar(30) NULL,
        start_date nvarchar(30) NULL,
        end_date nvarchar(30) NULL,
        [current_date] nvarchar(10)
            DEFAULT N'"' + CONVERT(nvarchar(10), DATEADD(DAY, 1, SYSDATETIME()), 112) + N'"',
        search_order nvarchar(10)
            DEFAULT N'"DESC"',
        command AS
            CONVERT
            (
                nvarchar(4000),
                N'EXECUTE master.dbo.xp_readerrorlog [@@@], 1, '
                + search_string
                + N', '
                + N'" "'
                + N', '
                + ISNULL(start_date, days_back)
                + N', '
                + ISNULL(end_date, [current_date])
                + N', '
                + search_order
                + N';'
            ) PERSISTED
    );

    CREATE TABLE
        #errors
    (
        id integer
           PRIMARY KEY CLUSTERED
           IDENTITY,
        command nvarchar(4000) NOT NULL
    );

    /*get all the error logs*/
    INSERT
        #enum
    (
        archive,
        log_date,
        log_size
    )
    EXECUTE sys.sp_enumerrorlogs;

    IF @debug = 1 BEGIN SELECT table_name = '#enum before delete', e.* FROM #enum AS e; END;

    /*filter out log files we won't use, if @days_back is set*/
    IF @days_back IS NOT NULL
    BEGIN
        DELETE
            e WITH(TABLOCKX)
        FROM #enum AS e
        WHERE e.log_date < DATEADD(DAY, @days_back, SYSDATETIME())
        AND   e.archive > 0
        OPTION(RECOMPILE);
    END;

    /*filter out log files we won't use, if @start_date and @end_date are set*/
    IF  @start_date IS NOT NULL
    AND @end_date IS NOT NULL
    BEGIN
        DELETE
            e WITH(TABLOCKX)
        FROM #enum AS e
        WHERE (e.log_date < CONVERT(date, @start_date)
        OR     e.log_date > CONVERT(date, @end_date))
        AND   e.archive > 0
        OPTION(RECOMPILE);
    END;

    /*maybe you only want the first one anyway*/
    IF @first_log_only = 1
    BEGIN
        DELETE
            e WITH(TABLOCKX)
        FROM #enum AS e
        WHERE e.archive > 1
        OPTION(RECOMPILE);
    END;

    IF @debug = 1 BEGIN SELECT table_name = '#enum after delete', e.* FROM #enum AS e; END;

    /*insert some canary values for things that we should always hit. look a little further back for these.*/
    INSERT
        #search
    (
        search_string,
        days_back,
        start_date,
        end_date
    )
    SELECT
        x.search_string,
        c.days_back,
        c.start_date,
        c.end_date
    FROM
    (
        VALUES
            (N'"Microsoft SQL Server"'),
            (N'"detected"'),
            (N'"SQL Server has encountered"'),
            (N'"Warning: Enterprise Server/CAL license used for this instance"')
    ) AS x (search_string)
    CROSS JOIN
    (
        SELECT
            days_back =
                N'"' + CONVERT(nvarchar(10), DATEADD(DAY, CASE WHEN @days_back > -90 THEN -90 ELSE @days_back END, SYSDATETIME()), 112) + N'"',
            start_date =
                N'"' + CONVERT(nvarchar(30), @start_date) + N'"',
            end_date =
                N'"' + CONVERT(nvarchar(30), @end_date) + N'"'
    ) AS c
    WHERE @custom_message_only = 0
    OPTION(RECOMPILE);

    /*these are the search strings we currently care about*/
    INSERT
        #search
    (
        search_string,
        days_back,
        start_date,
        end_date
    )
    SELECT
        search_string =
            N'"' +
            v.search_string +
            N'"',
        c.days_back,
        c.start_date,
        c.end_date
    FROM
    (
        VALUES
            ('error'), ('corrupt'), ('insufficient'), ('DBCC CHECKDB'), ('Attempt to fetch logical page'), ('Total Log Writer threads'),
            ('Wait for redo catchup for the database'), ('Restart the server to resolve this problem'), ('running low'), ('unexpected'),
            ('fail'), ('contact'), ('incorrect'), ('allocate'), ('allocation'), ('Timeout occurred'), ('memory manager'), ('operating system'),
            ('cannot obtain a LOCK resource'), ('Server halted'), ('spawn'), ('BobMgr'), ('Sort is retrying the read'), ('service'),
            ('resumed'), ('repair the database'), ('buffer'), ('I/O Completion Port'), ('assert'), ('integrity'), ('latch'), ('SQL Server is exiting'),
            ('SQL Server is unable to run'), ('suspect'), ('restore the database'), ('checkpoint'), ('version store is full'), ('Setting database option'),
            ('Perform a restore if necessary'), ('Autogrow of file'), ('Bringing down database'), ('hot add'), ('Server shut down'),
            ('stack'), ('inconsistency.'), ('invalid'), ('time out occurred'), ('The transaction log for database'), ('The virtual log file sequence'),
            ('Cannot accept virtual log file sequence'), ('The transaction in database'), ('Shutting down'), ('thread pool'), ('debug'), ('resolving'),
            ('Cannot load the Query Store metadata'), ('Cannot acquire'), ('SQL Server evaluation period has expired'), ('terminat'), ('currently busy'),
            ('SQL Server has been configured for lightweight pooling'), ('IOCP'), ('Not enough memory for the configured number of locks'),
            ('The tempdb database data files are not configured with the same initial size and autogrowth settings'), ('The SQL Server image'), ('affinity'),
            ('SQL Server is starting'), ('Ignoring trace flag '), ('20 physical cores'), ('No free space'), ('Warning ******************'),
            ('SQL Server should be restarted'), ('Server name is'), ('Could not connect'), ('yielding'), ('worker thread'), ('A new connection was rejected'),
            ('A significant part of sql server process memory has been paged out'), ('Dispatcher'), ('I/O requests taking longer than'), ('killed'),
            ('SQL Server could not start'), ('SQL Server cannot start'), ('System Manufacturer:'), ('columnstore'), ('timed out'), ('inconsistent'),
            ('flushcache'), ('Recovery for availability database')
    ) AS v (search_string)
    CROSS JOIN
    (
        SELECT
            days_back =
                N'"' + CONVERT(nvarchar(10), DATEADD(DAY, @days_back, SYSDATETIME()), 112) + N'"',
            start_date =
                N'"' + CONVERT(nvarchar(30), @start_date) + N'"',
            end_date =
                N'"' + CONVERT(nvarchar(30), @end_date) + N'"'
    ) AS c
    WHERE @custom_message_only = 0
    OPTION(RECOMPILE);

    /*deal with a custom search string here*/
    INSERT
        #search
    (
        search_string,
        days_back,
        start_date,
        end_date
    )
    SELECT
        x.search_string,
        x.days_back,
        x.start_date,
        x.end_date
    FROM
    (
        VALUES
           (
                N'"' + @custom_message + '"',
                N'"' + CONVERT(nvarchar(10), DATEADD(DAY, @days_back, SYSDATETIME()), 112) + N'"',
                N'"' + CONVERT(nvarchar(30), @start_date) + N'"',
                N'"' + CONVERT(nvarchar(30), @end_date) + N'"'
           )
    ) AS x (search_string, days_back, start_date, end_date)
    WHERE @custom_message LIKE N'_%'
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        SELECT table_name = '#search', s.* FROM #search AS s;
    END;

    /*Set the min and max logs we're getting for the loop*/
    SELECT
        @l_log = MIN(e.archive),
        @h_log = MAX(e.archive),
        @t_searches = (SELECT COUNT_BIG(*) FROM #search AS s)
    FROM #enum AS e
    OPTION(RECOMPILE);

    IF @debug = 1
    BEGIN
        RAISERROR('@l_log: %i', 0, 1, @l_log) WITH NOWAIT;
        RAISERROR('@h_log: %i', 0, 1, @h_log) WITH NOWAIT;
        RAISERROR('@t_searches: %I64d', 0, 1, @t_searches) WITH NOWAIT;
    END;

    IF @debug = 1 BEGIN RAISERROR('Declaring cursor', 0, 1) WITH NOWAIT; END;

    /*start the loops*/
    WHILE @l_log <= @h_log
    BEGIN
        DECLARE
            @cs CURSOR;

        SET
            @cs =
        CURSOR
            LOCAL
            SCROLL
            DYNAMIC
            READ_ONLY
        FOR
        SELECT
            command
        FROM #search;

        IF @debug = 1 BEGIN RAISERROR('Opening cursor', 0, 1) WITH NOWAIT; END;

        OPEN @cs;

        FETCH FIRST
        FROM @cs
        INTO @c;

        IF @debug = 1 BEGIN RAISERROR('Entering WHILE loop', 0, 1) WITH NOWAIT; END;
        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF @debug = 1 BEGIN RAISERROR('Entering cursor', 0, 1) WITH NOWAIT; END;

            /*If using RDS, need to call a different procedure*/
            IF @is_rds = 1
            BEGIN
                SELECT
                    @c =
                        REPLACE
                        (
                            @c,
                            N'master.dbo.xp_readerrorlog',
                            N'rdsadmin.dbo.rds_read_error_log'
                        );
            END;

            /*Replace the canary value with the log number we're working in*/
            SELECT
                @c =
                    REPLACE
                    (
                        @c,
                        N'[@@@]',
                        @l_log
                    );

            IF @debug = 1
            BEGIN
                RAISERROR('log %i of %i', 0, 1, @l_log, @h_log) WITH NOWAIT;
                RAISERROR('search %i of %I64d', 0, 1, @l_count, @t_searches) WITH NOWAIT;
                RAISERROR('@c: %s', 0, 1, @c) WITH NOWAIT;
            END;

            IF @debug = 1 BEGIN RAISERROR('Inserting to error log', 0, 1) WITH NOWAIT; END;
            BEGIN
                BEGIN TRY
                    /*Insert any log entries we find that match the search*/
                    INSERT
                        #error_log
                    (
                        log_date,
                        process_info,
                        text
                    )
                    EXECUTE sys.sp_executesql
                        @c;
                END TRY
                BEGIN CATCH
                    /*Insert any searches that throw an error here*/
                    INSERT
                        #errors
                    (
                        command
                    )
                    VALUES
                    (
                        @c
                    );
                END CATCH;
            END;

            IF @debug = 1 BEGIN RAISERROR('Fetching next', 0, 1) WITH NOWAIT; END;

            /*Get the next search command*/
            FETCH NEXT
            FROM @cs
            INTO @c;

            /*Increment our loop counter*/
            SELECT
                @l_count += 1;

        END;

        IF @debug = 1 BEGIN RAISERROR('Getting next log', 0, 1) WITH NOWAIT; END;

        /*Increment the log numbers*/
        SELECT
            @l_log = MIN(e.archive),
            @l_count = 1
        FROM #enum AS e
        WHERE e.archive > @l_log
        OPTION(RECOMPILE);

        IF @debug = 1
        BEGIN
            RAISERROR('log %i of %i', 0, 1, @l_log, @h_log) WITH NOWAIT;
        END;

        /*Stop the loop if this is NULL*/
        IF @l_log IS NULL
        BEGIN
            IF @debug = 1 BEGIN RAISERROR('Breaking', 0, 1) WITH NOWAIT; END;
            BREAK;
        END;
        IF @debug = 1 BEGIN RAISERROR('Ended WHILE loop', 0, 1) WITH NOWAIT; END;
    END;
    IF @debug = 1 BEGIN RAISERROR('Ended cursor', 0, 1) WITH NOWAIT; END;

    /*get rid of some messages we don't care about*/
    IF @debug = 1 BEGIN RAISERROR('Delete dumb messages', 0, 1) WITH NOWAIT; END;

    DELETE
        el WITH(TABLOCKX)
    FROM #error_log AS el
    WHERE el.text LIKE N'DBCC TRACEON 3604%'
    OR    el.text LIKE N'DBCC TRACEOFF 3604%'
    OR    el.text LIKE N'This instance of SQL Server has been using a process ID of%'
    OR    el.text LIKE N'Could not connect because the maximum number of ''1'' dedicated administrator connections already exists%'
    OR    el.text LIKE N'Login failed%'
    OR    el.text LIKE N'Backup(%'
    OR    el.text LIKE N'[[]INFO]%'
    OR    el.text LIKE N'[[]DISK_SPACE_TO_RESERVE_PROPERTY]%'
    OR    el.text LIKE N'[[]CFabricCommonUtils::GetFabricPropertyInternalWithRef]%'
    OR    el.text LIKE N'CHECKDB for database % finished without errors%'
    OR    el.text LIKE N'Parallel redo is shutdown for database%'
    OR    el.text LIKE N'%This is an informational message only. No user action is required.%'
    OR    el.text LIKE N'%SPN%'
    OR    el.text LIKE N'Service Broker manager has started.%'
    OR    el.text LIKE N'Parallel redo is started for database%'
    OR    el.text LIKE N'Starting up database%'
    OR    el.text LIKE N'Buffer pool extension is already disabled%'
    OR    el.text LIKE N'Buffer Pool: Allocating % bytes for % hashPages.'
    OR    el.text LIKE N'The client was unable to reuse a session with%'
    OR    el.text LIKE N'SSPI%'
    OR    el.text LIKE N'%Severity: 1[0-8]%'
    OR    el.text LIKE N'Login succeeded for user%'
    OR    el.text LIKE N'%query notification%'
    OR    el.text IN
          (
              N'The Database Mirroring endpoint is in disabled or stopped state.',
              N'The Service Broker endpoint is in disabled or stopped state.'
          )
    OPTION(RECOMPILE);

    /*get rid of duplicate messages we don't care about*/
    IF @debug = 1 BEGIN RAISERROR('Delete dupe messages', 0, 1) WITH NOWAIT; END;

    WITH
        d AS
    (
        SELECT
            el.*,
            n =
                ROW_NUMBER() OVER
                (
                    PARTITION BY
                        el.log_date,
                        el.process_info,
                        el.text
                    ORDER BY
                        el.log_date
                )
        FROM #error_log AS el
    )
    DELETE
        d
    FROM d AS d WITH (TABLOCK)
    WHERE d.n > 1;

    /*Return the search results*/
    SELECT
        table_name =
            '#error_log',
        el.*
    FROM #error_log AS el
    ORDER BY
        el.log_date DESC
    OPTION(RECOMPILE);

    /*If we hit any errors, show which searches failed here*/
    IF EXISTS
    (
        SELECT
            1/0
        FROM #errors AS e
    )
    BEGIN
        SELECT
            table_name =
                '#errors',
            e.*
        FROM #errors AS e
        ORDER BY
            e.id
        OPTION(RECOMPILE);
    END;
END;
GO
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
██████╗ ███████╗██████╗ ███████╗
██╔══██╗██╔════╝██╔══██╗██╔════╝
██████╔╝█████╗  ██████╔╝█████╗
██╔═══╝ ██╔══╝  ██╔══██╗██╔══╝
██║     ███████╗██║  ██║██║
╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝

 ██████╗██╗  ██╗███████╗ ██████╗██╗  ██╗
██╔════╝██║  ██║██╔════╝██╔════╝██║ ██╔╝
██║     ███████║█████╗  ██║     █████╔╝
██║     ██╔══██║██╔══╝  ██║     ██╔═██╗
╚██████╗██║  ██║███████╗╚██████╗██║  ██╗
 ╚═════╝╚═╝  ╚═╝╚══════╝ ╚═════╝╚═╝  ╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_PerfCheck
    @help = 1;

For working through errors:
EXECUTE sp_PerfCheck
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/

IF OBJECT_ID(N'dbo.sp_PerfCheck', N'P') IS NULL
BEGIN
    EXECUTE(N'CREATE PROCEDURE dbo.sp_PerfCheck AS RETURN 138;');
END;
GO

ALTER PROCEDURE
    dbo.sp_PerfCheck
(
    @database_name sysname = NULL, /* Database to check, NULL for all user databases */
    @help bit = 0, /*For helpfulness*/
    @debug bit = 0, /* Print diagnostic messages */
    @version varchar(30) = NULL OUTPUT, /* Returns version */
    @version_date datetime = NULL OUTPUT /* Returns version date */
)
WITH RECOMPILE
AS
BEGIN
    SET NOCOUNT ON;
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    BEGIN TRY
        /*
        Set version information
        */
    SELECT
        @version = N'2.3',
        @version_date = N'20260301';

    /*
    Help section, for help.
    Will become more helpful when out of beta.
    */
    IF @help = 1
    BEGIN
        SELECT
            help = N'hello, i am sp_PerfCheck'
          UNION ALL
        SELECT
            help = N'i look at important performance settings and metrics'
          UNION ALL
        SELECT
            help = N'don''t hate me because i''m beautiful.'
          UNION ALL
        SELECT
            help = N'brought to you by erikdarling.com / code.erikdarling.com';

        /*
        Parameters
        */
        SELECT
            parameter_name =
                ap.name,
            data_type =
                t.name,
            description =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'the name of the database you wish to analyze'
                    WHEN N'@help' THEN 'displays this help information'
                    WHEN N'@debug' THEN 'prints debug information during execution'
                    WHEN N'@version' THEN 'returns the version number of the procedure'
                    WHEN N'@version_date' THEN 'returns the date this version was released'
                    ELSE NULL
                END,
            valid_inputs =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'the name of a database you wish to check'
                    WHEN N'@help' THEN '0 or 1'
                    WHEN N'@debug' THEN '0 or 1'
                    WHEN N'@version' THEN 'OUTPUT parameter'
                    WHEN N'@version_date' THEN 'OUTPUT parameter'
                    ELSE NULL
                END,
            defaults =
                CASE
                    ap.name
                    WHEN N'@database_name' THEN 'NULL'
                    WHEN N'@help' THEN 'false'
                    WHEN N'@debug' THEN 'false'
                    WHEN N'@version' THEN 'NULL'
                    WHEN N'@version_date' THEN 'NULL'
                    ELSE NULL
                END
        FROM sys.all_parameters AS ap
        JOIN sys.all_objects AS o
          ON ap.object_id = o.object_id
        JOIN sys.types AS t
          ON  ap.system_type_id = t.system_type_id
          AND ap.user_type_id = t.user_type_id
        WHERE o.name = N'sp_PerfCheck'
        OPTION(MAXDOP 1, RECOMPILE);

        SELECT
            mit_license_yo = 'i am MIT licensed, so like, do whatever'

        UNION ALL

        SELECT
            mit_license_yo = 'see printed messages for full license';

        RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;

        RETURN;
    END;

    /*
    Variable Declarations
    */
    DECLARE
        @product_version sysname =
            CONVERT
            (
                sysname,
                SERVERPROPERTY(N'ProductVersion')
            ),
        @product_version_major decimal(10, 2) =
            SUBSTRING
            (
                CONVERT
                (
                    sysname,
                    SERVERPROPERTY(N'ProductVersion')
                ),
                1,
                CHARINDEX
                (
                    '.',
                    CONVERT
                    (
                        sysname,
                        SERVERPROPERTY(N'ProductVersion')
                    )
                ) + 1
            ),
        @product_version_minor decimal(10, 2) =
            PARSENAME
            (
                CONVERT
                (
                    varchar(32),
                    CONVERT
                    (
                        sysname,
                        SERVERPROPERTY(N'ProductVersion')
                    )
                ),
                2
            ),
        @product_level sysname =
            CONVERT
            (
                sysname,
                SERVERPROPERTY(N'ProductLevel')
            ),
        @product_edition sysname =
            CONVERT
            (
                sysname,
                SERVERPROPERTY(N'Edition')
            ),
        @server_name sysname =
            CONVERT
            (
                sysname,
                SERVERPROPERTY(N'ServerName')
            ),
        @engine_edition integer =
            CONVERT
            (
                integer,
                SERVERPROPERTY(N'EngineEdition')
            ),
        @start_time datetime2(0) = SYSDATETIME(),
        @error_message nvarchar(4000) = N'',
        @sql nvarchar(max) = N'',
        @azure_sql_db bit = 0,
        @azure_managed_instance bit = 0,
        @aws_rds bit = 0,
        @is_sysadmin bit =
            ISNULL
            (
                IS_SRVROLEMEMBER(N'sysadmin'),
                0
            ),
        @has_view_server_state bit =
        /*
            I'm using this as a shortcut here so I don't
            have to do anything else later if not sa
        */
            ISNULL
            (
                IS_SRVROLEMEMBER(N'sysadmin'),
                0
            ),
        @current_database_name sysname,
        @current_database_id integer,
        @processors integer,
        @message nvarchar(4000),
        /* Memory configuration variables */
        @min_server_memory bigint,
        @max_server_memory bigint,
        @physical_memory_gb decimal(10, 2),
        /* MAXDOP and CTFP variables */
        @max_dop integer,
        @cost_threshold integer,
        /* Other configuration variables */
        @priority_boost bit,
        @lightweight_pooling bit,
        @affinity_mask bigint,
        @affinity_io_mask bigint,
        @affinity64_mask bigint,
        @affinity64_io_mask bigint,
        /* TempDB configuration variables */
        @tempdb_data_file_count integer,
        @tempdb_log_file_count integer,
        @min_data_file_size decimal(18, 2),
        @max_data_file_size decimal(18, 2),
        @size_difference_pct decimal(18, 2),
        @has_percent_growth bit,
        @has_fixed_growth bit,
        /* Storage performance variables */
        @slow_read_ms decimal(10, 2) = 500.0, /* Threshold for slow reads (ms) */
        @slow_write_ms decimal(10, 2) = 500.0, /* Threshold for slow writes (ms) */
        /* Set threshold for "slow" autogrowth (in ms) */
        @slow_autogrow_ms integer = 1000,  /* 1 second */
        @trace_path nvarchar(260),
        @has_tables bit = 0,
        /* Determine total waits, uptime, and significant waits */
        @total_waits bigint,
        @uptime_ms bigint,
        @significant_wait_threshold_pct decimal(5, 2) = 10.0, /* Only waits above 10% */
        @significant_wait_threshold_avg decimal(10, 2) = 10.0, /* Or avg wait time > 10ms */
        /* Threshold settings for stolen memory alert */
        @buffer_pool_size_gb decimal(38, 2),
        @stolen_memory_gb decimal(38, 2),
        @stolen_memory_pct decimal(10, 2),
        @stolen_memory_threshold_pct decimal(10, 2) = 15.0, /* Alert if more than 15% memory is stolen */
        /* CPU scheduling variables */
        @signal_wait_time_ms bigint,
        @total_wait_time_ms bigint,
        @sos_scheduler_yield_ms bigint,
        @signal_wait_ratio decimal(10, 2),
        @sos_scheduler_yield_pct_of_uptime decimal(10, 2),
        /* First check what columns exist in sys.databases to handle version differences */
        @has_is_ledger bit = 0,
        @has_is_accelerated_database_recovery bit = 0,
        /*SQLDB stuff for IO stats*/
        @io_sql nvarchar(max) = N'',
        @file_io_sql nvarchar(max) = N'',
        @db_size_sql nvarchar(max) = N'',
        @tempdb_files_sql nvarchar(max) = N'',
        /* TempDB pagelatch contention variables */
        @pagelatch_wait_hours decimal(20,2),
        @server_uptime_hours decimal(20,2),
        @pagelatch_ratio_to_uptime decimal(10,4),
        /* Memory health history variables (SQL Server 2025+) */
        @health_history_exists bit =
            CASE
                WHEN OBJECT_ID('sys.dm_os_memory_health_history') IS NOT NULL
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @health_history_count bigint = 0;


    /* Check for VIEW SERVER STATE permission */
    IF @is_sysadmin = 0
    BEGIN
        BEGIN TRY
            EXECUTE sys.sp_executesql
                N'
                    SELECT
                        @has_view_server_state = 1
                    FROM sys.dm_os_sys_info AS osi;
                ',
                N'@has_view_server_state bit OUTPUT',
                  @has_view_server_state OUTPUT;
        END TRY
        BEGIN CATCH
            SET @has_view_server_state = 0;
        END CATCH;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            permission_check = N'Permission Check',
            is_sysadmin = @is_sysadmin,
            has_view_server_state = @has_view_server_state;
    END;

    /*
    Environment Detection
    */

    /* Is this Azure SQL DB? */
    IF @engine_edition = 5
    BEGIN
        SET @azure_sql_db = 1;
    END;

    /* Is this Azure Managed Instance? */
    IF @engine_edition = 8
    BEGIN
        SET @azure_managed_instance = 1;
    END;

    /* Is this AWS RDS? Only check if not Azure */
    IF  @azure_sql_db = 0
    AND @azure_managed_instance = 0
    BEGIN
        IF DB_ID('rdsadmin') IS NOT NULL
        BEGIN
            SET @aws_rds = 1;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            environment_check = N'Environment Check',
            product_version = @product_version,
            product_version_major = @product_version_major,
            engine_edition = @engine_edition,
            is_azure = @azure_sql_db,
            is_azure_managed_instance = @azure_managed_instance,
            is_aws_rds = @aws_rds;
    END;

    /*
    Create a table for stuff I care about from sys.databases
    With comments on what we want to check
    */
    CREATE TABLE
        #databases
    (
        name sysname NOT NULL,
        database_id integer NOT NULL,
        compatibility_level tinyint NOT NULL,
        collation_name sysname NULL,
        user_access_desc nvarchar(60) NOT NULL,
        is_read_only bit NOT NULL,
        is_auto_close_on bit NOT NULL,
        is_auto_shrink_on bit NOT NULL,
        state_desc nvarchar(60) NOT NULL,
        snapshot_isolation_state_desc nvarchar(60) NOT NULL,
        is_read_committed_snapshot_on bit NOT NULL,
        is_auto_create_stats_on bit NOT NULL,
        is_auto_create_stats_incremental_on bit NOT NULL,
        is_auto_update_stats_on bit NOT NULL,
        is_auto_update_stats_async_on bit NOT NULL,
        is_ansi_null_default_on bit NOT NULL,
        is_ansi_nulls_on bit NOT NULL,
        is_ansi_padding_on bit NOT NULL,
        is_ansi_warnings_on bit NOT NULL,
        is_arithabort_on bit NOT NULL,
        is_concat_null_yields_null_on bit NOT NULL,
        is_numeric_roundabort_on bit NOT NULL,
        is_quoted_identifier_on bit NOT NULL,
        is_parameterization_forced bit NOT NULL,
        is_query_store_on bit NOT NULL,
        is_distributor bit NOT NULL,
        is_cdc_enabled bit NOT NULL,
        target_recovery_time_in_seconds integer NULL,
        delayed_durability_desc nvarchar(60) NULL,
        is_accelerated_database_recovery_on bit NOT NULL,
        is_ledger_on bit NULL
    );

    /* Create table for database scoped configurations */
    CREATE TABLE
        #database_scoped_configs
    (
        database_id integer NOT NULL,
        database_name sysname NOT NULL,
        configuration_id integer NOT NULL,
        name nvarchar(60) NOT NULL,
        value sql_variant NULL,
        value_for_secondary sql_variant NULL,
        is_value_default bit NOT NULL
    );

    /*
    Create Results Table
    */
    CREATE TABLE
        #results
    (
        id integer IDENTITY PRIMARY KEY CLUSTERED,
        check_id integer NOT NULL,
        priority integer NOT NULL,
        category nvarchar(50) NOT NULL,
        finding nvarchar(200) NOT NULL,
        database_name sysname NOT NULL DEFAULT N'N/A',
        object_name sysname NOT NULL DEFAULT N'N/A',
        details nvarchar(4000) NULL,
        url nvarchar(200) NULL
    );

    /*
    Create Server Info Table
    */
    CREATE TABLE
        #server_info
    (
        id integer IDENTITY PRIMARY KEY CLUSTERED,
        info_type nvarchar(100) NOT NULL,
        value nvarchar(4000) NULL
    );

    /* Create temp table to store TempDB file info */
    CREATE TABLE
        #tempdb_files
    (
        file_id integer NOT NULL,
        file_name sysname NOT NULL,
        type_desc nvarchar(60) NOT NULL,
        size_mb decimal(18, 2) NOT NULL,
        max_size_mb decimal(18, 2) NOT NULL,
        growth_mb decimal(18, 2) NOT NULL,
        is_percent_growth bit NOT NULL
    );

    /* Create temp table for IO stats */
    CREATE TABLE
        #io_stats
    (
        database_name sysname NOT NULL,
        database_id integer NOT NULL,
        file_name sysname NOT NULL,
        type_desc nvarchar(60) NOT NULL,
        io_stall_read_ms bigint NOT NULL,
        num_of_reads bigint NOT NULL,
        avg_read_latency_ms decimal(18, 2) NOT NULL,
        io_stall_write_ms bigint NOT NULL,
        num_of_writes bigint NOT NULL,
        avg_write_latency_ms decimal(18, 2) NOT NULL,
        io_stall_ms bigint NOT NULL,
        total_io bigint NOT NULL,
        avg_io_latency_ms decimal(18, 2) NOT NULL,
        size_mb decimal(18, 2) NOT NULL,
        drive_location nvarchar(260) NULL,
        physical_name nvarchar(260) NOT NULL
    );

    /*
    Create Database List for Iteration
    */
    CREATE TABLE
        #database_list
    (
        id integer IDENTITY PRIMARY KEY CLUSTERED,
        database_name sysname NOT NULL,
        database_id integer NOT NULL,
        state integer NOT NULL,
        state_desc nvarchar(60) NOT NULL,
        compatibility_level integer NOT NULL,
        recovery_model_desc nvarchar(60) NOT NULL,
        is_read_only bit NOT NULL,
        is_in_standby bit NOT NULL,
        is_encrypted bit NOT NULL,
        create_date datetime NOT NULL,
        can_access bit NOT NULL
    );

    /* Create a temp table for trace flags */
    CREATE TABLE
        #trace_flags
    (
        trace_flag integer NOT NULL,
        status integer NOT NULL,
        global integer NOT NULL,
        session integer NOT NULL
    );

    /* Create temp table for trace events */
    CREATE TABLE
        #trace_events
    (
        id integer IDENTITY PRIMARY KEY CLUSTERED,
        event_time datetime NOT NULL,
        event_class integer NOT NULL,
        event_subclass integer NULL,
        event_name sysname NULL,
        category_name sysname NULL,
        database_name sysname NULL,
        database_id integer NULL,
        file_name nvarchar(260) NULL,
        object_name sysname NULL,
        object_type integer NULL,
        duration_ms bigint NULL,
        severity integer NULL,
        success bit NULL,
        error integer NULL,
        text_data nvarchar(MAX) NULL,
        file_growth bigint NULL,
        is_auto bit NULL,
        spid integer NOT NULL
    );

    /* Define event class mapping for more readable output */
    CREATE TABLE
        #event_class_map
    (
        event_class integer PRIMARY KEY CLUSTERED,
        event_name sysname NOT NULL,
        category_name sysname NOT NULL
    );

    /* Create temp table for wait stats */
    CREATE TABLE
        #wait_stats
    (
        id integer IDENTITY PRIMARY KEY CLUSTERED,
        wait_type nvarchar(60) NOT NULL,
        description nvarchar(100) NOT NULL,
        wait_time_ms bigint NOT NULL,
        wait_time_minutes AS (wait_time_ms / 1000.0 / 60.0),
        wait_time_hours AS (wait_time_ms / 1000.0 / 60.0 / 60.0),
        waiting_tasks_count bigint NOT NULL,
        avg_wait_ms AS (wait_time_ms / NULLIF(waiting_tasks_count, 0)),
        percentage decimal(5, 2) NOT NULL,
        signal_wait_time_ms bigint NOT NULL,
        wait_time_percent_of_uptime decimal(6, 2) NULL,
        category nvarchar(50) NOT NULL
    );

    /* Create temp table for benign waits to filter out */
    CREATE TABLE
        #benign_waits
    (
        wait_type nvarchar(60) PRIMARY KEY CLUSTERED
    );

    /* Populate benign waits table */
    INSERT INTO
        #benign_waits
    (
        wait_type
    )
    VALUES
        (N'BROKER_TASK_STOP'),
        (N'BROKER_TO_FLUSH'),
        (N'BROKER_TRANSMITTER'),
        (N'CHECKPOINT_QUEUE'),
        (N'CLR_AUTO_EVENT'),
        (N'CLR_MANUAL_EVENT'),
        (N'DIRTY_PAGE_POLL'),
        (N'DISPATCHER_QUEUE_SEMAPHORE'),
        (N'FSAGENT'),
        (N'FT_IFTS_SCHEDULER_IDLE_WAIT'),
        (N'FT_IFTSHC_MUTEX'),
        (N'HADR_FILESTREAM_IOMGR_IOCOMPLETION'),
        (N'HADR_LOGCAPTURE_WAIT'),
        (N'HADR_TIMER_TASK'),
        (N'HADR_WORK_QUEUE'),
        (N'LAZYWRITER_SLEEP'),
        (N'LOGMGR_QUEUE'),
        (N'MEMORY_ALLOCATION_EXT'),
        (N'PREEMPTIVE_XE_GETTARGETSTATE'),
        (N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP'),
        (N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP'),
        (N'REQUEST_FOR_DEADLOCK_SEARCH'),
        (N'RESOURCE_QUEUE'),
        (N'SERVER_IDLE_CHECK'),
        (N'SLEEP_DBSTARTUP'),
        (N'SLEEP_DCOMSTARTUP'),
        (N'SLEEP_MASTERDBREADY'),
        (N'SLEEP_MASTERMDREADY'),
        (N'SLEEP_MASTERUPGRADED'),
        (N'SLEEP_MSDBSTARTUP'),
        (N'SLEEP_SYSTEMTASK'),
        (N'SLEEP_TEMPDBSTARTUP'),
        (N'SNI_HTTP_ACCEPT'),
        (N'SP_SERVER_DIAGNOSTICS_SLEEP'),
        (N'SQLTRACE_BUFFER_FLUSH'),
        (N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP'),
        (N'SQLTRACE_WAIT_ENTRIES'),
        (N'STARTUP_DEPENDENCY_MANAGER'),
        (N'WAIT_FOR_RESULTS'),
        (N'WAITFOR'),
        (N'WAITFOR_TASKSHUTDOWN'),
        (N'WAIT_XTP_HOST_WAIT'),
        (N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG'),
        (N'WAIT_XTP_CKPT_CLOSE'),
        (N'XE_DISPATCHER_JOIN'),
        (N'XE_DISPATCHER_WAIT'),
        (N'XE_LIVE_TARGET_TVF'),
        (N'XE_TIMER_EVENT');

    /* Create temp table for database I/O stalls */
    CREATE TABLE
        #io_stalls_by_db
    (
        database_name sysname NOT NULL,
        database_id integer NOT NULL,
        total_io_stall_ms bigint NOT NULL,
        total_io_mb decimal(18, 2) NOT NULL,
        avg_io_stall_ms decimal(18, 2) NOT NULL,
        read_io_stall_ms bigint NOT NULL,
        read_io_mb decimal(18, 2) NOT NULL,
        avg_read_stall_ms decimal(18, 2) NOT NULL,
        write_io_stall_ms bigint NOT NULL,
        write_io_mb decimal(18, 2) NOT NULL,
        avg_write_stall_ms decimal(18, 2) NOT NULL,
        total_size_mb decimal(18, 2) NOT NULL
    );

    /*
    Collect basic server information (works on all platforms)
    */
    IF @debug = 1
    BEGIN
        RAISERROR('Collecting server information', 0, 1) WITH NOWAIT;
    END;

    /* Basic server information that works across all platforms */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
        (N'sp_PerfCheck', N'Brought to you by Darling Data');

    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
        (N'https://code.erikdarling.com', N'https://erikdarling.com');

    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
    (
        N'Version',
        @version +
        N' (' +
        CONVERT
        (
            varchar(10),
            @version_date,
            101
        ) +
        N')'
    );

    /* Using server name variable declared earlier */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
        (N'Server Name', @server_name);

    /* Using product version and level variables declared earlier */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
    (
        N'SQL Server Version',
        @product_version +
        N' (' +
        @product_level +
        N')'
    );

    /* Using product edition variable declared earlier */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
        (N'SQL Server Edition', @product_edition);

    /* Environment information - Already detected earlier */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    SELECT
        N'Environment',
        CASE
            WHEN @azure_sql_db = 1
            THEN N'Azure SQL Database'
            WHEN @azure_managed_instance = 1
            THEN N'Azure SQL Managed Instance'
            WHEN @aws_rds = 1
            THEN N'AWS RDS SQL Server'
            ELSE N'On-premises or IaaS SQL Server'
        END;

    /* Uptime information - works on all platforms if permissions allow */
    IF @has_view_server_state = 1
    BEGIN
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        SELECT
            N'Uptime',
            CONVERT
            (
                nvarchar(30),
                DATEDIFF
                (
                    DAY,
                    osi.sqlserver_start_time,
                    SYSDATETIME()
                )
            ) +
            N' days, ' +
            CONVERT
            (
                nvarchar(8),
                CONVERT
                (
                    time,
                    DATEADD
                    (
                        SECOND,
                        DATEDIFF
                        (
                            SECOND,
                            osi.sqlserver_start_time,
                            SYSDATETIME()
                        ) % 86400,
                        '00:00:00'
                    )
                ),
                108
            ) +
            N' (hh:mm:ss)'
        FROM sys.dm_os_sys_info AS osi;
    END
    ELSE
    BEGIN
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
            (N'Uptime', N'Information unavailable (requires VIEW SERVER STATE permission)');
    END;

    /* CPU information - works on all platforms if permissions allow */
    IF @has_view_server_state = 1
    BEGIN
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        SELECT
            N'CPU',
            CONVERT(nvarchar(10), osi.cpu_count) +
            N' logical processors, ' +
            CONVERT(nvarchar(10), osi.hyperthread_ratio) +
            N' physical cores, ' +
            CONVERT(nvarchar(10), ISNULL(osi.numa_node_count, 1)) +
            N' NUMA node(s)'
        FROM sys.dm_os_sys_info AS osi;

        /* Store processor count for TempDB file count checks */
        SELECT
            @processors = osi.cpu_count
        FROM sys.dm_os_sys_info AS osi;
    END
    ELSE
    BEGIN
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
            (N'CPU', N'Information unavailable (requires VIEW SERVER STATE permission)');
    END;

    /* Check for offline schedulers */
    IF @azure_sql_db = 0 /* Not applicable to Azure SQL DB */
    AND @has_view_server_state = 1 /* Requires VIEW SERVER STATE permission */
    BEGIN
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 4001,
            priority = 10, /* Critical: missing CPU resources */
            category = N'CPU Configuration',
            finding = N'Offline CPU Schedulers',
            details =
                CONVERT(nvarchar(10), COUNT_BIG(*)) +
                N' CPU scheduler(s) are offline out of ' +
                CONVERT(nvarchar(10), (SELECT cpu_count FROM sys.dm_os_sys_info)) +
                N' logical processors. This reduces available processing power. ' +
                N'Check affinity mask configuration, licensing, or VM CPU cores/sockets',
            url = N'https://erikdarling.com/sp_perfcheck/#OfflineCPU'
        FROM sys.dm_os_schedulers AS dos
        WHERE dos.is_online = 0
        HAVING
            COUNT_BIG(*) > 0; /* Only if there are offline schedulers */
    END;

    /* Check for forced grants and memory grant timeouts - requires VIEW SERVER STATE permission */
    IF @has_view_server_state = 1
    BEGIN
        /* Check for forced grants */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 4101,
            priority = 20, /* High: active memory spills */
            category = N'Memory Pressure',
            finding = N'Memory-Starved Queries Detected',
            details =
                N'dm_exec_query_resource_semaphores has ' +
                CONVERT(nvarchar(10), MAX(ders.forced_grant_count)) +
                N' forced memory grants. ' +
                N'Queries are being forced to run with less memory than requested, which can cause spills to tempdb and poor performance.',
            url = N'https://erikdarling.com/sp_perfcheck/#MemoryStarved'
        FROM sys.dm_exec_query_resource_semaphores AS ders
        WHERE ders.forced_grant_count > 0
        HAVING
            MAX(ders.forced_grant_count) > 0; /* Only if there are actually forced grants */

        /* Check for memory grant timeouts */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 4103,
            priority = 20, /* High: queries can't get memory */
            category = N'Memory Pressure',
            finding = N'Memory-Starved Queries Detected',
            details =
                N'dm_exec_query_resource_semaphores has ' +
                CONVERT(nvarchar(10), MAX(ders.timeout_error_count)) +
                N' memory grant timeouts. ' +
                N'Queries are waiting for memory for a long time and giving up.',
            url = N'https://erikdarling.com/sp_perfcheck/#MemoryStarved'
        FROM sys.dm_exec_query_resource_semaphores AS ders
        WHERE ders.timeout_error_count > 0
        HAVING
            MAX(ders.timeout_error_count) > 0; /* Only if there are actually forced grants */
    END;

    /* Check for SQL Server memory dumps (on-prem only) */
    IF  @azure_sql_db = 0
    AND @azure_managed_instance = 0
    AND @has_view_server_state = 1 /* Requires sysadmin permission */
    BEGIN
        /* First check if the DMV exists (SQL 2008+) */
        IF OBJECT_ID('sys.dm_server_memory_dumps') IS NOT NULL
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            SELECT
                check_id = 4102,
                priority = 10, /* Critical: server crashing */
                category = N'Server Stability',
                finding = N'Memory Dumps Detected In Last 90 Days',
                details =
                    CONVERT(nvarchar(10), COUNT_BIG(*)) +
                    N' memory dump(s) found. Most recent: ' +
                    CONVERT(nvarchar(30), MAX(dsmd.creation_time), 120) +
                    N', ' +
                    N' at ' +
                    MAX(dsmd.filename) +
                    N'. Check the SQL Server error log and Windows event logs.',
                url = N'https://erikdarling.com/sp_perfcheck/#MemoryDumps'
            FROM sys.dm_server_memory_dumps AS dsmd
            WHERE dsmd.creation_time >= DATEADD(DAY, -90, SYSDATETIME())
            HAVING
                COUNT_BIG(*) > 0; /* Only if there are memory dumps */
        END;
    END;

    IF @has_view_server_state = 1
    BEGIN
    /* Check for high number of deadlocks */
    INSERT INTO
        #results
    (
        check_id,
        priority,
        category,
        finding,
        details,
        url
    )
    SELECT
        check_id = 5103,
        priority =
            CASE
                WHEN
                (
                    1.0 *
                    p.cntr_value /
                    NULLIF
                    (
                        DATEDIFF
                        (
                            DAY,
                            osi.sqlserver_start_time,
                            SYSDATETIME()
                        ),
                        0
                    )
                ) > 100
                THEN 20 /* High: >100 deadlocks/day */
                WHEN
                (
                    1.0 *
                    p.cntr_value /
                    NULLIF
                    (
                        DATEDIFF
                        (
                            DAY,
                            osi.sqlserver_start_time,
                            SYSDATETIME()
                        ),
                        0
                    )
                ) > 50
                THEN 20 /* High: >50 deadlocks/day is still severe */
                ELSE 30 /* Medium: >9 deadlocks/day */
            END,
        category = N'Concurrency',
        finding = N'High Number of Deadlocks',
        details =
            CASE
                WHEN DATEDIFF(DAY, osi.sqlserver_start_time, SYSDATETIME()) >= 1
                THEN
                    N'Server is averaging ' +
                    CONVERT(nvarchar(20), CONVERT(decimal(10, 2), 1.0 * p.cntr_value /
                      DATEDIFF(DAY, osi.sqlserver_start_time, SYSDATETIME()))) +
                    N' deadlocks per day since startup (' +
                    CONVERT(nvarchar(20), p.cntr_value) +
                    N' total deadlocks over ' +
                    CONVERT(nvarchar(10), DATEDIFF(DAY, osi.sqlserver_start_time, SYSDATETIME())) +
                    N' days). ' +
                    N'High deadlock rates indicate concurrency issues that should be investigated.'
                ELSE
                    N'Server has recorded ' +
                    CONVERT(nvarchar(20), p.cntr_value) +
                    N' deadlocks in ' +
                    CONVERT(nvarchar(10), DATEDIFF(HOUR, osi.sqlserver_start_time, SYSDATETIME())) +
                    N' hours since startup. ' +
                    N'High deadlock rates indicate concurrency issues that should be investigated.'
            END,
        url = N'https://erikdarling.com/sp_perfcheck/#Deadlocks'
    FROM sys.dm_os_performance_counters AS p
    CROSS JOIN sys.dm_os_sys_info AS osi
    WHERE RTRIM(p.counter_name) = N'Number of Deadlocks/sec'
    AND   RTRIM(p.instance_name) = N'_Total'
    AND   p.cntr_value > 0
    AND
    (
        1.0 *
        p.cntr_value /
        NULLIF
        (
            DATEDIFF
            (
                DAY,
                osi.sqlserver_start_time,
                SYSDATETIME()
            ),
            0
        )
    ) > 9; /* More than 9 deadlocks per day */

    /* Check for large USERSTORE_TOKENPERM (security cache) */
    INSERT INTO
        #results
    (
        check_id,
        priority,
        category,
        finding,
        details,
        url
    )
    SELECT
        check_id = 4104,
        priority =
            CASE
                WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) > 5
                THEN 20 /* High: >5GB security cache is severe */
                WHEN CONVERT(decimal(10, 2), (domc.pages_kb / 1024.0 / 1024.0)) > 2
                THEN 30 /* Medium: 2-5GB security cache */
                ELSE 40 /* Low: 1-2GB security cache */
            END,
        category = N'Memory Usage',
        finding = N'Large Security Token Cache',
        details =
            N'TokenAndPermUserStore cache size is ' +
            CONVERT(nvarchar(20), CONVERT(decimal(10, 2), ISNULL(domc.pages_kb, 0) / 1024.0 / 1024.0)) +
            N' GB. Large security caches can consume significant memory and may indicate security-related issues ' +
            N'such as excessive application role usage or frequent permission changes. ' +
            N'Consider using dbo.ClearTokenPerm stored procedure to manage this issue.',
        url = N'https://erikdarling.com/sp_perfcheck/#SecurityToken'
    FROM sys.dm_os_memory_clerks AS domc
    WHERE domc.type = N'USERSTORE_TOKENPERM'
    AND   domc.name = N'TokenAndPermUserStore'
    AND   domc.pages_kb >= 1048576; /* Only if bigger than 1 GB */

    /* Get physical memory for LPIM check */
    SELECT
        @physical_memory_gb =
            CONVERT
            (
                decimal(10, 2),
                osi.physical_memory_kb / 1024.0 / 1024.0
            )
    FROM sys.dm_os_sys_info AS osi;
    END;

    /* Check if Lock Pages in Memory is enabled (on-prem and managed instances only) */
    IF  @azure_sql_db = 0
    AND @has_view_server_state = 1
    BEGIN
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 4105,
            priority = 40, /* Low: best practice */
            category = N'Memory Configuration',
            finding = N'Lock Pages in Memory Not Enabled',
            details =
                N'SQL Server is not using locked pages in memory (LPIM). This can lead to Windows ' +
                N'taking memory away from SQL Server under memory pressure, causing performance issues. ' +
                N'For production SQL Servers with more than 64GB of memory, LPIM should be enabled.',
            url = N'https://erikdarling.com/sp_perfcheck/#LPIM'
        FROM sys.dm_os_sys_info AS osi
        WHERE osi.sql_memory_model_desc = N'CONVENTIONAL' /* Conventional means not using LPIM */
        AND   @physical_memory_gb >= 32 /* Only recommend for servers with >=32GB RAM */;

        INSERT
            #server_info
        (
            info_type,
            value
        )
        SELECT
            N'Memory Model',
            osi.sql_memory_model_desc
        FROM sys.dm_os_sys_info AS osi;
    END;

    /* Check if Instant File Initialization is enabled (on-prem only) */
    IF  @azure_sql_db = 0
    AND @azure_managed_instance = 0
    AND @aws_rds = 0
    AND @has_view_server_state = 1
    BEGIN
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        SELECT
            N'Instant File Initialization',
            CASE
                WHEN dss.instant_file_initialization_enabled = N'Y'
                THEN N'Enabled'
                ELSE N'Disabled'
            END
        FROM sys.dm_server_services AS dss
        WHERE dss.filename LIKE N'%sqlservr.exe%'
        AND   dss.servicename LIKE N'SQL Server%';

        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT TOP (1)
            check_id = 4106,
            priority = 40, /* Low: best practice */
            category = N'Storage Configuration',
            finding = N'Instant File Initialization Disabled',
            details =
                N'Instant File Initialization is not enabled. This can significantly slow down database file ' +
                N'creation and growth operations, as SQL Server must zero out data files before using them. ' +
                N'Enable this feature by granting the "Perform Volume Maintenance Tasks" permission to the SQL Server service account.',
            url = N'https://erikdarling.com/sp_perfcheck/#IFI'
        FROM sys.dm_server_services AS dss
        WHERE dss.filename LIKE N'%sqlservr.exe%'
        AND   dss.servicename LIKE N'SQL Server%'
        AND   dss.instant_file_initialization_enabled = N'N';
    END;

    /* Check if Resource Governor is enabled, leaving this check open for all versions */
    IF @has_view_server_state = 1
    BEGIN
        /* First, add Resource Governor status to server info */
        IF EXISTS (SELECT 1/0 FROM sys.resource_governor_configuration AS rgc WHERE rgc.is_enabled = 1)
        BEGIN
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            SELECT
                N'Resource Governor',
                N'Enabled';

            /* Add informational message about Resource Governor with query suggestion */
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            SELECT
                check_id = 4107,
                priority = 50, /* Informational: may be intentional */
                category = N'Resource Governor',
                finding = N'Resource Governor Enabled',
                details =
                    N'Resource Governor is enabled on this instance. This affects workload resource allocation and may ' +
                    N'impact performance by limiting resources available to various workloads. ' +
                    N'For more details, run these queries to explore your configuration:' + NCHAR(13) + NCHAR(10) +
                    N'/* Resource Governor configuration */' + NCHAR(13) + NCHAR(10) +
                    N'SELECT c.* FROM sys.resource_governor_configuration AS c;' + NCHAR(13) + NCHAR(10) +
                    N'/* Resource pools and their settings */' + NCHAR(13) + NCHAR(10) +
                    N'SELECT p.* FROM sys.dm_resource_governor_resource_pools AS p;' + NCHAR(13) + NCHAR(10) +
                    N'/* Workload groups and their settings */' + NCHAR(13) + NCHAR(10) +
                    N'SELECT wg.* FROM sys.dm_resource_governor_workload_groups AS wg;' + NCHAR(13) + NCHAR(10) +
                    N'/* Classifier function (if configured) */' + NCHAR(13) + NCHAR(10) +
                    N'SELECT cf.* FROM sys.resource_governor_configuration AS gc' + NCHAR(13) + NCHAR(10) +
                    N'CROSS APPLY (SELECT OBJECT_NAME(gc.classifier_function_id) AS classifier_function_name) AS cf;',
                url = N'https://erikdarling.com/sp_perfcheck/#ResourceGovernor'
            FROM sys.resource_governor_configuration AS rgc
            WHERE rgc.is_enabled = 1;
        END
        ELSE
        BEGIN
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            SELECT
                N'Resource Governor',
                N'Disabled';
        END;
    END;

    /* Check for globally enabled trace flags (not in Azure) */
    IF  @azure_sql_db = 0
    AND @azure_managed_instance = 0
    AND @aws_rds = 0
    BEGIN
        /* Capture trace flags */
        BEGIN TRY
            INSERT INTO
                #trace_flags
            (
                trace_flag,
                status,
                global,
                session
            )
            EXECUTE sys.sp_executesql
                N'DBCC TRACESTATUS WITH NO_INFOMSGS';
        END TRY
        BEGIN CATCH
            IF @debug = 1
            BEGIN
                SET @error_message = N'Error capturing trace flags: ' + ERROR_MESSAGE();
                PRINT @error_message;
            END;

            /* Log error in results */
            INSERT INTO #results
            (
                check_id,
                priority,
                category,
                finding,
                details
            )
            VALUES
            (
                9998,
                90, /* Low priority informational */
                N'Errors',
                N'Error Capturing Trace Flags',
                N'Unable to capture trace flags: ' + ERROR_MESSAGE()
            );
        END CATCH;

        /* Add trace flags to server info */
        IF EXISTS (SELECT 1/0 FROM #trace_flags AS tf WHERE tf.global = 1)
        BEGIN
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            SELECT
                N'Global Trace Flags',
                STUFF
                (
                    (
                        SELECT
                            N', ' +
                            CONVERT(varchar(10), tf.trace_flag)
                        FROM #trace_flags AS tf
                        WHERE tf.global = 1
                        ORDER BY
                            tf.trace_flag
                        FOR
                            XML
                            PATH('')
                    ),
                    1,
                    2,
                    N''
                );
        END;
    END;

    /* Memory information - works on all platforms */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    SELECT
        N'Memory',
        N'Total: ' +
        CONVERT
        (
            nvarchar(20),
            CONVERT
            (
                decimal(10, 2),
                osi.physical_memory_kb / 1024.0 / 1024.0
            )
        ) +
        N' GB, ' +
        N'Target: ' +
        CONVERT
        (
            nvarchar(20),
            CONVERT
            (
                decimal(10, 2),
                osi.committed_target_kb / 1024.0 / 1024.0
            )
        ) +
        N' GB'
    FROM sys.dm_os_sys_info AS osi;

    /* Check for important events in default trace (Windows only for now) */
    IF  @azure_sql_db = 0
    BEGIN
        /* Get default trace path */
        BEGIN TRY
            SELECT
                @trace_path =
                    REVERSE
                    (
                        SUBSTRING
                        (
                            REVERSE(t.path),
                            CHARINDEX
                            (
                                CHAR(92),
                                REVERSE(t.path)
                            ),
                            260
                        )
                    ) + N'log.trc'
            FROM sys.traces AS t
            WHERE t.is_default = 1;
        END TRY
        BEGIN CATCH
            SET @trace_path = NULL;

            INSERT
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            VALUES
            (
                5000,
                50,
                N'Default Trace Permissions',
                N'Inadequate permissions',
                N'N/A',
                N'System Trace',
                N'Access to sys.traces is only available to accounts with elevated privileges, or when explicitly granted',
                N'GRANT ALTER TRACE TO ' +
                SUSER_NAME() +
                N';'
            );
        END CATCH;

        IF @trace_path IS NOT NULL
        BEGIN
            /* Insert common event classes we're interested in */
            INSERT INTO
                #event_class_map
            (
                event_class,
                event_name,
                category_name
            )
            VALUES
                (92,  N'Data File Auto Grow',   N'Database'),
                (93,  N'Log File Auto Grow',    N'Database'),
                (94,  N'Data File Auto Shrink', N'Database'),
                (95,  N'Log File Auto Shrink',  N'Database'),
                (116, N'DBCC Event',            N'Database'),
                (137, N'Server Memory Change',  N'Server');

            /* Get relevant events from default trace */
            INSERT INTO
                #trace_events
            (
                event_time,
                event_class,
                event_subclass,
                database_name,
                database_id,
                file_name,
                object_name,
                object_type,
                duration_ms,
                severity,
                success,
                error,
                text_data,
                file_growth,
                is_auto,
                spid
            )
            SELECT
                event_time = t.StartTime,
                event_class = t.EventClass,
                event_subclass = t.EventSubClass,
                database_name = DB_NAME(t.DatabaseID),
                database_id = t.DatabaseID,
                file_name = t.FileName,
                object_name = t.ObjectName,
                object_type = t.ObjectType,
                duration_ms = t.Duration / 1000, /* Duration is in microseconds, convert to ms */
                severity = t.Severity,
                success = t.Success,
                error = t.Error,
                text_data = t.TextData,
                file_growth = t.IntegerData, /* Size of growth in Data/Log Auto Grow event */
                is_auto = t.IsSystem,
                spid = t.SPID
            FROM sys.fn_trace_gettable(@trace_path, DEFAULT) AS t
            WHERE
            (
                /* Auto-grow and auto-shrink events */
                t.EventClass IN (92, 93, 94, 95)
                /* DBCC Events */
                OR
                (
                      t.EventClass = 116
                  AND
                  (
                         t.TextData LIKE N'%FREEPROCCACHE%'
                      OR t.TextData LIKE N'%FREESYSTEMCACHE%'
                      OR t.TextData LIKE N'%DROPCLEANBUFFERS%'
                      OR t.TextData LIKE N'%SHRINKDATABASE%'
                      OR t.TextData LIKE N'%SHRINKFILE%'
                      OR t.TextData LIKE N'%WRITEPAGE%'
                  )
                )
                /* Server memory change events */
                OR t.EventClass = 137
                /* Deadlock events - typically not in default trace but including for completeness */
                OR t.EventClass = 148
            )
            /* Look back at the past 7 days of events at most */
            AND t.StartTime > DATEADD(DAY, -7, SYSDATETIME());

            /* Update event names from map */
            UPDATE
                te
            SET
                te.event_name = m.event_name,
                te.category_name = m.category_name
            FROM #trace_events AS te
            JOIN #event_class_map AS m
              ON te.event_class = m.event_class;

            /* Check for slow autogrow events */
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            SELECT TOP (10)
                check_id = 5001,
                priority =
                    CASE
                        WHEN te.event_class = 93
                        THEN 30 /* Medium: log file autogrow (zeroing required) */
                        ELSE 40 /* Low: data file autogrow */
                    END,
                category = N'Database File Configuration',
                finding =
                    CASE
                        WHEN te.event_class = 92
                        THEN N'Slow Data File Auto Grow'
                        WHEN te.event_class = 93
                        THEN N'Slow Log File Auto Grow'
                        ELSE N'Slow File Auto Grow'
                    END,
                database_name = te.database_name,
                object_name = te.file_name,
                details =
                    N'Auto grow operation took ' +
                    CONVERT(nvarchar(20), te.duration_ms) +
                    N' ms (' +
                    CONVERT(nvarchar(20), te.duration_ms / 1000.0) +
                    N' seconds) on ' +
                    CONVERT(nvarchar(30), te.event_time, 120) +
                    N'. ' +
                    N'Growth amount: ' +
                    CONVERT(nvarchar(20), CONVERT(decimal(18,2), te.file_growth * 8.0 / 1024.0)) +
                    N' MB. ',
                url = N'https://erikdarling.com/sp_perfcheck/#AutoGrowth'
            FROM #trace_events AS te
            WHERE te.event_class IN (92, 93) /* Auto-grow events */
            AND   te.duration_ms > @slow_autogrow_ms
            ORDER BY
                te.duration_ms DESC;

            /* Check for auto-shrink events */
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            SELECT TOP (10)
                check_id = 5002,
                priority = 40, /* Low: harmful config executing */
                category = N'Database File Configuration',
                finding =
                    CASE
                        WHEN te.event_class = 94
                        THEN N'Data File Auto Shrink'
                        WHEN te.event_class = 95
                        THEN N'Log File Auto Shrink'
                        ELSE N'File Auto Shrink'
                    END,
                database_name = te.database_name,
                object_name = te.file_name,
                details =
                    N'Auto shrink operation occurred on ' +
                    CONVERT(nvarchar(30), te.event_time, 120) +
                    N'. ' +
                    N'Auto-shrink is generally not recommended as it can lead to file fragmentation and ' +
                    N'repeated grow/shrink cycles. Consider disabling auto-shrink on this database.',
                url = N'https://erikdarling.com/sp_perfcheck/#AutoShrink'
            FROM #trace_events AS te
            WHERE te.event_class IN (94, 95) /* Auto-shrink events */
            ORDER BY
                te.event_time DESC;

            /* Check for potentially problematic DBCC commands - group by command type */
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                details,
                url
            )
            SELECT TOP (10)
                5003,
                priority =
                    CASE
                        WHEN dbcc_cmd.dbcc_pattern LIKE N'%FREEPROCCACHE%'
                        OR   dbcc_cmd.dbcc_pattern LIKE N'%FREESYSTEMCACHE%'
                        OR   dbcc_cmd.dbcc_pattern LIKE N'%DROPCLEANBUFFERS%'
                        OR   dbcc_cmd.dbcc_pattern LIKE N'%WRITEPAGE%'
                        THEN 30 /* Medium: destructive DBCC commands */
                        ELSE 50 /* Informational: other DBCC commands */
                    END,
                N'System Management',
                N'Potentially Disruptive DBCC Commands',
                MAX(te.database_name),
                N'Found ' +
                CONVERT(nvarchar(20), COUNT_BIG(*)) +
                N' instances of "' +
                CASE
                    WHEN te.text_data LIKE N'%FREEPROCCACHE%' THEN N'DBCC FREEPROCCACHE'
                    WHEN te.text_data LIKE N'%FREESYSTEMCACHE%' THEN N'DBCC FREESYSTEMCACHE'
                    WHEN te.text_data LIKE N'%DROPCLEANBUFFERS%' THEN N'DBCC DROPCLEANBUFFERS'
                    WHEN te.text_data LIKE N'%SHRINKDATABASE%' THEN N'DBCC SHRINKDATABASE'
                    WHEN te.text_data LIKE N'%SHRINKFILE%' THEN N'DBCC SHRINKFILE'
                    WHEN te.text_data LIKE N'%WRITEPAGE%' THEN N'DBCC WRITEPAGE'
                    ELSE LEFT(te.text_data, 40) /* Take first 40 chars for other commands just in case */
                END +
                N'" between ' +
                CONVERT(nvarchar(30), MIN(te.event_time), 120) +
                N' and ' +
                CONVERT(nvarchar(30), MAX(te.event_time), 120) +
                N'. These commands can impact server performance or database integrity. ' +
                N'Review why these commands are being executed, especially if on a production system.',
                N'https://erikdarling.com/sp_perfcheck/#DisruptiveDBCC'
            FROM #trace_events AS te
            CROSS APPLY
            (
                SELECT dbcc_pattern =
                    CASE
                        WHEN te.text_data LIKE N'%FREEPROCCACHE%' THEN N'DBCC FREEPROCCACHE'
                        WHEN te.text_data LIKE N'%FREESYSTEMCACHE%' THEN N'DBCC FREESYSTEMCACHE'
                        WHEN te.text_data LIKE N'%DROPCLEANBUFFERS%' THEN N'DBCC DROPCLEANBUFFERS'
                        WHEN te.text_data LIKE N'%SHRINKDATABASE%' THEN N'DBCC SHRINKDATABASE'
                        WHEN te.text_data LIKE N'%SHRINKFILE%' THEN N'DBCC SHRINKFILE'
                        WHEN te.text_data LIKE N'%WRITEPAGE%' THEN N'DBCC WRITEPAGE'
                        ELSE LEFT(te.text_data, 40) /* Take first 40 chars for other commands just in case*/
                    END
            ) AS dbcc_cmd
            WHERE te.event_class = 116 /* DBCC events */
            AND   te.text_data IS NOT NULL
            GROUP BY
                dbcc_cmd.dbcc_pattern,
                CASE
                    WHEN te.text_data LIKE N'%FREEPROCCACHE%' THEN N'DBCC FREEPROCCACHE'
                    WHEN te.text_data LIKE N'%FREESYSTEMCACHE%' THEN N'DBCC FREESYSTEMCACHE'
                    WHEN te.text_data LIKE N'%DROPCLEANBUFFERS%' THEN N'DBCC DROPCLEANBUFFERS'
                    WHEN te.text_data LIKE N'%SHRINKDATABASE%' THEN N'DBCC SHRINKDATABASE'
                    WHEN te.text_data LIKE N'%SHRINKFILE%' THEN N'DBCC SHRINKFILE'
                    WHEN te.text_data LIKE N'%WRITEPAGE%' THEN N'DBCC WRITEPAGE'
                    ELSE LEFT(te.text_data, 40) /* Take first 40 chars for other commands just i case*/
                END
            ORDER BY
                COUNT_BIG(*) DESC;

            /* Get summary of SLOW autogrow events for server_info */
            /* Data file autogrows */
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            SELECT
                info_type = N'Slow Data File Autogrows (7 days)',
                value =
                    CONVERT
                    (
                        nvarchar(50),
                        COUNT_BIG(*)
                    ) +
                    N' events (avg ' +
                    CONVERT
                    (
                        nvarchar(20),
                        AVG(te.duration_ms) / 1000.0
                    ) +
                    N' sec)'
            FROM #trace_events AS te
            WHERE te.event_class = 92 /* Data file auto-grow */
            AND   te.duration_ms > @slow_autogrow_ms /* Only slow auto-grows */
            HAVING
                COUNT_BIG(*) > 0;

            /* Log file autogrows */
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            SELECT
                info_type = N'Slow Log File Autogrows (7 days)',
                value =
                    CONVERT
                    (
                        nvarchar(50),
                        COUNT_BIG(*)
                    ) +
                    N' events (avg ' +
                    CONVERT
                    (
                        nvarchar(20),
                        AVG(te.duration_ms) / 1000.0
                    ) +
                    N' sec)'
            FROM #trace_events AS te
            WHERE te.event_class = 93 /* Log file auto-grow */
            AND   te.duration_ms > @slow_autogrow_ms /* Only slow auto-grows */
            HAVING
                COUNT_BIG(*) > 0;
        END;
    END;

    /* Check for significant wait stats */
    IF @has_view_server_state = 1
    BEGIN
        /* Get uptime */
        SELECT
            @uptime_ms =
                CASE
                    WHEN DATEDIFF(DAY, osi.sqlserver_start_time, SYSDATETIME()) >= 24
                    THEN DATEDIFF(SECOND, osi.sqlserver_start_time, SYSDATETIME()) * 1000.
                    ELSE DATEDIFF(MILLISECOND, osi.sqlserver_start_time, SYSDATETIME())
                END
        FROM sys.dm_os_sys_info AS osi;

        /* Get total wait time */
        SELECT
            @total_waits =
                SUM
                (
                    CONVERT
                    (
                        bigint,
                        osw.wait_time_ms
                    )
                )
        FROM sys.dm_os_wait_stats AS osw
        WHERE NOT EXISTS
        (
            SELECT
                1/0
            FROM #benign_waits AS bw
            WHERE bw.wait_type = osw.wait_type
        );

        /* Insert important waits into the temp table */
        INSERT INTO
            #wait_stats
        (
            wait_type,
            description,
            wait_time_ms,
            waiting_tasks_count,
            signal_wait_time_ms,
            percentage,
            category
        )
        SELECT
            dows.wait_type,
            description =
                CASE
                    WHEN dows.wait_type = N'PAGEIOLATCH_SH'
                    THEN N'Selects reading pages from disk into memory'
                    WHEN dows.wait_type = N'PAGEIOLATCH_EX'
                    THEN N'Modifications reading pages from disk into memory'
                    WHEN dows.wait_type = N'RESOURCE_SEMAPHORE'
                    THEN N'Queries waiting to get memory to run'
                    WHEN dows.wait_type = N'RESOURCE_SEMAPHORE_QUERY_COMPILE'
                    THEN N'Queries waiting to get memory to compile'
                    WHEN dows.wait_type = N'CXPACKET'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXCONSUMER'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXSYNC_PORT'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXSYNC_CONSUMER'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'SOS_SCHEDULER_YIELD'
                    THEN N'Query scheduling'
                    WHEN dows.wait_type = N'THREADPOOL'
                    THEN N'Potential worker thread exhaustion'
                    WHEN dows.wait_type = N'RESOURCE_GOVERNOR_IDLE'
                    THEN N'Potential CPU cap waits'
                    WHEN dows.wait_type = N'CMEMTHREAD'
                    THEN N'Tasks waiting on memory objects'
                    WHEN dows.wait_type = N'PAGELATCH_EX'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type = N'PAGELATCH_SH'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type = N'PAGELATCH_UP'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type LIKE N'LCK%'
                    THEN N'Queries waiting to acquire locks'
                    WHEN dows.wait_type = N'WRITELOG'
                    THEN N'Transaction Log writes'
                    WHEN dows.wait_type = N'LOGBUFFER'
                    THEN N'Transaction Log buffering'
                    WHEN dows.wait_type = N'LOG_RATE_GOVERNOR'
                    THEN N'Azure Transaction Log throttling'
                    WHEN dows.wait_type = N'POOL_LOG_RATE_GOVERNOR'
                    THEN N'Azure Transaction Log throttling'
                    WHEN dows.wait_type = N'SLEEP_TASK'
                    THEN N'Potential Hash spills'
                    WHEN dows.wait_type = N'BPSORT'
                    THEN N'Potential batch mode sort performance issues'
                    WHEN dows.wait_type = N'EXECSYNC'
                    THEN N'Potential eager index spool creation'
                    WHEN dows.wait_type = N'IO_COMPLETION'
                    THEN N'Potential sort spills'
                    WHEN dows.wait_type = N'ASYNC_NETWORK_IO'
                    THEN N'Potential client issues'
                    WHEN dows.wait_type = N'SLEEP_BPOOL_STEAL'
                    THEN N'Potential buffer pool pressure'
                    WHEN dows.wait_type = N'PWAIT_QRY_BPMEMORY'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTREPARTITION'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTBUILD'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTMEMO'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTDELETE'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTREINIT'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'BTREE_INSERT_FLOW_CONTROL'
                    THEN N'Optimize For Sequential Key'
                    WHEN dows.wait_type = N'HADR_SYNC_COMMIT'
                    THEN N'Potential Availability Group Issues'
                    WHEN dows.wait_type = N'HADR_GROUP_COMMIT'
                    THEN N'Potential Availability Group Issues'
                    WHEN dows.wait_type = N'WAIT_ON_SYNC_STATISTICS_REFRESH'
                    THEN N'Waiting on sync stats updates (compilation)'
                    WHEN dows.wait_type = N'IO_QUEUE_LIMIT'
                    THEN N'Azure SQLDB Throttling'
                    WHEN dows.wait_type = N'IO_RETRY'
                    THEN N'I/O Failures retried'
                    WHEN dows.wait_type = N'RESMGR_THROTTLED'
                    THEN N'Azure SQLDB Throttling'
                    ELSE N'Other significant wait type'
                END,
            wait_time_ms = dows.wait_time_ms,
            waiting_tasks_count = dows.waiting_tasks_count,
            signal_wait_time_ms = dows.signal_wait_time_ms,
            percentage =
                CONVERT
                (
                    decimal(5,2),
                    dows.wait_time_ms * 100.0 / NULLIF(@total_waits, 0)
                ),
            category =
                CASE
                    WHEN dows.wait_type IN (N'PAGEIOLATCH_SH', N'PAGEIOLATCH_EX', N'IO_COMPLETION', N'IO_RETRY')
                    THEN N'I/O'
                    WHEN dows.wait_type IN (N'RESOURCE_SEMAPHORE', N'RESOURCE_SEMAPHORE_QUERY_COMPILE', N'CMEMTHREAD', N'SLEEP_BPOOL_STEAL')
                    THEN N'Memory'
                    WHEN dows.wait_type IN (N'CXPACKET', N'CXCONSUMER', N'CXSYNC_PORT', N'CXSYNC_CONSUMER')
                    THEN N'Parallelism'
                    WHEN dows.wait_type IN (N'SOS_SCHEDULER_YIELD', N'THREADPOOL', N'RESOURCE_GOVERNOR_IDLE')
                    THEN N'CPU'
                    WHEN dows.wait_type IN (N'PAGELATCH_EX', N'PAGELATCH_SH', N'PAGELATCH_UP')
                    THEN N'TempDB Contention'
                    WHEN dows.wait_type LIKE N'LCK%'
                    THEN N'Locking'
                    WHEN dows.wait_type IN (N'WRITELOG', N'LOGBUFFER', N'LOG_RATE_GOVERNOR', N'POOL_LOG_RATE_GOVERNOR')
                    THEN N'Transaction Log'
                    WHEN dows.wait_type IN (N'SLEEP_TASK', N'BPSORT', N'PWAIT_QRY_BPMEMORY', N'HTREPARTITION', N'HTBUILD', N'HTMEMO', N'HTDELETE', N'HTREINIT')
                    THEN N'Query Execution'
                    WHEN dows.wait_type = N'ASYNC_NETWORK_IO'
                    THEN N'Network'
                    WHEN dows.wait_type IN (N'HADR_SYNC_COMMIT', N'HADR_GROUP_COMMIT')
                    THEN N'Availability Groups'
                    WHEN dows.wait_type IN (N'IO_QUEUE_LIMIT', N'RESMGR_THROTTLED')
                    THEN N'Azure SQL Throttling'
                    WHEN dows.wait_type = N'BTREE_INSERT_FLOW_CONTROL'
                    THEN N'Index Management'
                    WHEN dows.wait_type = N'WAIT_ON_SYNC_STATISTICS_REFRESH'
                    THEN N'Statistics'
                    ELSE N'Other'
                END
        FROM sys.dm_os_wait_stats AS dows
        WHERE
        /* Only include specific wait types identified as important */
        (
               dows.wait_type = N'PAGEIOLATCH_SH'
            OR dows.wait_type = N'PAGEIOLATCH_EX'
            OR dows.wait_type = N'RESOURCE_SEMAPHORE'
            OR dows.wait_type = N'RESOURCE_SEMAPHORE_QUERY_COMPILE'
            OR dows.wait_type = N'CXPACKET'
            OR dows.wait_type = N'CXCONSUMER'
            OR dows.wait_type = N'CXSYNC_PORT'
            OR dows.wait_type = N'CXSYNC_CONSUMER'
            OR dows.wait_type = N'SOS_SCHEDULER_YIELD'
            OR dows.wait_type = N'THREADPOOL'
            OR dows.wait_type = N'RESOURCE_GOVERNOR_IDLE'
            OR dows.wait_type = N'CMEMTHREAD'
            OR dows.wait_type = N'PAGELATCH_EX'
            OR dows.wait_type = N'PAGELATCH_SH'
            OR dows.wait_type = N'PAGELATCH_UP'
            OR dows.wait_type LIKE N'LCK%'
            OR dows.wait_type = N'WRITELOG'
            OR dows.wait_type = N'LOGBUFFER'
            OR dows.wait_type = N'LOG_RATE_GOVERNOR'
            OR dows.wait_type = N'POOL_LOG_RATE_GOVERNOR'
            OR dows.wait_type = N'SLEEP_TASK'
            OR dows.wait_type = N'BPSORT'
            OR dows.wait_type = N'EXECSYNC'
            OR dows.wait_type = N'IO_COMPLETION'
            OR dows.wait_type = N'ASYNC_NETWORK_IO'
            OR dows.wait_type = N'SLEEP_BPOOL_STEAL'
            OR dows.wait_type = N'PWAIT_QRY_BPMEMORY'
            OR dows.wait_type = N'HTREPARTITION'
            OR dows.wait_type = N'HTBUILD'
            OR dows.wait_type = N'HTMEMO'
            OR dows.wait_type = N'HTDELETE'
            OR dows.wait_type = N'HTREINIT'
            OR dows.wait_type = N'BTREE_INSERT_FLOW_CONTROL'
            OR dows.wait_type = N'HADR_SYNC_COMMIT'
            OR dows.wait_type = N'HADR_GROUP_COMMIT'
            OR dows.wait_type = N'WAIT_ON_SYNC_STATISTICS_REFRESH'
            OR dows.wait_type = N'IO_QUEUE_LIMIT'
            OR dows.wait_type = N'IO_RETRY'
            OR dows.wait_type = N'RESMGR_THROTTLED'
        )
        /* Only include waits that are significant in terms of percentage of uptime or average wait time (>1 second) */
        AND
        (
             (dows.wait_time_ms * 100.0 / NULLIF(@uptime_ms, 0)) > @significant_wait_threshold_pct
          OR (dows.wait_time_ms * 1.0 / NULLIF(dows.waiting_tasks_count, 0)) > 1000.0 /* Average wait time > 1 second */
        );

        /* Calculate wait time as percentage of uptime */
        UPDATE
            #wait_stats
        SET
            #wait_stats.wait_time_percent_of_uptime =
                (wait_time_ms * 100.0 / NULLIF(@uptime_ms, 0));

        /* Add only waits that represent >=10% of server uptime */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT TOP (10) /* Limit to top 10 most significant waits */
            6001,
            priority =
                CASE
                    WHEN ws.wait_time_percent_of_uptime > 100
                    THEN 20 /* High: >100% of uptime */
                    WHEN ws.wait_time_percent_of_uptime > 75
                    THEN 20 /* High: >75% of uptime */
                    WHEN ws.wait_time_percent_of_uptime >= 50
                    THEN 30 /* Medium: >=50% of uptime */
                    ELSE 40 /* Low: >=10% of uptime */
                END,
            category = N'Wait Statistics',
            finding =
                N'High Impact Wait Type: ' +
                ws.wait_type +
                N' (' +
                ws.category +
                N')',
            details =
                N'Wait type: ' +
                ws.wait_type +
                N' represents ' +
                CONVERT(nvarchar(10), CONVERT(decimal(10, 2), ws.wait_time_percent_of_uptime)) +
                N'% of server uptime (' +
                CONVERT(nvarchar(20), CONVERT(decimal(10, 2), ws.wait_time_minutes)) +
                N' minutes). ' +
                N'Average wait: ' +
                CONVERT(nvarchar(10), CONVERT(decimal(10, 2), ws.avg_wait_ms)) +
                N' ms per wait. ' +
                N'Description: ' +
                ws.description,
            url = N'https://erikdarling.com/sp_perfcheck/#WaitStats'
        FROM #wait_stats AS ws
        WHERE ws.wait_time_percent_of_uptime >= 10.0 /* Only include waits that are at least 10% of uptime */
        AND   ws.wait_type <> N'SLEEP_TASK'
        ORDER BY
            ws.wait_time_percent_of_uptime DESC;
    END;

    /* Calculate pagelatch wait time for TempDB contention check */
    /* Check for CPU scheduling pressure (signal wait ratio) */
    /* Check for stolen memory from buffer pool */
    IF @has_view_server_state = 1
    BEGIN
        /* Calculate pagelatch wait time for TempDB contention check */
        SELECT
            @pagelatch_wait_hours =
                SUM
                (
                    CASE
                        WHEN osw.wait_type IN (N'PAGELATCH_UP', N'PAGELATCH_SH', N'PAGELATCH_EX')
                        THEN osw.wait_time_ms / 1000.0 / 3600.0
                        ELSE 0
                    END
                ),
            @server_uptime_hours =
                DATEDIFF(SECOND, osi.sqlserver_start_time, SYSDATETIME()) / 3600.0
        FROM sys.dm_os_wait_stats AS osw
        CROSS JOIN sys.dm_os_sys_info AS osi
        GROUP BY
            DATEDIFF(SECOND, osi.sqlserver_start_time, SYSDATETIME()) / 3600.0;

        SET @pagelatch_ratio_to_uptime =
            @pagelatch_wait_hours / NULLIF(@server_uptime_hours, 0) * 100;

        /* Check for CPU scheduling pressure (signal wait ratio) */
        /* Get total and signal wait times */
        SELECT
            @signal_wait_time_ms =
                SUM(CONVERT(bigint, osw.signal_wait_time_ms)),
            @total_wait_time_ms =
                SUM(CONVERT(bigint, osw.wait_time_ms)),
            @sos_scheduler_yield_ms =
                SUM
                (
                    CASE
                        WHEN osw.wait_type = N'SOS_SCHEDULER_YIELD'
                        THEN CONVERT(bigint, osw.wait_time_ms)
                        ELSE CONVERT(bigint, 0)
                    END
                )
        FROM sys.dm_os_wait_stats AS osw
        WHERE NOT EXISTS
        (
            SELECT
                1/0
            FROM #benign_waits AS bw
            WHERE bw.wait_type = osw.wait_type
        );

        /* Calculate signal wait ratio (time spent waiting for CPU vs. total wait time) */
        IF @total_wait_time_ms > 0
        BEGIN
            SET @signal_wait_ratio =
                    (@signal_wait_time_ms * 100.0) / @total_wait_time_ms;

            /* Calculate SOS_SCHEDULER_YIELD percentage of uptime */
            IF  @uptime_ms > 0
            AND @sos_scheduler_yield_ms > 0
            BEGIN
                SET @sos_scheduler_yield_pct_of_uptime =
                        (@sos_scheduler_yield_ms * 100.0) / @uptime_ms;
            END;

            /* Add CPU scheduling info to server_info */
            INSERT INTO
                #server_info
             (
                 info_type,
                 value
             )
            VALUES
            (
                 N'Signal Wait Ratio',
                 CONVERT(nvarchar(10), CONVERT(decimal(10, 2), @signal_wait_ratio)) +
                 N'%' +
                 CASE
                     WHEN @signal_wait_ratio >= 50.0
                     THEN N' (High - CPU pressure detected)'
                     WHEN @signal_wait_ratio >= 25.0
                     THEN N' (Moderate - CPU pressure likely)'
                     ELSE N' (Normal)'
                 END
            );

            IF @sos_scheduler_yield_pct_of_uptime >= 10.0
            BEGIN
                INSERT INTO
                    #server_info
                (
                    info_type,
                    value
                )
                VALUES
                (
                    N'SOS_SCHEDULER_YIELD',
                    CONVERT
                    (
                        nvarchar(10),
                        CONVERT
                        (
                            decimal(10, 2),
                            @sos_scheduler_yield_pct_of_uptime
                        )
                    ) +
                    N'% of server uptime'
                );
            END;

            /* Add finding if signal wait ratio exceeds threshold */
            IF @signal_wait_ratio >= 25.0
            BEGIN
                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    details,
                    url
                )
                VALUES
                (
                    6101,
                    CASE
                        WHEN @signal_wait_ratio >= 50.0
                        THEN 20 /* High: >=50% signal waits */
                        WHEN @signal_wait_ratio >= 30.0
                        THEN 30 /* Medium: >=30% signal waits */
                        ELSE 40 /* Low: notable signal waits */
                    END,
                    N'CPU Scheduling',
                    N'High Signal Wait Ratio',
                    N'Signal wait ratio is ' +
                    CONVERT(nvarchar(10), CONVERT(decimal(10, 2), @signal_wait_ratio)) +
                    N'%. This indicates significant CPU scheduling pressure. ' +
                    N'Processes are waiting to get scheduled on the CPU, which can impact query performance. ' +
                    N'Consider investigating high-CPU queries, reducing server load, or adding CPU resources.',
                    N'https://erikdarling.com/sp_perfcheck/#CPUPressure'
                );
            END;

            /* Add finding for significant SOS_SCHEDULER_YIELD waits */
            IF @sos_scheduler_yield_pct_of_uptime >= 25.0
            BEGIN
                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    details,
                    url
                )
                VALUES
                (
                    6102,
                    CASE
                        WHEN @sos_scheduler_yield_pct_of_uptime >= 50.0
                        THEN 20 /* High: >=50% of uptime in scheduler yields */
                        WHEN @sos_scheduler_yield_pct_of_uptime >= 30.0
                        THEN 30 /* Medium: >=30% of uptime */
                        ELSE 40 /* Low: >=25% of uptime */
                    END,
                    N'CPU Scheduling',
                    N'High SOS_SCHEDULER_YIELD Waits',
                    N'SOS_SCHEDULER_YIELD waits account for ' +
                    CONVERT(nvarchar(10), CONVERT(decimal(10, 2), @sos_scheduler_yield_pct_of_uptime)) +
                    N'% of server uptime. This indicates tasks frequently giving up their quantum of CPU time. ' +
                    N'This can be caused by CPU-intensive queries, causing threads to context switch frequently. ' +
                    N'Consider tuning queries with high CPU usage or adding CPU resources.',
                    N'https://erikdarling.com/sp_perfcheck/#CPUPressure'
                );
            END;
        END;

        /* Check for stolen memory from buffer pool */
        /* Get buffer pool size */
        SELECT
            @buffer_pool_size_gb =
                CONVERT
                (
                    decimal(38, 2),
                    SUM(domc.pages_kb) / 1024.0 / 1024.0
                )
        FROM sys.dm_os_memory_clerks AS domc
        WHERE domc.type = N'MEMORYCLERK_SQLBUFFERPOOL';

        /* Get stolen memory */
        SELECT
            @stolen_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    dopc.cntr_value / 1024.0 / 1024.0
                )
        FROM sys.dm_os_performance_counters AS dopc
        WHERE dopc.counter_name LIKE N'Stolen Server%';

        /* Calculate stolen memory percentage */
        IF @buffer_pool_size_gb > 0
        BEGIN
            SET @stolen_memory_pct =
                    (@stolen_memory_gb / (@buffer_pool_size_gb + @stolen_memory_gb)) * 100.0;

            /* Query memory health history if available (SQL Server 2025+) */
            IF @health_history_exists = CONVERT(bit, 'true')
            BEGIN
                EXECUTE sys.sp_executesql
                    N'
                    SELECT
                        @health_history_count = COUNT_BIG(*)
                    FROM sys.dm_os_memory_health_history AS hh
                    WHERE hh.severity_level > 1;
                    ',
                    N'@health_history_count bigint OUTPUT',
                    @health_history_count OUTPUT;
            END;

            /* Add buffer pool info to server_info */
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            VALUES
            (
                N'Buffer Pool Size',
                CONVERT
                (
                    nvarchar(20),
                    @buffer_pool_size_gb
                ) +
                N' GB'
            );

            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            VALUES
            (
                N'Stolen Memory',
                CONVERT
                (
                    nvarchar(20),
                    ISNULL(@stolen_memory_gb, 0)
                ) +
                N' GB (' +
                CONVERT
                (
                    nvarchar(10),
                    CONVERT
                    (
                        decimal(10, 1),
                        ISNULL(@stolen_memory_pct, 0)
                    )
                ) +
                N'%)'
            );

            /* Add memory health history count if available */
            IF @health_history_count > 0
            BEGIN
                INSERT INTO
                    #server_info
                (
                    info_type,
                    value
                )
                VALUES
                (
                    N'Memory Health History (Severity > 1)',
                    CONVERT
                    (
                        nvarchar(20),
                        @health_history_count
                    ) +
                    N' events'
                );
            END;

            /* Add finding if stolen memory exceeds threshold */
            IF @stolen_memory_pct > @stolen_memory_threshold_pct
            BEGIN
                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    details,
                    url
                )
                VALUES
                (
                    6002,
                    CASE
                        WHEN @stolen_memory_pct > 30
                        THEN 20 /* High: actively starving buffer pool */
                        WHEN @stolen_memory_pct > 15
                        THEN 30 /* Medium: significant stolen memory */
                        ELSE 40 /* Low: moderate stolen memory */
                    END,
                    N'Memory Usage',
                    N'High Stolen Memory Percentage',
                    N'Memory stolen from buffer pool: ' +
                    CONVERT(nvarchar(20), ISNULL(@stolen_memory_gb, 0)) +
                    N' GB (' +
                    CONVERT(nvarchar(10), CONVERT(decimal(10, 1), ISNULL(@stolen_memory_pct, 0))) +
                    N'% of total memory). This reduces memory available for data caching and can impact performance. ' +
                    N'Consider investigating memory usage by CLR, extended stored procedures, linked servers, or other memory clerks.',
                    N'https://erikdarling.com/sp_perfcheck/#StolenMemory'
                );

                /* Also add the top 5 non-buffer pool memory consumers for visibility */
                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    details,
                    url
                )
                SELECT TOP (5)
                    check_id = 6003,
                    priority = 50, /* Informational: memory context */
                    category = N'Memory Usage',
                    finding =
                        N'Top Memory Consumer: ' +
                        domc.type,
                    details =
                        N'Memory clerk "' +
                        domc.type +
                        N'" is using ' +
                        CONVERT
                        (
                            nvarchar(20),
                            CONVERT
                            (
                                decimal(38, 2),
                                SUM(domc.pages_kb) / 1024.0 / 1024.0
                            )
                        ) +
                        N' GB of memory. This is one of the top consumers of memory outside the buffer pool.',
                    url = N'https://erikdarling.com/sp_perfcheck/#StolenMemory'
                FROM sys.dm_os_memory_clerks AS domc
                WHERE domc.type <> N'MEMORYCLERK_SQLBUFFERPOOL'
                GROUP BY
                    domc.type
                HAVING
                    SUM(domc.pages_kb) / 1024.0 / 1024.0 >= 1.0 /* Only show clerks using more than 1 GB */
                ORDER BY
                    SUM(domc.pages_kb) DESC;
            END;
        END;

        /* Check for I/O stalls per database */
        /* First clear any existing data */
        TRUNCATE TABLE
            #io_stalls_by_db;

        /* Get database-level I/O stall statistics */
        SET @io_sql = N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        SELECT
            database_name = DB_NAME(fs.database_id),
            database_id = fs.database_id,
            total_io_stall_ms = SUM(fs.io_stall),
            total_io_mb =
                CONVERT(decimal(18, 2), SUM(fs.num_of_bytes_read + fs.num_of_bytes_written) / 1024.0 / 1024.0),
            avg_io_stall_ms =
                CASE
                    WHEN SUM(fs.num_of_reads + fs.num_of_writes) = 0
                    THEN 0
                    ELSE CONVERT(decimal(18, 2), SUM(fs.io_stall) * 1.0 / SUM(fs.num_of_reads + fs.num_of_writes))
                END,
            read_io_stall_ms = SUM(fs.io_stall_read_ms),
            read_io_mb =
                CONVERT(decimal(18, 2), SUM(fs.num_of_bytes_read) / 1024.0 / 1024.0),
            avg_read_stall_ms =
                CASE
                    WHEN SUM(fs.num_of_reads) = 0
                    THEN 0
                    ELSE CONVERT(decimal(18, 2), SUM(fs.io_stall_read_ms) * 1.0 / SUM(fs.num_of_reads))
                END,
            write_io_stall_ms = SUM(fs.io_stall_write_ms),
            write_io_mb =
                CONVERT(decimal(18, 2), SUM(fs.num_of_bytes_written) / 1024.0 / 1024.0),
            avg_write_stall_ms =
                CASE
                    WHEN SUM(fs.num_of_writes) = 0
                    THEN 0
                    ELSE CONVERT(decimal(18, 2), SUM(fs.io_stall_write_ms) * 1.0 / SUM(fs.num_of_writes))
                END,
            total_size_mb = CONVERT(decimal(18, 2), SUM(CONVERT(bigint, mf.size)) * 8.0 / 1024.0)
        FROM sys.dm_io_virtual_file_stats
        (' +
        CASE
            WHEN @azure_sql_db = 1
            THEN N'
            DB_ID()'
            ELSE N'
            NULL'
        END + N',
            NULL
        ) AS fs
        JOIN ' +
        CASE
            WHEN @azure_sql_db = 1
            THEN N'sys.database_files AS mf
          ON  fs.file_id = mf.file_id
          AND fs.database_id = DB_ID()'
            ELSE N'sys.master_files AS mf
          ON  fs.database_id = mf.database_id
          AND fs.file_id = mf.file_id'
        END + N'
        WHERE
        (
            ' +
        CASE
            WHEN @azure_sql_db = 1
            THEN N'1 = 1' /* Always true for Azure SQL DB since we only have the current database */
            ELSE N'fs.database_id > 4
          OR fs.database_id = 2'
        END +
        N'
        ) /* User databases or TempDB */
        GROUP BY
            fs.database_id
        HAVING
            /* Skip idle databases and system databases except tempdb */
            (SUM(fs.num_of_reads + fs.num_of_writes) > 0);';

        IF @debug = 1
        BEGIN
            PRINT @io_sql;
        END;

        BEGIN TRY
            INSERT INTO
                #io_stalls_by_db
            (
                database_name,
                database_id,
                total_io_stall_ms,
                total_io_mb,
                avg_io_stall_ms,
                read_io_stall_ms,
                read_io_mb,
                avg_read_stall_ms,
                write_io_stall_ms,
                write_io_mb,
                avg_write_stall_ms,
                total_size_mb
            )
            EXECUTE sys.sp_executesql
                @io_sql;
        END TRY
        BEGIN CATCH
            IF @debug = 1
            BEGIN
                SET @error_message = N'Error collecting IO stall stats: ' + ERROR_MESSAGE();
                PRINT @error_message;
            END;

            /* Log error in results */
            INSERT INTO #results
            (
                check_id,
                priority,
                category,
                finding,
                details
            )
            VALUES
            (
                9997,
                50, /* Informational: collection error */
                N'Errors',
                N'Error Collecting IO Statistics',
                N'Unable to collect IO stall statistics: ' + ERROR_MESSAGE()
            );
        END CATCH;

        /* Add I/O stall summary to server_info - one line per database */
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        SELECT TOP (5)
            info_type =
                N'Database I/O Stall: ' +
                i.database_name,
            value =
                N'Average latency: ' +
                CONVERT
                (
                    nvarchar(10),
                    CONVERT
                    (
                        decimal(10, 2),
                        i.avg_io_stall_ms
                    )
                ) +
                N' ms (Read: ' +
                CONVERT
                (
                    nvarchar(10),
                    CONVERT
                    (
                        decimal(10, 2),
                        i.avg_read_stall_ms
                    )
                ) +
                N' ms, Write: ' +
                CONVERT
                (
                    nvarchar(10),
                    CONVERT
                    (
                        decimal(10, 2),
                        i.avg_write_stall_ms
                    )
                ) +
                N' ms)'
        FROM #io_stalls_by_db AS i
        WHERE
        (
             i.avg_read_stall_ms >= @slow_read_ms
          OR i.avg_write_stall_ms >= @slow_write_ms
        )
        ORDER BY
            i.avg_io_stall_ms DESC;

        /* Check 6201 (High Database I/O Stalls) removed — duplicated by file-level checks 3001/3002/3003 */
    END;

    /*
    Storage Performance Checks - I/O Latency for database files
    */
    IF @debug = 1
    BEGIN
        RAISERROR('Checking storage performance', 0, 1) WITH NOWAIT;
    END;

    SET @file_io_sql = N'
    SELECT
        database_name = DB_NAME(fs.database_id),
        fs.database_id,
        file_name = mf.name,
        mf.type_desc,
        io_stall_read_ms = fs.io_stall_read_ms,
        num_of_reads = fs.num_of_reads,
        avg_read_latency_ms =
            CASE
                WHEN fs.num_of_reads = 0
                THEN 0
                ELSE fs.io_stall_read_ms * 1.0 / fs.num_of_reads
            END,
        io_stall_write_ms = fs.io_stall_write_ms,
        num_of_writes = fs.num_of_writes,
        avg_write_latency_ms =
            CASE
                WHEN fs.num_of_writes = 0
                THEN 0
                ELSE fs.io_stall_write_ms * 1.0 / fs.num_of_writes
            END,
        io_stall_ms = fs.io_stall,
        total_io = fs.num_of_reads + fs.num_of_writes,
        avg_io_latency_ms =
            CASE
                WHEN (fs.num_of_reads + fs.num_of_writes) = 0
                THEN 0
                ELSE fs.io_stall * 1.0 / (fs.num_of_reads + fs.num_of_writes)
            END,
        size_mb = mf.size * 8.0 / 1024,
        drive_location =
            CASE
                WHEN mf.physical_name LIKE N''http%''
                THEN mf.physical_name
                WHEN mf.physical_name LIKE N''\\\\%''
                THEN N''UNC: '' +
                     SUBSTRING(mf.physical_name, 3, CHARINDEX(N''\\'', mf.physical_name, 3) - 3)
                ELSE UPPER(LEFT(mf.physical_name, 2))
            END,
        physical_name = mf.physical_name
    FROM sys.dm_io_virtual_file_stats
    (' +
    CASE
        WHEN @azure_sql_db = 1
        THEN N'
        DB_ID()'
        ELSE N'
        NULL'
    END + N',
        NULL
    ) AS fs
    JOIN ' +
    CASE
        WHEN @azure_sql_db = 1
        THEN N'sys.database_files AS mf
      ON  fs.file_id = mf.file_id
      AND fs.database_id = DB_ID()'
        ELSE N'sys.master_files AS mf
      ON  fs.database_id = mf.database_id
      AND fs.file_id = mf.file_id'
    END + N'
    WHERE
    (
         fs.num_of_reads > 0
      OR fs.num_of_writes > 0
    ); /* Only include files with some activity */';

    IF @debug = 1
    BEGIN
        PRINT @file_io_sql;
    END;

    /* Gather IO Stats */
    BEGIN TRY
        INSERT INTO
            #io_stats
        (
            database_name,
            database_id,
            file_name,
            type_desc,
            io_stall_read_ms,
            num_of_reads,
            avg_read_latency_ms,
            io_stall_write_ms,
            num_of_writes,
            avg_write_latency_ms,
            io_stall_ms,
            total_io,
            avg_io_latency_ms,
            size_mb,
            drive_location,
            physical_name
        )
        EXECUTE sys.sp_executesql
            @file_io_sql;
    END TRY
    BEGIN CATCH
        IF @debug = 1
        BEGIN
            SET @error_message = N'Error collecting file IO stats: ' + ERROR_MESSAGE();
            PRINT @error_message;
        END;

        /* Log error in results */
        INSERT INTO #results
        (
            check_id,
            priority,
            category,
            finding,
            details
        )
        VALUES
        (
            9996,
            50, /* Informational: collection error */
            N'Errors',
            N'Error Collecting File IO Statistics',
            N'Unable to collect file IO statistics: ' + ERROR_MESSAGE()
        );
    END CATCH;

    /* Add results for slow reads */
    INSERT INTO
        #results
    (
        check_id,
        priority,
        category,
        finding,
        database_name,
        object_name,
        details,
        url
    )
    SELECT
        check_id = 3001,
        priority =
            CASE
                WHEN i.avg_read_latency_ms > @slow_read_ms * 2
                THEN 20 /* High: >1000ms is severe */
                ELSE 30 /* Medium: >500ms is significant */
            END,
        category = N'Storage Performance',
        finding = N'Slow Read Latency',
        database_name = i.database_name,
        object_name =
            i.file_name +
            N' (' +
            i.type_desc +
            N')',
        details =
            N'Average read latency of ' +
            CONVERT(nvarchar(20), CONVERT(decimal(10, 2), i.avg_read_latency_ms)) +
            N' ms for ' +
            CONVERT(nvarchar(20), i.num_of_reads) +
            N' reads. ' +
            N'This is above the ' +
            CONVERT(nvarchar(10), CONVERT(integer, @slow_read_ms)) +
            N' ms threshold and may indicate storage performance issues.',
        url = N'https://erikdarling.com/sp_perfcheck/#StoragePerformance'
    FROM #io_stats AS i
    WHERE i.avg_read_latency_ms > @slow_read_ms
    AND   i.num_of_reads > 1000; /* Only alert if there's been a significant number of reads */

    /* Add results for slow writes */
    INSERT INTO
        #results
    (
        check_id,
        priority,
        category,
        finding,
        database_name,
        object_name,
        details,
        url
    )
    SELECT
        check_id = 3002,
        priority =
            CASE
                WHEN i.avg_write_latency_ms > @slow_write_ms * 2
                THEN 20 /* High: >1000ms is severe */
                ELSE 30 /* Medium: >500ms is significant */
            END,
        category = N'Storage Performance',
        finding = N'Slow Write Latency',
        database_name = i.database_name,
        object_name =
            i.file_name +
            N' (' +
            i.type_desc +
            N')',
        details =
            N'Average write latency of ' +
            CONVERT(nvarchar(20), CONVERT(decimal(10, 2), i.avg_write_latency_ms)) +
            N' ms for ' +
            CONVERT(nvarchar(20), i.num_of_writes) +
            N' writes. ' +
            N'This is above the ' +
            CONVERT(nvarchar(10), CONVERT(integer, @slow_write_ms)) +
            N' ms threshold and may indicate storage performance issues.',
        url = N'https://erikdarling.com/sp_perfcheck/#StoragePerformance'
    FROM #io_stats AS i
    WHERE i.avg_write_latency_ms > @slow_write_ms
    AND   i.num_of_writes > 1000; /* Only alert if there's been a significant number of writes */

    /* Add drive level warnings if we have multiple slow files on same drive */
    INSERT INTO
        #results
    (
        check_id,
        priority,
        category,
        finding,
        details,
        url
    )
    SELECT
        check_id = 3003,
        priority = 20, /* High: systemic storage problem */
        category = N'Storage Performance',
        finding =
            N'Multiple Slow Files on Storage Location ' +
            i.drive_location,
        details =
            N'Storage location ' +
            i.drive_location +
            N' has ' +
            CONVERT(nvarchar(10), COUNT_BIG(*)) +
            N' database files with slow I/O. ' +
            N'Average overall latency: ' +
            CONVERT(nvarchar(10), CONVERT(decimal(10, 2), AVG(i.avg_io_latency_ms))) +
            N' ms. ' +
            N'This may indicate an overloaded drive or underlying storage issue.',
        url = N'https://erikdarling.com/sp_perfcheck/#StoragePerformance'
    FROM #io_stats AS i
    WHERE
    (
         i.avg_read_latency_ms > @slow_read_ms
      OR i.avg_write_latency_ms > @slow_write_ms
    )
    AND  i.drive_location IS NOT NULL
    GROUP BY
        i.drive_location
    HAVING
        COUNT_BIG(*) > 1;

    /* Get database sizes - safely handles permissions */
    BEGIN TRY
        BEGIN
            SET @db_size_sql = N'
            SELECT
                N''Total Database Size'',
                N''Allocated: '' +
                CONVERT(nvarchar(20), CONVERT(decimal(10, 2), SUM(f.size * 8.0 / 1024.0 / 1024.0))) +
                N'' GB''
            FROM ' +
            CASE
                WHEN @azure_sql_db = 1
                THEN N'sys.database_files AS f
            WHERE f.type_desc = N''ROWS'''
                ELSE N'sys.master_files AS f
            WHERE f.type_desc = N''ROWS'''
            END;

            IF @debug = 1
            BEGIN
                PRINT @db_size_sql;
            END;

            /* For non-Azure SQL DB, get size across all accessible databases */
            INSERT INTO
                #server_info
            (
                info_type,
                value
            )
            EXECUTE sys.sp_executesql
                @db_size_sql;
        END;
    END TRY
    BEGIN CATCH
        /* If we can't access the files due to permissions */
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
            (N'Database Size', N'Unable to determine (permission error)');
    END CATCH;

    /*
    Collect Instance-level Configuration Settings - Platform aware
    */
    IF @azure_sql_db = 0 /* Skip some checks for Azure SQL DB */
    BEGIN
        /* Collect memory settings */
        SELECT
            @min_server_memory =
                CONVERT(bigint, c1.value_in_use),
            @max_server_memory =
                CONVERT(bigint, c2.value_in_use)
        FROM sys.configurations AS c1
        CROSS JOIN sys.configurations AS c2
        WHERE c1.name = N'min server memory (MB)'
        AND   c2.name = N'max server memory (MB)';

        /* Get physical memory for comparison */
        SELECT
            @physical_memory_gb =
                CONVERT
                (
                    decimal(10, 2),
                    osi.physical_memory_kb / 1024.0 / 1024.0
                )
        FROM sys.dm_os_sys_info AS osi;

        /* Add min/max server memory info */
        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
        (
            N'Min Server Memory',
            CONVERT(nvarchar(20), @min_server_memory) +
            N' MB'
        );

        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
        (
            N'Max Server Memory',
            CONVERT(nvarchar(20), @max_server_memory) +
            N' MB'
        );

        /* Collect MAXDOP and CTFP settings */
        SELECT
            @max_dop =
                CONVERT(integer, c1.value_in_use),
            @cost_threshold =
                CONVERT(integer, c2.value_in_use)
        FROM sys.configurations AS c1
        CROSS JOIN sys.configurations AS c2
        WHERE c1.name = N'max degree of parallelism'
        AND   c2.name = N'cost threshold for parallelism';

        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
        (
            N'MAXDOP',
            CONVERT(nvarchar(10), @max_dop)
        );

        INSERT INTO
            #server_info
        (
            info_type,
            value
        )
        VALUES
        (
            N'Cost Threshold for Parallelism',
            CONVERT(nvarchar(10), @cost_threshold)
        );

        /* Collect other significant configuration values */
        SELECT
            @priority_boost =
                CONVERT(bit, c1.value_in_use),
            @lightweight_pooling =
                CONVERT(bit, c2.value_in_use)
        FROM sys.configurations AS c1
        CROSS JOIN sys.configurations AS c2
        WHERE c1.name = N'priority boost'
        AND   c2.name = N'lightweight pooling';

        /* Collect affinity mask settings */
        SELECT
            @affinity_mask =
                CONVERT(bigint, c1.value_in_use),
            @affinity_io_mask =
                CONVERT(bigint, c2.value_in_use),
            @affinity64_mask =
                CONVERT(bigint, c3.value_in_use),
            @affinity64_io_mask =
                CONVERT(bigint, c4.value_in_use)
        FROM sys.configurations AS c1
        CROSS JOIN sys.configurations AS c2
        CROSS JOIN sys.configurations AS c3
        CROSS JOIN sys.configurations AS c4
        WHERE c1.name = N'affinity mask'
        AND   c2.name = N'affinity I/O mask'
        AND   c3.name = N'affinity64 mask'
        AND   c4.name = N'affinity64 I/O mask';
    END;

    /*
    Server Configuration Checks (separated from information gathering)
    */
    IF @azure_sql_db = 0 /* Skip these checks for Azure SQL DB */
    BEGIN
        /* Check for non-default configuration values */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 1000,
            priority = 50, /* Informational: non-default config */
            category = N'Server Configuration',
            finding = N'Non-Default Configuration: ' + c.name,
            details =
                N'Configuration option "' + c.name +
                N'" has been changed from the default. Current: ' +
                CONVERT(nvarchar(50), c.value_in_use) +
                CASE
                    /* Configuration options from your lists */
                    WHEN c.name = N'access check cache bucket count' THEN N', Default: 0'
                    WHEN c.name = N'access check cache quota' THEN N', Default: 0'
                    WHEN c.name = N'Ad Hoc Distributed Queries' THEN N', Default: 0'
                    WHEN c.name = N'ADR cleaner retry timeout (min)' THEN N', Default: 120'
                    WHEN c.name = N'ADR Cleaner Thread Count' THEN N', Default: 1'
                    WHEN c.name = N'ADR Preallocation Factor' THEN N', Default: 4'
                    WHEN c.name = N'affinity mask' THEN N', Default: 0'
                    WHEN c.name = N'affinity I/O mask' THEN N', Default: 0'
                    WHEN c.name = N'affinity64 mask' THEN N', Default: 0'
                    WHEN c.name = N'affinity64 I/O mask' THEN N', Default: 0'
                    WHEN c.name = N'cost threshold for parallelism' THEN N', Default: 5'
                    WHEN c.name = N'max degree of parallelism' THEN N', Default: 0'
                    WHEN c.name = N'max server memory (MB)' THEN N', Default: 2147483647'
                    WHEN c.name = N'max worker threads' THEN N', Default: 0'
                    WHEN c.name = N'min memory per query (KB)' THEN N', Default: 1024'
                    WHEN c.name = N'min server memory (MB)' THEN N', Default: 0'
                    WHEN c.name = N'optimize for ad hoc workloads' THEN N', Default: 0'
                    WHEN c.name = N'priority boost' THEN N', Default: 0'
                    WHEN c.name = N'query governor cost limit' THEN N', Default: 0'
                    WHEN c.name = N'recovery interval (min)' THEN N', Default: 0'
                    WHEN c.name = N'tempdb metadata memory-optimized' THEN N', Default: 0'
                    WHEN c.name = N'lightweight pooling' THEN N', Default: 0'
                    ELSE N', Default: Unknown'
                END,
            url = N'https://erikdarling.com/sp_perfcheck/#ServerSettings'
        FROM sys.configurations AS c
        WHERE
            /* Access check cache settings */
               (c.name = N'access check cache bucket count' AND c.value_in_use <> 0)
            OR (c.name = N'access check cache quota' AND c.value_in_use <> 0)
            OR (c.name = N'Ad Hoc Distributed Queries' AND c.value_in_use <> 0)
            /* ADR settings */
            OR (c.name = N'ADR cleaner retry timeout (min)' AND c.value_in_use NOT IN (0, 15, 120))
            OR (c.name = N'ADR Cleaner Thread Count' AND c.value_in_use <> 1)
            OR (c.name = N'ADR Preallocation Factor' AND c.value_in_use NOT IN (0, 4))
            /* Affinity settings */
            OR (c.name = N'affinity mask' AND c.value_in_use <> 0)
            OR (c.name = N'affinity I/O mask' AND c.value_in_use <> 0)
            OR (c.name = N'affinity64 mask' AND c.value_in_use <> 0)
            OR (c.name = N'affinity64 I/O mask' AND c.value_in_use <> 0)
            /* Common performance settings */
            OR (c.name = N'cost threshold for parallelism' AND c.value_in_use <> 5)
            OR (c.name = N'max degree of parallelism' AND c.value_in_use <> 0)
            OR (c.name = N'max server memory (MB)' AND c.value_in_use <> 2147483647)
            OR (c.name = N'max worker threads' AND c.value_in_use <> 0)
            OR (c.name = N'min memory per query (KB)' AND c.value_in_use <> 1024)
            OR (c.name = N'min server memory (MB)' AND c.value_in_use NOT IN (0, 16))
            OR (c.name = N'optimize for ad hoc workloads' AND c.value_in_use <> 0)
            OR (c.name = N'priority boost' AND c.value_in_use <> 0)
            OR (c.name = N'query governor cost limit' AND c.value_in_use <> 0)
            OR (c.name = N'recovery interval (min)' AND c.value_in_use <> 0)
            OR (c.name = N'tempdb metadata memory-optimized' AND c.value_in_use <> 0)
            OR (c.name = N'lightweight pooling' AND c.value_in_use <> 0);

        /*
        TempDB Configuration Checks (not applicable to Azure SQL DB)
        */
        IF @debug = 1
        BEGIN
            RAISERROR('Checking TempDB configuration', 0, 1) WITH NOWAIT;
        END;

        SET @tempdb_files_sql = N'
        SELECT
            mf.file_id,
            mf.name,
            mf.type_desc,
            size_mb = CONVERT(decimal(18, 2), mf.size * 8.0 / 1024),
            max_size_mb =
                CASE
                    WHEN mf.max_size = -1
                    THEN -1 -- Unlimited
                    ELSE CONVERT(decimal(18, 2), mf.max_size * 8.0 / 1024)
                END,
            growth_mb =
                CASE
                    WHEN mf.is_percent_growth = 1
                    THEN CONVERT(decimal(18, 2), mf.growth) -- Percent
                    ELSE CONVERT(decimal(18, 2), mf.growth * 8.0 / 1024) -- MB
                END,
            mf.is_percent_growth
        FROM ' +
        CASE
            WHEN @azure_sql_db = 1
            THEN N'sys.database_files AS mf
        WHERE DB_NAME() = N''tempdb'';'
            ELSE N'sys.master_files AS mf
        WHERE mf.database_id = 2;'
        END;

        IF @debug = 1
        BEGIN
            PRINT @tempdb_files_sql;
        END;

        /* Get TempDB file information */
        BEGIN TRY
            INSERT INTO
                #tempdb_files
            (
                file_id,
                file_name,
                type_desc,
                size_mb,
                max_size_mb,
                growth_mb,
                is_percent_growth
            )
            EXECUTE sys.sp_executesql
                @tempdb_files_sql;
        END TRY
        BEGIN CATCH
            IF @debug = 1
            BEGIN
                SET @error_message = N'Error collecting TempDB file information: ' + ERROR_MESSAGE();
                PRINT @error_message;
            END;

            /* Log error in results */
            INSERT INTO #results
            (
                check_id,
                priority,
                category,
                finding,
                details
            )
            VALUES
            (
                9995,
                50, /* Informational: collection error */
                N'Errors',
                N'Error Collecting TempDB File Information',
                N'Unable to collect TempDB file information: ' +
                ERROR_MESSAGE()
            );
        END CATCH;

        /* Get file counts and size range */
        SELECT
            @tempdb_data_file_count =
                SUM
                (
                    CASE
                        WHEN tf.type_desc = N'ROWS'
                        THEN 1
                        ELSE 0
                    END
                ),
            @tempdb_log_file_count =
                SUM
                (
                    CASE
                        WHEN tf.type_desc = N'LOG'
                        THEN 1
                        ELSE 0
                    END
                ),
            @min_data_file_size =
                MIN
                (
                    CASE
                        WHEN tf.type_desc = N'ROWS'
                        THEN tf.size_mb / 1024
                        ELSE NULL
                    END
                ),
            @max_data_file_size =
                MAX
                (
                    CASE
                        WHEN tf.type_desc = N'ROWS'
                        THEN tf.size_mb / 1024
                        ELSE NULL
                    END
                ),
            @has_percent_growth =
                MAX
                (
                    CASE
                        WHEN tf.type_desc = N'ROWS'
                        AND  tf.is_percent_growth = 1
                        THEN 1
                        ELSE 0
                    END
                ),
            @has_fixed_growth =
                MAX
                (
                    CASE
                        WHEN tf.type_desc = N'ROWS'
                        AND  tf.is_percent_growth = 0
                        THEN 1
                        ELSE 0
                    END
                )
        FROM #tempdb_files AS tf;

        /* Calculate size difference percentage */
        IF  @max_data_file_size > 0
        AND @min_data_file_size > 0
        BEGIN
            SET @size_difference_pct =
                    (
                      (@max_data_file_size - @min_data_file_size) /
                       @min_data_file_size
                    ) * 100;
        END;
        ELSE
        BEGIN
            SET @size_difference_pct = 0;
        END;

        /* Check for single data file */
        IF  @tempdb_data_file_count = 1
        AND @processors IS NOT NULL
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2001,
                30, /* Medium: single file causes contention */
                N'TempDB Configuration',
                N'Single TempDB Data File',
                N'TempDB has only one data file on a ' + CONVERT(nvarchar(10), @processors) +
                N'-core system. This creates allocation contention. Recommendation: Add ' +
                CASE
                    WHEN @processors > 8 THEN N'8'
                    ELSE CONVERT(nvarchar(10), @processors)
                END + N' data files total.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for odd number of files compared to CPUs */
        IF  @tempdb_data_file_count % 2 <> 0
        AND @tempdb_data_file_count <> @processors
        AND @processors > 1
        BEGIN
            INSERT INTO #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2002,
                50, /* Informational */
                N'TempDB Configuration',
                N'Odd Number of TempDB Files',
                N'TempDB has ' + CONVERT(nvarchar(10), @tempdb_data_file_count) +
                N' data files. This is an odd number and not equal to the ' +
                CONVERT(nvarchar(10), @processors) + ' logical processors. ' +
                N'Consider using an even number of files for better performance.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for more files than CPUs */
        IF  @tempdb_data_file_count > @processors
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2003,
                50, /* Informational */
                N'TempDB Configuration',
                N'More TempDB Files Than CPUs',
                N'TempDB has ' +
                CONVERT(nvarchar(10), @tempdb_data_file_count) +
                N' data files, which is more than the ' +
                CONVERT(nvarchar(10), @processors) +
                N' logical processors. ',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for uneven file sizes (if difference > 10%) */
        IF @size_difference_pct > 10.0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2004,
                40, /* Low: uneven file sizes */
                N'TempDB Configuration',
                N'Uneven TempDB Data File Sizes',
                N'TempDB data files vary in size by ' +
                CONVERT(nvarchar(10), CONVERT(integer, @size_difference_pct)) +
                N'%. Smallest: ' +
                CONVERT(nvarchar(10), CONVERT(integer, @min_data_file_size)) +
                N' GB, Largest: ' +
                CONVERT(nvarchar(10), CONVERT(integer, @max_data_file_size)) +
                N' GB. For best performance, TempDB data files should be the same size.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for mixed autogrowth settings */
        IF  @has_percent_growth = 1
        AND @has_fixed_growth = 1
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2005,
                40, /* Low: inconsistent growth settings */
                N'TempDB Configuration',
                N'Mixed TempDB Autogrowth Settings',
                N'TempDB data files have inconsistent autogrowth settings - some use percentage growth and others use fixed size growth. ' +
                N'This can lead to uneven file sizes over time. Use consistent settings for all files.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for percentage growth in tempdb */
        IF @has_percent_growth = 1
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2006,
                40, /* Low: percent growth in tempdb */
                N'TempDB Configuration',
                N'Percentage Auto-Growth Setting in TempDB',
                N'TempDB data files are using percentage growth settings. This can lead to increasingly larger growth events as files grow. ' +
                N'TempDB is recreated on server restart, so using predictable fixed-size growth is recommended for better performance.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Check for TempDB allocation contention based on pagelatch waits */
        IF  @tempdb_data_file_count <= @processors
        AND @tempdb_data_file_count < 8
        AND @has_view_server_state = 1
        AND @pagelatch_ratio_to_uptime >= 1.0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                2010,
                30, /* Medium: active contention */
                N'TempDB Performance',
                N'TempDB Allocation Contention Detected',
                N'Server has spent ' +
                CONVERT(nvarchar(20), CONVERT(decimal(10,2), @pagelatch_wait_hours)) +
                N' hours (' +
                CONVERT(nvarchar(10), CONVERT(decimal(5,2), @pagelatch_ratio_to_uptime)) +
                N'% of uptime) waiting on page latches. TempDB has ' +
                CONVERT(nvarchar(10), @tempdb_data_file_count) +
                N' data files. Consider adding files up to ' +
                CASE
                    WHEN @processors > 8 THEN N'8'
                    ELSE CONVERT(nvarchar(10), @processors)
                END + N' total to reduce allocation contention.',
                N'https://erikdarling.com/sp_perfcheck/#TempDB'
            );
        END;

        /* Memory configuration checks */
        IF @min_server_memory >= (@max_server_memory * 0.9) /* Within 10% */
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1001,
                40, /* Low: config recommendation */
                N'Server Configuration',
                N'Min Server Memory Too Close To Max',
                N'Min server memory (' +
                CONVERT(nvarchar(20), @min_server_memory) +
                N' MB) is >= 90% of max server memory (' +
                CONVERT(nvarchar(20), @max_server_memory) +
                N' MB). This prevents SQL Server from dynamically adjusting memory.',
                N'https://erikdarling.com/sp_perfcheck/#MinMaxMemory'
            );
        END;

        /* Check if max server memory is too close to physical memory */
        IF @max_server_memory >= (@physical_memory_gb * 1024 * 0.95) /* Within 5% */
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1002,
                40, /* High priority */
                N'Server Configuration',
                N'Max Server Memory Too Close To Physical Memory',
                N'Max server memory (' +
                CONVERT(nvarchar(20), @max_server_memory) +
                N' MB) is >= 95% of physical memory (' +
                CONVERT(nvarchar(20), CONVERT(bigint, @physical_memory_gb * 1024)) +
                N' MB). This may not leave enough memory for the OS and other processes.',
                N'https://erikdarling.com/sp_perfcheck/#MinMaxMemory'
            );
        END;

        /* MAXDOP check */
        IF  @max_dop = 0
        AND @processors > 8
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1003,
                40, /* Low: config recommendation */
                N'Server Configuration',
                N'MAXDOP Not Configured',
                N'Max degree of parallelism is set to 0 (default) on a server with ' +
                CONVERT(nvarchar(10), @processors) +
                N' logical processors. This can lead to excessive parallelism.',
                N'https://erikdarling.com/sp_perfcheck/#MAXDOP'
            );
        END;

        /* Cost Threshold for Parallelism check */
        IF @cost_threshold <= 5
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1004,
                40, /* Low: config recommendation */
                N'Server Configuration',
                N'Low Cost Threshold for Parallelism',
                N'Cost threshold for parallelism is set to ' +
                CONVERT(nvarchar(10), @cost_threshold) +
                N'. Low values can cause excessive parallelism for small queries.',
                N'https://erikdarling.com/sp_perfcheck/#CostThreshold'
            );
        END;

        /* Priority Boost check */
        IF @priority_boost = 1
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1005,
                20, /* High: priority boost is dangerous */
                N'Server Configuration',
                N'Priority Boost Enabled',
                N'Priority boost is enabled.
                  This can cause issues with Windows scheduling priorities and is not recommended.',
                N'https://erikdarling.com/sp_perfcheck/#PriorityBoost'
            );
        END;

        /* Lightweight Pooling check */
        IF @lightweight_pooling = 1
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1006,
                40, /* Low: rarely beneficial */
                N'Server Configuration',
                N'Lightweight Pooling Enabled',
                N'Lightweight pooling (fiber mode) is enabled.
                  This is rarely beneficial and can cause issues with OLEDB providers and other components.',
                N'https://erikdarling.com/sp_perfcheck/#LightweightPooling'
            );
        END;

        /* Affinity Mask check */
        IF @affinity_mask <> 0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1008,
                50, /* Informational: may be intentional */
                N'Server Configuration',
                N'Affinity Mask Configured',
                N'Affinity mask has been manually configured to ' +
                CONVERT(nvarchar(20), @affinity_mask) +
                N'. This can limit SQL Server CPU usage and should only be used when necessary for specific CPU binding scenarios.',
                N'https://erikdarling.com/sp_perfcheck/#AffinityMask'
            );
        END;

        /* Affinity I/O Mask check */
        IF @affinity_io_mask <> 0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1009,
                50, /* Informational: may be intentional */
                N'Server Configuration',
                N'Affinity I/O Mask Configured',
                N'Affinity I/O mask has been manually configured to ' +
                CONVERT(nvarchar(20), @affinity_io_mask) +
                N'. This binds I/O completion to specific CPUs and should only be used for specialized workloads.',
                N'https://erikdarling.com/sp_perfcheck/#AffinityIOMask'
            );
        END;

        /* Affinity64 Mask check */
        IF @affinity64_mask <> 0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1010,
                50, /* Informational: may be intentional */
                N'Server Configuration',
                N'Affinity64 Mask Configured',
                N'Affinity64 mask has been manually configured to ' +
                CONVERT(nvarchar(20), @affinity64_mask) +
                N'. This can limit SQL Server CPU usage on high-CPU systems and should be carefully evaluated.',
                N'https://erikdarling.com/sp_perfcheck/#Affinity64Mask'
            );
        END;

        /* Affinity64 I/O Mask check */
        IF @affinity64_io_mask <> 0
        BEGIN
            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                details,
                url
            )
            VALUES
            (
                1011,
                50, /* Informational: may be intentional */
                N'Server Configuration',
                N'Affinity64 I/O Mask Configured',
                N'Affinity64 I/O mask has been manually configured to ' +
                CONVERT(nvarchar(20), @affinity64_io_mask) +
                N'. This binds I/O completion on high-CPU systems and should be carefully evaluated.',
                N'https://erikdarling.com/sp_perfcheck/#Affinity64IOMask'
            );
        END;

        /* Check for value_in_use <> running_value */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            details,
            url
        )
        SELECT
            check_id = 1007,
            priority = 10, /* Critical: server not running intended config */
            category = N'Server Configuration',
            finding = N'Configuration Pending Reconfigure',
            details =
                N'The configuration option "' +
                c.name +
                N'" has been changed but requires a reconfigure to take effect. ' +
                N'Current value: ' +
                CONVERT(nvarchar(50), c.value_in_use) +
                N', ' +
                N'Pending value: ' +
                CONVERT(nvarchar(50), c.value),
            url = N'https://erikdarling.com/sp_perfcheck/#ServerSettings'
        FROM sys.configurations AS c
        WHERE c.value <> c.value_in_use
        AND NOT
        (
              c.name = N'min server memory (MB)'
          AND c.value_in_use = 16
        );
    END;

    /* Populate #databases table with version-aware dynamic SQL */
    IF COL_LENGTH(N'sys.databases', N'is_ledger_on') IS NOT NULL
    BEGIN
        SET @has_is_ledger = 1;
    END;

    IF COL_LENGTH(N'sys.databases', N'is_accelerated_database_recovery_on') IS NOT NULL
    BEGIN
        SET @has_is_accelerated_database_recovery = 1;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            feature_check = N'Database columns',
            has_is_ledger = @has_is_ledger,
            has_is_accelerated_database_recovery = @has_is_accelerated_database_recovery;
    END;

    SET @sql += N'
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

    SELECT
        d.name,
        d.database_id,
        d.compatibility_level,
        d.collation_name,
        d.user_access_desc,
        d.is_read_only,
        d.is_auto_close_on,
        d.is_auto_shrink_on,
        d.state_desc,
        d.snapshot_isolation_state_desc,
        d.is_read_committed_snapshot_on,
        d.is_auto_create_stats_on,
        d.is_auto_create_stats_incremental_on,
        d.is_auto_update_stats_on,
        d.is_auto_update_stats_async_on,
        d.is_ansi_null_default_on,
        d.is_ansi_nulls_on,
        d.is_ansi_padding_on,
        d.is_ansi_warnings_on,
        d.is_arithabort_on,
        d.is_concat_null_yields_null_on,
        d.is_numeric_roundabort_on,
        d.is_quoted_identifier_on,
        d.is_parameterization_forced,
        d.is_query_store_on,
        d.is_distributor,
        d.is_cdc_enabled,
        d.target_recovery_time_in_seconds,
        d.delayed_durability_desc,';

    /* Handle accelerated database recovery column */
    IF @has_is_accelerated_database_recovery = 1
    BEGIN
        SET @sql += N'
        d.is_accelerated_database_recovery_on';
    END
    ELSE
    BEGIN
        SET @sql += N'
        is_accelerated_database_recovery_on = CONVERT(bit, 0)';
    END;

    /* Add is_ledger_on if it exists */
    IF @has_is_ledger = 1
    BEGIN
        SET @sql += N',
        d.is_ledger_on';
    END;
    ELSE
    BEGIN
        SET @sql += N',
        is_ledger_on = CONVERT(bit, 0)'
    END

    /* Apply appropriate filters based on environment */
    IF @azure_sql_db = 1
    BEGIN
        SET @sql += N'
    FROM sys.databases AS d
    WHERE d.database_id = DB_ID();';
    END
    ELSE
    BEGIN
        IF @database_name IS NULL
        BEGIN
            SET @sql += N'
    FROM sys.databases AS d
    WHERE d.database_id > 4;'; /* Skip system databases */
        END
        ELSE
        BEGIN
            SET @sql += N'
    FROM sys.databases AS d
    WHERE d.name = @database_name;';
        END
    END;

    IF @debug = 1
    BEGIN
        RAISERROR('SQL for #databases: %s', 0, 1, @sql) WITH NOWAIT;
        PRINT REPLICATE(N'=', 128);
        PRINT @sql;
    END;

    INSERT INTO
        #databases
    (
        name,
        database_id,
        compatibility_level,
        collation_name,
        user_access_desc,
        is_read_only,
        is_auto_close_on,
        is_auto_shrink_on,
        state_desc,
        snapshot_isolation_state_desc,
        is_read_committed_snapshot_on,
        is_auto_create_stats_on,
        is_auto_create_stats_incremental_on,
        is_auto_update_stats_on,
        is_auto_update_stats_async_on,
        is_ansi_null_default_on,
        is_ansi_nulls_on,
        is_ansi_padding_on,
        is_ansi_warnings_on,
        is_arithabort_on,
        is_concat_null_yields_null_on,
        is_numeric_roundabort_on,
        is_quoted_identifier_on,
        is_parameterization_forced,
        is_query_store_on,
        is_distributor,
        is_cdc_enabled,
        target_recovery_time_in_seconds,
        delayed_durability_desc,
        is_accelerated_database_recovery_on,
        is_ledger_on
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_name sysname',
        @database_name;

    IF @debug = 1
    BEGIN
        SELECT
            d.*
        FROM #databases AS d
        ORDER BY
            d.database_id;
    END;

    /* Build database list based on context */
    IF @azure_sql_db = 1
    BEGIN
        /* In Azure SQL DB, just use current database */
        INSERT
            #database_list
        (
            database_name,
            database_id,
            state,
            state_desc,
            compatibility_level,
            recovery_model_desc,
            is_read_only,
            is_in_standby,
            is_encrypted,
            create_date,
            can_access
        )
        SELECT
            database_name = d.name,
            database_id = d.database_id,
            state = d.state,
            state_desc = d.state_desc,
            compatibility_level = d.compatibility_level,
            recovery_model_desc = d.recovery_model_desc,
            is_read_only = d.is_read_only,
            is_in_standby = d.is_in_standby,
            is_encrypted = d.is_encrypted,
            create_date = d.create_date,
            can_access = 1
        FROM sys.databases AS d
        WHERE d.database_id = DB_ID();
    END;
    ELSE
    BEGIN
        /* For non-Azure SQL DB, build list from all accessible databases */
        IF @database_name IS NULL
        BEGIN
            /* All user databases */
            INSERT
                #database_list
            (
                database_name,
                database_id,
                state,
                state_desc,
                compatibility_level,
                recovery_model_desc,
                is_read_only,
                is_in_standby,
                is_encrypted,
                create_date,
                can_access
            )
            SELECT
                database_name = d.name,
                database_id = d.database_id,
                state = d.state,
                state_desc = d.state_desc,
                compatibility_level = d.compatibility_level,
                recovery_model_desc = d.recovery_model_desc,
                is_read_only = d.is_read_only,
                is_in_standby = d.is_in_standby,
                is_encrypted = d.is_encrypted,
                create_date = d.create_date,
                can_access = 1 /* Default to accessible, will check individually later */
            FROM sys.databases AS d
            WHERE d.database_id > 4 /* Skip system databases */
            AND   d.state = 0; /* Only online databases */
        END;
        ELSE
        BEGIN
            /* Specific database */
            INSERT
                #database_list
            (
                database_name,
                database_id,
                state,
                state_desc,
                compatibility_level,
                recovery_model_desc,
                is_read_only,
                is_in_standby,
                is_encrypted,
                create_date,
                can_access
            )
            SELECT
                database_name = d.name,
                database_id = d.database_id,
                state = d.state,
                state_desc = d.state_desc,
                compatibility_level = d.compatibility_level,
                recovery_model_desc = d.recovery_model_desc,
                is_read_only = d.is_read_only,
                is_in_standby = d.is_in_standby,
                is_encrypted = d.is_encrypted,
                create_date = d.create_date,
                can_access = 1 /* Default to accessible, will check individually later */
            FROM sys.databases AS d
            WHERE d.name = @database_name
            AND   d.state = 0; /* Only online databases */
        END;

        /* Check each database for accessibility using three-part naming */
        DECLARE
            @db_cursor CURSOR;

        SET @db_cursor =
            CURSOR
            LOCAL
            FAST_FORWARD
        FOR
        SELECT
            dl.database_name,
            dl.database_id
        FROM #database_list AS dl;

        OPEN @db_cursor;

        FETCH NEXT
        FROM @db_cursor
        INTO
            @current_database_name,
            @current_database_id;

        WHILE @@FETCH_STATUS = 0
        BEGIN
            /* Try to access database using three-part naming to ensure we have proper permissions */
            BEGIN TRY
                SET @sql = N'
                SELECT
                    @has_tables =
                        CASE
                            WHEN EXISTS
                            (
                                SELECT
                                    1/0
                                FROM ' +
                                QUOTENAME(@current_database_name) +
                                N'.sys.tables AS t
                            )
                            THEN 1
                            ELSE 0
                        END;';

                IF @debug = 1
                BEGIN
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql,
                  N'@has_tables bit OUTPUT',
                    @has_tables OUTPUT;
            END TRY
            BEGIN CATCH
                /* If we can't access it, mark it */
                UPDATE
                    #database_list
                SET
                    #database_list.can_access = 0
                WHERE #database_list.database_id = @current_database_id;

                IF @debug = 1
                BEGIN
                    SET @message =
                        N'Cannot access database: ' +
                        @current_database_name;

                    RAISERROR(@message, 0, 1) WITH NOWAIT;
                END;
            END CATCH;

            FETCH NEXT
            FROM @db_cursor
            INTO
                @current_database_name,
                @current_database_id;
        END;
    END;

    IF @debug = 1
    BEGIN
        SELECT
            dl.*
        FROM #database_list AS dl;
    END;

    /*
    Database Iteration and Checks
    */
    DECLARE
        @database_cursor CURSOR;

    SET @database_cursor =
        CURSOR
        LOCAL
        FAST_FORWARD
    FOR
    SELECT
        dl.database_name,
        dl.database_id
    FROM #database_list AS dl
    WHERE dl.can_access = 1;

    OPEN @database_cursor;

    FETCH NEXT
    FROM @database_cursor
    INTO
        @current_database_name,
        @current_database_id;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @debug = 1
        BEGIN
            SET @message = N'Processing database: ' + @current_database_name;
            RAISERROR(@message, 0, 1) WITH NOWAIT;
        END;

        /*
        Database-specific checks using three-part naming to maintain context
        */

        /* Check for auto-shrink enabled */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7001,
            priority = 30, /* Medium: actively harmful config */
            category = N'Database Configuration',
            finding = N'Auto-Shrink Enabled',
            database_name = d.name,
            details =
                N'Database has auto-shrink enabled, which can cause significant performance problems.',
            url = N'https://erikdarling.com/sp_perfcheck/#AutoShrink'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.is_auto_shrink_on = 1;

        /* Check for auto-close enabled */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7002,
            priority = 40, /* Low: bad practice but less destructive */
            category = N'Database Configuration',
            finding = N'Auto-Close Enabled',
            database_name = d.name,
            details =
                N'Database has auto-close enabled, which can cause connection delays while the database is reopened.
                 This setting can impact performance for applications that frequently connect to and disconnect from the database.',
            url = N'https://erikdarling.com/sp_perfcheck/#AutoClose'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.is_auto_close_on = 1;

        /* Check for non-MULTI_USER access mode */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7003,
            priority = 20, /* High: apps can't connect */
            category = N'Database Configuration',
            finding =
                N'Restricted Access Mode: ' +
                d.user_access_desc,
            database_name = d.name,
            details =
                N'Database is not in MULTI_USER mode. Current mode: ' +
                d.user_access_desc +
                N'. This restricts normal database access and may prevent applications from connecting.',
            url = N'https://erikdarling.com/sp_perfcheck/#RestrictedAccess'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.user_access_desc <> N'MULTI_USER';

        /* Check for disabled auto-statistics settings */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7004,
            priority = 30, /* Medium: causes stale stats */
            category = N'Database Configuration',
            finding =
                CASE
                    WHEN d.is_auto_create_stats_on = 0
                    AND  d.is_auto_update_stats_on = 0
                    THEN N'Auto Create and Update Statistics Disabled'
                    WHEN d.is_auto_create_stats_on = 0
                    THEN N'Auto Create Statistics Disabled'
                    WHEN d.is_auto_update_stats_on = 0
                    THEN N'Auto Update Statistics Disabled'
                END,
            database_name = d.name,
            details =
                CASE
                    WHEN d.is_auto_create_stats_on = 0
                    AND  d.is_auto_update_stats_on = 0
                    THEN N'Both auto create and auto update statistics are disabled. This can lead to poor query performance due to outdated or missing statistics.'
                    WHEN d.is_auto_create_stats_on = 0
                    AND  d.is_auto_update_stats_on = 1
                    THEN N'Auto create statistics is disabled. This can lead to suboptimal query plans for columns without statistics.'
                    WHEN d.is_auto_update_stats_on = 0
                    AND  d.is_auto_create_stats_on = 1
                    THEN N'Auto update statistics is disabled. This can lead to poor query performance due to outdated statistics.'
                END,
            url = N'https://erikdarling.com/sp_perfcheck/#Statistics'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND
        (
             d.is_auto_create_stats_on = 0
          OR d.is_auto_update_stats_on = 0
        );

        /* Check ANSI settings that might cause issues */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7005,
            priority = 50, /* Informational */
            category = N'Database Configuration',
            finding = N'ANSI Settings Require Review',
            database_name = d.name,
            details =
                N'One or more ANSI settings differ from recommended best practices: ' +
                      CASE WHEN d.is_ansi_null_default_on = 0 THEN N'ANSI_NULL_DEFAULT OFF (recommended ON), ' ELSE N'' END +
                      CASE WHEN d.is_ansi_nulls_on = 0 THEN N'ANSI_NULLS OFF (recommended ON), ' ELSE N'' END +
                      CASE WHEN d.is_ansi_padding_on = 0 THEN N'ANSI_PADDING OFF (recommended ON), ' ELSE N'' END +
                      CASE WHEN d.is_ansi_warnings_on = 0 THEN N'ANSI_WARNINGS OFF (recommended ON), ' ELSE N'' END +
                      CASE WHEN d.is_arithabort_on = 0 THEN N'ARITHABORT OFF (recommended ON in many contexts), ' ELSE N'' END +
                      CASE WHEN d.is_concat_null_yields_null_on = 0 THEN N'CONCAT_NULL_YIELDS_NULL OFF (recommended ON), ' ELSE N'' END +
                      CASE WHEN d.is_numeric_roundabort_on = 1 THEN N'NUMERIC_ROUNDABORT ON (recommended OFF), ' ELSE N'' END +
                      CASE WHEN d.is_quoted_identifier_on = 0 THEN N'QUOTED_IDENTIFIER OFF (recommended ON), ' ELSE N'' END +
                N'These settings may lead to inconsistent behavior, reduced feature compatibility, or unexpected query results ' +
                N'if they do not align with recommended best practices.',
            url = N'https://erikdarling.com/sp_perfcheck/#ANSISettings'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND
        (
             d.is_ansi_null_default_on = 0
          OR d.is_ansi_nulls_on = 0
          OR d.is_ansi_padding_on = 0
          OR d.is_ansi_warnings_on = 0
          OR d.is_arithabort_on = 0
          OR d.is_concat_null_yields_null_on = 0
          OR d.is_numeric_roundabort_on = 1
          OR d.is_quoted_identifier_on = 0
        );

        /* Check Query Store Status */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7006,
            priority = 50, /* Informational: missed opportunity */
            category = N'Database Configuration',
            finding = N'Query Store Not Enabled',
            database_name = d.name,
            details = N'Query Store is not enabled.'
                    + N' Consider enabling Query Store to track query performance'
                    + N' over time and identify regression issues.',
            url = N'https://erikdarling.com/sp_perfcheck/#QueryStore'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.is_query_store_on = 0
        /* Skip this check for Azure SQL DB since Query Store is typically always enabled
           and Azure might be reporting is_query_store_on incorrectly */
        AND   @azure_sql_db = 0;

        /* For Azure SQL DB, explicitly check Query Store status since is_query_store_on might be incorrect */
        IF @azure_sql_db = 1
        BEGIN
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7006,
                priority = 50, /* Informational: missed opportunity */
                category = N''Database Configuration'',
                finding = N''Query Store Not Enabled'',
                database_name = @current_database_name,
                details = N''Query Store is not enabled.
                          Consider enabling Query Store to track query performance
                          over time and identify regression issues.'',
                url = N''https://erikdarling.com/sp_perfcheck/#QueryStore''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_query_store_options AS qso
            WHERE qso.actual_state = 0 /* OFF */;';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;
        END;

        /* Check for Query Store in problematic state */
        BEGIN TRY
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7011,
                priority = 30, /* Medium: QS not working as intended */
                category = N''Database Configuration'',
                finding = N''Query Store State Mismatch'',
                database_name = @current_database_name,
                details =
                    ''Query Store desired state ('' +
                    qso.desired_state_desc +
                    '') does not match actual state ('' +
                    qso.actual_state_desc + ''). '' +
                    CASE qso.readonly_reason
                        WHEN 0 THEN N''No specific reason identified.''
                        WHEN 2 THEN N''Database is in single user mode.''
                        WHEN 4 THEN N''Database is in emergency mode.''
                        WHEN 8 THEN N''Database is an Availability Group secondary.''
                        WHEN 65536 THEN N''Query Store has reached maximum size: '' +
                                        CONVERT(nvarchar(20), qso.current_storage_size_mb) +
                                        '' of '' +
                                        CONVERT(nvarchar(20), qso.max_storage_size_mb) +
                                        '' MB.''
                        WHEN 131072 THEN N''The number of different statements in Query Store has reached the internal memory limit.''
                        WHEN 262144 THEN N''Size of in-memory items waiting to be persisted on disk has reached the internal memory limit.''
                        WHEN 524288 THEN N''Database has reached disk size limit.''
                        ELSE N''Unknown reason code: '' + CONVERT(nvarchar(20), qso.readonly_reason)
                    END,
                url = N''https://erikdarling.com/sp_perfcheck/#QueryStoreHealth''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_query_store_options AS qso
            WHERE qso.desired_state <> 0 /* Not intentionally OFF */
            AND   qso.readonly_reason <> 8 /* Ignore AG secondaries */
            AND   qso.desired_state <> qso.actual_state /* States don''t match */
            AND   qso.actual_state IN (0, 3); /* Either OFF or READ_ONLY when it shouldn''t be */';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;

            /* Check for Query Store with potentially problematic settings */
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7012,
                priority = 40, /* Low: tuning recommendation */
                category = N''Database Configuration'',
                finding = N''Query Store Suboptimal Configuration'',
                database_name = @current_database_name,
                details =
                    CASE
                        WHEN qso.max_storage_size_mb < 1024
                        THEN N''Query Store max size ('' +
                             CONVERT(nvarchar(20), qso.max_storage_size_mb) +
                             '' MB) is less than 1 GB. This may be too small for production databases.''
                        WHEN qso.query_capture_mode_desc = N''NONE''
                        THEN N''Query Store capture mode is set to NONE. No new queries will be captured.''
                        WHEN qso.size_based_cleanup_mode_desc = N''OFF''
                        THEN N''Size-based cleanup is disabled. Query Store may fill up and become read-only.''
                        WHEN qso.stale_query_threshold_days < 3
                        THEN N''Stale query threshold is only '' +
                             CONVERT(nvarchar(20), qso.stale_query_threshold_days) +
                             '' days. Short retention periods may lose historical performance data.''
                        WHEN qso.max_plans_per_query < 10
                        THEN N''Max plans per query is only '' +
                             CONVERT(nvarchar(20), qso.max_plans_per_query) +
                             ''. This may cause relevant plans to be purged prematurely.''
                    END,
                url = N''https://erikdarling.com/sp_perfcheck/#QueryStoreHealth''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_query_store_options AS qso
            WHERE qso.actual_state = 2 /* Query Store is ON */
            AND
            (
                   qso.max_storage_size_mb < 1000
                OR qso.query_capture_mode_desc = N''NONE''
                OR qso.size_based_cleanup_mode_desc = N''OFF''
                OR qso.stale_query_threshold_days < 3
                OR qso.max_plans_per_query < 10
            );';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;

            /* Check for non-default database scoped configurations */
            /* First check if the sys.database_scoped_configurations view exists */
            SET @sql = N'
            IF EXISTS
            (
                SELECT
                    1/0
                FROM ' +
                QUOTENAME(@current_database_name) +
                N'.sys.all_objects AS ao
                WHERE ao.name = N''database_scoped_configurations''
            )
            BEGIN
                /* Delete any existing values for this database */
                TRUNCATE TABLE
                    #database_scoped_configs;

                /* Insert default values as reference for comparison */
                INSERT INTO
                    #database_scoped_configs
                (
                    database_id,
                    database_name,
                    configuration_id,
                    name,
                    value,
                    value_for_secondary,
                    is_value_default
                )
                VALUES
                    (@current_database_id, @current_database_name, 1,  N''MAXDOP'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 2,  N''LEGACY_CARDINALITY_ESTIMATION'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 3,  N''PARAMETER_SNIFFING'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 4,  N''QUERY_OPTIMIZER_HOTFIXES'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 7,  N''INTERLEAVED_EXECUTION_TVF'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 8,  N''BATCH_MODE_MEMORY_GRANT_FEEDBACK'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 9,  N''BATCH_MODE_ADAPTIVE_JOINS'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 10, N''TSQL_SCALAR_UDF_INLINING'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 13, N''OPTIMIZE_FOR_AD_HOC_WORKLOADS'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 16, N''ROW_MODE_MEMORY_GRANT_FEEDBACK'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 18, N''BATCH_MODE_ON_ROWSTORE'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 19, N''DEFERRED_COMPILATION_TV'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 20, N''ACCELERATED_PLAN_FORCING'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 24, N''LAST_QUERY_PLAN_STATS'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 27, N''EXEC_QUERY_STATS_FOR_SCALAR_FUNCTIONS'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 28, N''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 31, N''CE_FEEDBACK'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 33, N''MEMORY_GRANT_FEEDBACK_PERSISTENCE'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 34, N''MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 35, N''OPTIMIZED_PLAN_FORCING'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 37, N''DOP_FEEDBACK'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 39, N''FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION'', NULL, NULL, 1),
                    /* SQL Server 2025 options - IDs to be verified against actual SQL Server 2025 instance */
                    (@current_database_id, @current_database_name, 40, N''PREVIEW_FEATURES'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 41, N''OPTIMIZED_SP_EXECUTESQL'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 42, N''FULLTEXT_INDEX_VERSION'', NULL, NULL, 1),
                    (@current_database_id, @current_database_name, 43, N''OPTIONAL_PARAMETER_OPTIMIZATION'', NULL, NULL, 1);

                /* Get actual non-default settings */
                INSERT INTO
                    #database_scoped_configs
                (
                    database_id,
                    database_name,
                    configuration_id,
                    name,
                    value,
                    value_for_secondary,
                    is_value_default
                )
                SELECT
                    @current_database_id,
                    @current_database_name,
                    sc.configuration_id,
                    sc.name,
                    sc.value,
                    sc.value_for_secondary,
                    CASE
                        WHEN sc.name = N''MAXDOP'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''LEGACY_CARDINALITY_ESTIMATION'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''PARAMETER_SNIFFING'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''QUERY_OPTIMIZER_HOTFIXES'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''INTERLEAVED_EXECUTION_TVF'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''BATCH_MODE_MEMORY_GRANT_FEEDBACK'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''BATCH_MODE_ADAPTIVE_JOINS'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''TSQL_SCALAR_UDF_INLINING'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''OPTIMIZE_FOR_AD_HOC_WORKLOADS'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''ROW_MODE_MEMORY_GRANT_FEEDBACK'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''ISOLATE_SECURITY_POLICY_CARDINALITY'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''BATCH_MODE_ON_ROWSTORE'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''DEFERRED_COMPILATION_TV'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''ACCELERATED_PLAN_FORCING'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''LAST_QUERY_PLAN_STATS'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''EXEC_QUERY_STATS_FOR_SCALAR_FUNCTIONS'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''PARAMETER_SENSITIVE_PLAN_OPTIMIZATION'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''CE_FEEDBACK'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''MEMORY_GRANT_FEEDBACK_PERSISTENCE'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''MEMORY_GRANT_FEEDBACK_PERCENTILE_GRANT'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''OPTIMIZED_PLAN_FORCING'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        WHEN sc.name = N''DOP_FEEDBACK'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''FORCE_SHOWPLAN_RUNTIME_PARAMETER_COLLECTION'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        /* SQL Server 2025 options */
                        WHEN sc.name = N''PREVIEW_FEATURES'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''OPTIMIZED_SP_EXECUTESQL'' AND CONVERT(integer, sc.value) = 0 THEN 1
                        WHEN sc.name = N''FULLTEXT_INDEX_VERSION'' AND CONVERT(integer, sc.value) = 2 THEN 1
                        WHEN sc.name = N''OPTIONAL_PARAMETER_OPTIMIZATION'' AND CONVERT(integer, sc.value) = 1 THEN 1
                        ELSE 0 /* Non-default */
                    END
                FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_scoped_configurations AS sc
                WHERE sc.configuration_id IN
                      (
                        1, 2, 3, 4, 7, 8, 9,
                        10, 13, 16, 18, 19, 20, 24,
                        27, 28, 31, 33, 34, 35, 37, 39,
                        40, 41, 42, 43  /* SQL Server 2025 options */
                      );
            END;';

            IF @debug = 1
            BEGIN
                SELECT
                    dsc.*
                FROM #database_scoped_configs AS dsc
                ORDER BY
                    dsc.database_id,
                    dsc.configuration_id;

                PRINT @current_database_id;
                PRINT @current_database_name;
                PRINT REPLICATE('=', 64);
                PRINT SUBSTRING(@sql, 1, 4000);
                PRINT SUBSTRING(@sql, 4001, 8000);
            END;

            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_id integer,
                @current_database_name sysname',
                @current_database_id,
                @current_database_name;

                /* Add results for non-default configurations */
                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    database_name,
                    object_name,
                    details,
                    url
                )
                SELECT
                    check_id = 7020,
                    priority = 50, /* Informational: non-default config */
                    category = N'Database Configuration',
                    finding = N'Non-Default Database Scoped Configuration',
                    database_name = dsc.database_name,
                    object_name = dsc.name,
                    details =
                        N'Database uses non-default setting for ' +
                        dsc.name +
                        N': ' +
                        ISNULL(CONVERT(nvarchar(100), dsc.value), N'NULL') +
                        CASE
                            WHEN dsc.value_for_secondary IS NOT NULL
                            THEN N' (Secondary: ' +
                                 CONVERT(nvarchar(100), dsc.value_for_secondary) +
                                 N')'
                            ELSE N''
                        END +
                        N'. ',
                    url = N'https://erikdarling.com/sp_perfcheck/#DSC'
                FROM #database_scoped_configs AS dsc
                WHERE dsc.database_id = @current_database_id
                AND   dsc.is_value_default = 0;
        END TRY
        BEGIN CATCH
            IF @debug = 1
            BEGIN
                SET @message =
                    N'Error checking database configuration for ' +
                    @current_database_name +
                    N': ' +
                    ERROR_MESSAGE();

                RAISERROR(@message, 0, 1) WITH NOWAIT;
            END;
        END CATCH;

        /* Check for non-default target recovery time */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7007,
            priority = 50, /* Informational */
            category = N'Database Configuration',
            finding = N'Non-Default Target Recovery Time',
            database_name = d.name,
            details =
                N'Database target recovery time is ' +
                CONVERT(nvarchar(20), d.target_recovery_time_in_seconds) +
                N' seconds, which differs from the default of 60 seconds. This affects checkpoint frequency and recovery time.',
            url = N'https://erikdarling.com/sp_perfcheck/#RecoveryTime'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.target_recovery_time_in_seconds <> 60;

        /* Check transaction durability */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7008,
            priority = 30, /* Medium: data loss risk on crash */
            category = N'Database Configuration',
            finding = N'Delayed Durability: ' + d.delayed_durability_desc,
            database_name = d.name,
            details =
                N'Database uses ' +
                d.delayed_durability_desc +
                N' durability mode. This can improve performance but increases the risk of data loss during a server failure.',
            url = N'https://erikdarling.com/sp_perfcheck/#TransactionDurability'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.delayed_durability_desc <> N'DISABLED';

        /* Check if the database has accelerated database recovery disabled with SI/RCSI enabled */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7009,
            priority = 40, /* Low: recommendation */
            category = N'Database Configuration',
            finding = N'Accelerated Database Recovery Not Enabled With Snapshot Isolation',
            database_name = d.name,
            details =
                N'Database has Snapshot Isolation or RCSI enabled but Accelerated Database Recovery (ADR) is disabled. ' +
                N'ADR can significantly improve performance with these isolation levels by reducing version store cleanup overhead.',
            url = N'https://erikdarling.com/sp_perfcheck/#ADR'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.is_accelerated_database_recovery_on = 0
        AND
        (
              d.snapshot_isolation_state_desc = N'ON'
           OR d.is_read_committed_snapshot_on = 1
        );

        /* Check if ledger is enabled */
        INSERT INTO
            #results
        (
            check_id,
            priority,
            category,
            finding,
            database_name,
            details,
            url
        )
        SELECT
            check_id = 7010,
            priority = 50, /* Informational */
            category = N'Database Configuration',
            finding = N'Ledger Feature Enabled',
            database_name = d.name,
            details =
                N'Database has the ledger feature enabled, which adds blockchain-like capabilities
                 but may impact performance due to additional overhead for maintaining cryptographic verification.',
            url = N'https://erikdarling.com/sp_perfcheck/#Ledger'
        FROM #databases AS d
        WHERE d.database_id = @current_database_id
        AND   d.is_ledger_on = 1;

        /* Check for database file growth settings */
        BEGIN TRY
            /* Check for percentage growth settings on data files */
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7101,
                priority = 40, /* Low: best practice */
                category = N''Database Files'',
                finding = N''Percentage Auto-Growth Setting on Data File'',
                database_name = @current_database_name,
                object_name = mf.name,
                details =
                    ''Database data file is using percentage growth setting ('' +
                    CONVERT(nvarchar(20), mf.growth) +
                    ''%). Current file size is '' +
                    CONVERT(nvarchar(20), CONVERT(decimal(18, 2), mf.size * 8.0 / 1024 / 1024)) +
                    '' GB. This can lead to increasingly larger growth events as the file grows,
                    potentially causing larger file sizes than intended. Even with instant file initialization enabled,
                    consider using a fixed size instead for more predictable growth.'',
                url = N''https://erikdarling.com/sp_perfcheck/#DataFileGrowth''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_files AS mf
            WHERE mf.is_percent_growth = 1
            AND   mf.type_desc = N''ROWS'';';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;

            /* Check for percentage growth settings on log files */
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7102,
                priority = 30, /* Medium: log file percent growth */
                category = N''Database Files'',
                finding = N''Percentage Auto-Growth Setting on Log File'',
                database_name = @current_database_name,
                object_name = mf.name,
                details =
                    ''Transaction log file is using percentage growth setting ('' +
                    CONVERT(nvarchar(20), mf.growth) +
                    ''%). Current file size is '' +
                    CONVERT(nvarchar(20), CONVERT(decimal(18, 2), mf.size * 8.0 / 1024 / 1024)) +
                    '' GB. This can lead to increasingly larger growth events and significant stalls
                    as log files must be zeroed out during auto-growth operations.
                    Always use fixed size growth for log files.'',
                url = N''https://erikdarling.com/sp_perfcheck/#LogFileGrowth''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_files AS mf
            WHERE mf.is_percent_growth = 1
            AND   mf.type_desc = N''LOG'';';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;

            /* Check for non-optimal log growth increments in SQL Server 2022, Azure SQL DB, or Azure MI */
            IF @product_version_major >= 16
            OR @azure_sql_db = 1
            OR @azure_managed_instance = 1
            BEGIN
                SET @sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

                SELECT
                    check_id = 7103,
                    priority = 40, /* Low: best practice for SQL 2022+ */
                    category = N''Database Files'',
                    finding = N''Non-Optimal Log File Growth Increment'',
                    database_name = @current_database_name,
                    object_name = mf.name,
                    details =
                        ''Transaction log file is using a growth increment of '' +
                        CONVERT(nvarchar(20), CONVERT(decimal(18, 2), mf.growth * 8.0 / 1024)) + '' MB. '' +
                        ''On SQL Server 2022, Azure SQL DB, or Azure MI, transaction logs can use instant file initialization when set to exactly 64 MB. '' +
                        ''Consider changing the growth increment to 64 MB for improved performance.'',
                    url = N''https://erikdarling.com/sp_perfcheck/#LogGrowthSize''
                FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_files AS mf
                WHERE mf.is_percent_growth = 0
                AND   mf.type_desc = N''LOG''
                AND   mf.growth * 8.0 / 1024 <> 64;';

                IF @debug = 1
                BEGIN
                    PRINT @sql;
                END;

                INSERT INTO
                    #results
                (
                    check_id,
                    priority,
                    category,
                    finding,
                    database_name,
                    object_name,
                    details,
                    url
                )
                EXECUTE sys.sp_executesql
                    @sql,
                  N'@current_database_name sysname',
                    @current_database_name;
            END;

            /* Check for very large fixed growth settings (>10GB) */
            SET @sql = N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

            SELECT
                check_id = 7104,
                priority = 40, /* Low: very large growth increment */
                category = N''Database Files'',
                finding = N''Extremely Large Auto-Growth Setting'',
                database_name = @current_database_name,
                object_name = mf.name,
                details =
                    ''Database file is using a very large fixed growth increment of '' +
                    CONVERT(nvarchar(20),
                    CONVERT(decimal(18, 2), mf.growth *
                    CONVERT(decimal(18, 2), 8.0) /
                    CONVERT(decimal(18, 2), 1024.0) /
                    CONVERT(decimal(18, 2), 1024.0))) +
                    '' GB. Very large growth increments can lead to excessive space allocation. '' +
                    CASE
                        WHEN mf.type_desc = N''ROWS''
                        THEN N''Even with instant file initialization, consider using smaller increments for more controlled growth.''
                        WHEN mf.type_desc = N''LOG''
                        THEN N''This can cause significant stalls as log files must be zeroed out during growth operations.''
                    END,
                url = N''https://erikdarling.com/sp_perfcheck/#LargeGrowth''
            FROM ' + QUOTENAME(@current_database_name) + N'.sys.database_files AS mf
            WHERE mf.is_percent_growth = 0
            AND   mf.growth * CONVERT(decimal(18, 2), 8.0) /
                  CONVERT(decimal(18, 2), 1024.0) /
                  CONVERT(decimal(18, 2), 1024.0) > 10.0; /* Growth > 10GB */';

            IF @debug = 1
            BEGIN
                PRINT @sql;
            END;

            INSERT INTO
                #results
            (
                check_id,
                priority,
                category,
                finding,
                database_name,
                object_name,
                details,
                url
            )
            EXECUTE sys.sp_executesql
                @sql,
              N'@current_database_name sysname',
                @current_database_name;
        END TRY
        BEGIN CATCH
            IF @debug = 1
            BEGIN
                SET @message =
                    N'Error checking database file growth settings for ' +
                    @current_database_name +
                    N': ' +
                    ERROR_MESSAGE();

                RAISERROR(@message, 0, 1) WITH NOWAIT;
            END;
        END CATCH;

        FETCH NEXT
        FROM @database_cursor
        INTO
            @current_database_name,
            @current_database_id;
    END;

    /* Add scan time footer to server info */
    INSERT INTO
        #server_info
    (
        info_type,
        value
    )
    VALUES
        (N'Run Date', CONVERT(varchar(25), @start_time, 121));

    /*
    Return Server Info First
    */
    SELECT
        [Server Information] =
            si.info_type,
        [Details] =
            si.value
    FROM #server_info AS si
    ORDER BY
        si.id;

    /*
    Return Performance Check Results
    */
    SELECT
        r.check_id,
        r.priority,
        priority_label =
            CASE r.priority
                WHEN 10 THEN N'Critical'
                WHEN 20 THEN N'High'
                WHEN 30 THEN N'Medium'
                WHEN 40 THEN N'Low'
                WHEN 50 THEN N'Informational'
                ELSE N'Unknown'
            END,
        r.category,
        r.finding,
        r.database_name,
        r.object_name,
        r.details,
        r.url
    FROM #results AS r
    ORDER BY
        r.priority,
        r.category,
        r.finding,
        r.database_name,
        r.check_id;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK;
        END;

        THROW;
    END CATCH;
END;
GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*

██████╗ ██████╗ ███████╗███████╗███████╗██╗   ██╗██████╗ ███████╗
██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██║   ██║██╔══██╗██╔════╝
██████╔╝██████╔╝█████╗  ███████╗███████╗██║   ██║██████╔╝█████╗
██╔═══╝ ██╔══██╗██╔══╝  ╚════██║╚════██║██║   ██║██╔══██╗██╔══╝
██║     ██║  ██║███████╗███████║███████║╚██████╔╝██║  ██║███████╗
╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝

██████╗ ███████╗████████╗███████╗ ██████╗████████╗ ██████╗ ██████╗
██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
██║  ██║█████╗     ██║   █████╗  ██║        ██║   ██║   ██║██████╔╝
██║  ██║██╔══╝     ██║   ██╔══╝  ██║        ██║   ██║   ██║██╔══██╗
██████╔╝███████╗   ██║   ███████╗╚██████╗   ██║   ╚██████╔╝██║  ██║
╚═════╝ ╚══════╝   ╚═╝   ╚══════╝ ╚═════╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_PressureDetector
    @help = 1;

For working through errors:
EXECUTE sp_PressureDetector
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/


IF OBJECT_ID(N'dbo.sp_PressureDetector', N'P') IS NULL
    EXECUTE (N'CREATE PROCEDURE dbo.sp_PressureDetector AS RETURN 138;');
GO

ALTER PROCEDURE
    dbo.sp_PressureDetector
(
    @what_to_check varchar(6) = 'all', /*areas to check for pressure*/
    @skip_queries bit = 0, /*if you want to skip looking at running queries*/
    @skip_plan_xml bit = 0, /*if you want to skip getting plan XML*/
    @minimum_disk_latency_ms smallint = 100, /*low bound for reporting disk latency*/
    @cpu_utilization_threshold smallint = 50, /*low bound for reporting high cpu utlization*/
    @skip_waits bit = 0, /*skips waits when you do not need them on every run*/
    @skip_perfmon bit = 0, /*skips perfmon counters when you do not need them on every run*/
    @sample_seconds tinyint = 0, /*take a sample of your server's metrics*/
    @log_to_table bit = 0, /*enable logging to permanent tables*/
    @log_database_name sysname = NULL, /*database to store logging tables*/
    @log_schema_name sysname = NULL, /*schema to store logging tables*/
    @log_table_name_prefix sysname = 'PressureDetector', /*prefix for all logging tables*/
    @log_retention_days integer = 30, /*Number of days to keep logs, 0 = keep indefinitely*/
    @help bit = 0, /*how you got here*/
    @troubleshoot_blocking bit = 0, /*show blocking chains instead of pressure analysis*/
    @debug bit = 0, /*prints dynamic sql, displays parameter and variable values, and table contents*/
    @version varchar(5) = NULL OUTPUT, /*OUTPUT; for support*/
    @version_date datetime = NULL OUTPUT /*OUTPUT; for support*/
)
WITH RECOMPILE
AS
BEGIN
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET LANGUAGE us_english;

SELECT
    @version = '6.3',
    @version_date = '20260301';


IF @help = 1
BEGIN
    /*
    Introduction
    */
    SELECT
        introduction =
           'hi, i''m sp_PressureDetector!' UNION ALL
    SELECT 'you got me from https://code.erikdarling.com' UNION ALL
    SELECT 'i''m a lightweight tool for monitoring cpu and memory pressure' UNION ALL
    SELECT 'i''ll tell you: ' UNION ALL
    SELECT ' * what''s currently consuming memory on your server' UNION ALL
    SELECT ' * wait stats relevant to cpu, memory, and disk pressure, along with query performance' UNION ALL
    SELECT ' * how many worker threads and how much memory you have available' UNION ALL
    SELECT ' * running queries that are using cpu and memory' UNION ALL
    SELECT 'from https://erikdarling.com';

    /*
    Parameters
    */
    SELECT
        parameter_name =
            ap.name,
        data_type = t.name,
        description =
            CASE
                ap.name
                WHEN N'@what_to_check' THEN N'areas to check for pressure'
                WHEN N'@skip_queries' THEN N'if you want to skip looking at running queries'
                WHEN N'@skip_plan_xml' THEN N'if you want to skip getting plan XML'
                WHEN N'@minimum_disk_latency_ms' THEN N'low bound for reporting disk latency'
                WHEN N'@cpu_utilization_threshold' THEN N'low bound for reporting high cpu utlization'
                WHEN N'@skip_waits' THEN N'skips waits when you do not need them on every run'
                WHEN N'@skip_perfmon' THEN N'skips perfmon counters when you do not need them on every run'
                WHEN N'@sample_seconds' THEN N'take a sample of your server''s metrics'
                WHEN N'@log_to_table' THEN N'enable logging to permanent tables instead of returning results'
                WHEN N'@log_database_name' THEN N'database to store logging tables'
                WHEN N'@log_schema_name' THEN N'schema to store logging tables'
                WHEN N'@log_table_name_prefix' THEN N'prefix for all logging tables'
                WHEN N'@log_retention_days' THEN N'how many days of data to retain'
                WHEN N'@help' THEN N'how you got here'
                WHEN N'@troubleshoot_blocking' THEN N'show blocking chains instead of pressure analysis'
                WHEN N'@debug' THEN N'prints dynamic sql, displays parameter and variable values, and table contents'
                WHEN N'@version' THEN N'OUTPUT; for support'
                WHEN N'@version_date' THEN N'OUTPUT; for support'
            END,
        valid_inputs =
            CASE
                ap.name
                WHEN N'@what_to_check' THEN N'"all", "cpu", and "memory"'
                WHEN N'@skip_queries' THEN N'0 or 1'
                WHEN N'@skip_plan_xml' THEN N'0 or 1'
                WHEN N'@minimum_disk_latency_ms' THEN N'a reasonable number of milliseconds for disk latency'
                WHEN N'@cpu_utilization_threshold' THEN N'a reasonable cpu utlization percentage'
                WHEN N'@skip_waits' THEN N'0 or 1'
                WHEN N'@skip_perfmon' THEN N'0 or 1'
                WHEN N'@sample_seconds' THEN N'a valid tinyint: 0-255'
                WHEN N'@log_to_table' THEN N'0 or 1'
                WHEN N'@log_database_name' THEN N'any valid database name'
                WHEN N'@log_schema_name' THEN N'any valid schema name'
                WHEN N'@log_table_name_prefix' THEN N'any valid identifier'
                WHEN N'@log_retention_days' THEN N'a positive integer'
                WHEN N'@help' THEN N'0 or 1'
                WHEN N'@troubleshoot_blocking' THEN N'0 or 1'
                WHEN N'@debug' THEN N'0 or 1'
                WHEN N'@version' THEN N'none'
                WHEN N'@version_date' THEN N'none'
            END,
        defaults =
            CASE
                ap.name
                WHEN N'@what_to_check' THEN N'all'
                WHEN N'@skip_queries' THEN N'0'
                WHEN N'@skip_plan_xml' THEN N'0'
                WHEN N'@minimum_disk_latency_ms' THEN N'100'
                WHEN N'@cpu_utilization_threshold' THEN N'50'
                WHEN N'@skip_waits' THEN N'0'
                WHEN N'@skip_perfmon' THEN N'0'
                WHEN N'@sample_seconds' THEN N'0'
                WHEN N'@log_to_table' THEN N'0'
                WHEN N'@log_database_name' THEN N'NULL (current database)'
                WHEN N'@log_schema_name' THEN N'NULL (dbo)'
                WHEN N'@log_table_name_prefix' THEN N'PressureDetector'
                WHEN N'@log_retention_days' THEN N'30'
                WHEN N'@help' THEN N'0'
                WHEN N'@troubleshoot_blocking' THEN N'0'
                WHEN N'@debug' THEN N'0'
                WHEN N'@version' THEN N'none; OUTPUT'
                WHEN N'@version_date' THEN N'none; OUTPUT'
            END
    FROM sys.all_parameters AS ap
    JOIN sys.all_objects AS o
      ON ap.object_id = o.object_id
    JOIN sys.types AS t
      ON  ap.system_type_id = t.system_type_id
      AND ap.user_type_id = t.user_type_id
    WHERE o.name = N'sp_PressureDetector'
    OPTION(MAXDOP 1, RECOMPILE);

    SELECT
        mit_license_yo =
           'i am MIT licensed, so like, do whatever'

    UNION ALL

    SELECT
        mit_license_yo =
            'see printed messages for full license';

    RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;

    RETURN;
END; /*End help section*/

/*
If @troubleshoot_blocking = 1, skip all other analysis and go directly to blocking analysis
*/
IF @troubleshoot_blocking = 1
BEGIN
    GOTO troubleshoot_blocking;
END;

    /*
    Fix parameters and check the values, etc.
    */
    SELECT
        @what_to_check = ISNULL(@what_to_check, 'all'),
        @skip_queries = ISNULL(@skip_queries, 0),
        @skip_plan_xml = ISNULL(@skip_plan_xml, 0),
        @minimum_disk_latency_ms = ISNULL(@minimum_disk_latency_ms, 100),
        @cpu_utilization_threshold = ISNULL(@cpu_utilization_threshold, 50),
        @skip_waits = ISNULL(@skip_waits, 0),
        @skip_perfmon = ISNULL(@skip_perfmon, 0),
        @sample_seconds = ISNULL(@sample_seconds, 0),
        @log_to_table = ISNULL(@log_to_table, 0),
        @troubleshoot_blocking = ISNULL(@troubleshoot_blocking, 0),
        @help = ISNULL(@help, 0),
        @debug = ISNULL(@debug, 0);

    SELECT
        @what_to_check = LOWER(@what_to_check);

    IF @what_to_check NOT IN ('cpu', 'memory', 'all')
    BEGIN
        RAISERROR('@what_to_check was set to %s, setting to all', 0, 1, @what_to_check) WITH NOWAIT;

        SELECT
            @what_to_check = 'all';
    END;

    IF  @log_to_table = 1
    AND @cpu_utilization_threshold > 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Setting @cpu_utilization_threshold to 0 to capture all CPU utilization data when logging to tables', 0, 1) WITH NOWAIT;
        END;
        SELECT
            @cpu_utilization_threshold = 0;
    END;

    IF  @log_to_table = 1
    AND @sample_seconds <> 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Logging to tables is not compatible with @sample_seconds. Using @sample_seconds = 0', 0, 1) WITH NOWAIT;
        END;
        SELECT
            @sample_seconds = 0;
    END;

    IF   @log_to_table = 1
    AND @what_to_check <> 'all'
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('@what_to_check was set to %s, setting to all when logging to tables', 0, 1, @what_to_check) WITH NOWAIT;
        END;
        SELECT
            @what_to_check = 'all';
    END;

    IF   @log_to_table = 1
    AND
    (
           @skip_queries = 1
        OR @skip_plan_xml = 1
        OR @skip_waits = 1
        OR @skip_perfmon = 1
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('reverting skip options for table logging', 0, 1, @what_to_check) WITH NOWAIT;
        END;
        SELECT
            @skip_queries = 0,
            @skip_plan_xml = 0,
            @skip_waits = 0,
            @skip_perfmon = 0;
    END;

    /*
    Declarations of Variablependence
    */
    IF @debug = 1
    BEGIN
        RAISERROR('Declaring variables and temporary tables', 0, 1) WITH NOWAIT;
    END;

    DECLARE
        @azure bit =
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        SERVERPROPERTY('EngineEdition')
                    ) = 5
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @pool_sql nvarchar(max) = N'',
        @pages_kb bit =
            CASE
                WHEN
                (
                    SELECT
                        COUNT_BIG(*)
                    FROM sys.all_columns AS ac
                    WHERE ac.object_id = OBJECT_ID(N'sys.dm_os_memory_clerks')
                    AND   ac.name = N'pages_kb'
                ) = 1
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @mem_sql nvarchar(max) = N'',
        @helpful_new_columns bit =
            CASE
                WHEN
                (
                    SELECT
                        COUNT_BIG(*)
                    FROM sys.all_columns AS ac
                    WHERE ac.object_id = OBJECT_ID(N'sys.dm_exec_query_memory_grants')
                    AND   ac.name IN
                          (
                              N'reserved_worker_count',
                              N'used_worker_count'
                          )
                ) = 2
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @cpu_sql nvarchar(max) = N'',
        @cool_new_columns bit =
            CASE
                WHEN
                (
                    SELECT
                        COUNT_BIG(*)
                    FROM sys.all_columns AS ac
                    WHERE ac.object_id = OBJECT_ID(N'sys.dm_exec_requests')
                    AND ac.name IN
                        (
                            N'dop',
                            N'parallel_worker_count'
                        )
                ) = 2
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @reserved_worker_count_out varchar(10) = '0',
        @reserved_worker_count nvarchar(max) = N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
    @reserved_worker_count_out =
        SUM(deqmg.reserved_worker_count)
FROM sys.dm_exec_query_memory_grants AS deqmg
OPTION(MAXDOP 1, RECOMPILE);
            ',
        @cpu_details nvarchar(max) = N'',
        @cpu_details_output xml = N'',
        @cpu_details_columns nvarchar(max) = N'',
        @cpu_details_select nvarchar(max) = N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT
    @cpu_details_output =
        (
            SELECT
                offline_cpus =
                    (SELECT COUNT_BIG(*) FROM sys.dm_os_schedulers dos WHERE dos.is_online = 0),
',
        @cpu_details_from nvarchar(max) = N'
            FROM sys.dm_os_sys_info AS osi
            FOR XML
                PATH(''cpu_details''),
                TYPE
        )
OPTION(MAXDOP 1, RECOMPILE);',
        @database_size_out nvarchar(max) = N'',
        @database_size_out_gb nvarchar(10) = '0',
        @total_physical_memory_gb bigint,
        @cpu_utilization xml = N'',
        @low_memory xml = N'',
        @health_history bit =
            CASE
                WHEN OBJECT_ID('sys.dm_os_memory_health_history') IS NOT NULL
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @health_history_xml xml,
        @disk_check nvarchar(max) = N'',
        @live_plans bit =
            CASE
                WHEN OBJECT_ID('sys.dm_exec_query_statistics_xml') IS NOT NULL
                THEN CONVERT(bit, 'true')
                ELSE CONVERT(bit, 'false')
            END,
        @waitfor varchar(20) =
            CONVERT
            (
                nvarchar(20),
                DATEADD
                (
                    SECOND,
                    @sample_seconds,
                    '19000101'
                 ),
                 114
            ),
        @pass tinyint =
            CASE @sample_seconds
                 WHEN 0
                 THEN 1
                 ELSE 0
            END,
        @prefix sysname =
        ISNULL
        (
            (
                SELECT TOP (1)
                    SUBSTRING
                    (
                        dopc.object_name,
                        1,
                        CHARINDEX(N':', dopc.object_name)
                    )
                FROM sys.dm_os_performance_counters AS dopc
            ) +
            N'%',
            N'%'
        ),
        @memory_grant_cap xml,
        @cache_xml xml,
        @cache_sql nvarchar(max) = N'',
        @resource_semaphores nvarchar(max) = N'',
        @cpu_threads nvarchar(max) = N'',
        /*Log to table stuff*/
        @log_table_waits sysname,
        @log_table_file_metrics sysname,
        @log_table_perfmon sysname,
        @log_table_memory sysname,
        @log_table_cpu sysname,
        @log_table_memory_consumers sysname,
        @log_table_memory_queries sysname,
        @log_table_cpu_queries sysname,
        @log_table_cpu_events sysname,
        @cleanup_date datetime2(7),
        @max_sample_time datetime,
        @check_sql nvarchar(max) = N'',
        @create_sql nvarchar(max) = N'',
        @insert_sql nvarchar(max) = N'',
        @delete_sql nvarchar(max) = N'',
        @log_database_schema nvarchar(1024);

    /* Validate logging parameters */
    IF @log_to_table = 1
    BEGIN

        SELECT
            /* Default database name to current database if not specified */
            @log_database_name = ISNULL(@log_database_name, DB_NAME()),
            /* Default schema name to dbo if not specified */
            @log_schema_name = ISNULL(@log_schema_name, N'dbo');

        /* Validate database exists */
        IF NOT EXISTS
        (
            SELECT
                1/0
            FROM sys.databases AS d
            WHERE d.name = @log_database_name
        )
        BEGIN
            RAISERROR('The specified logging database %s does not exist. Logging will be disabled.', 11, 1, @log_database_name) WITH NOWAIT;
            RETURN;
        END;

        SELECT
            @log_database_schema =
                QUOTENAME(@log_database_name) +
                N'.' +
                QUOTENAME(@log_schema_name) +
                N'.';

        /* Generate fully qualified table names */
        SELECT
            @log_table_waits =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_Waits'),
            @log_table_file_metrics =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_FileMetrics'),
            @log_table_perfmon =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_Perfmon'),
            @log_table_memory =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_Memory'),
            @log_table_cpu =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_CPU'),
            @log_table_memory_consumers =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_MemoryConsumers'),
            @log_table_memory_queries =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_MemoryQueries'),
            @log_table_cpu_queries =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_CPUQueries'),
            @log_table_cpu_events =
                @log_database_schema +
                QUOTENAME(@log_table_name_prefix + N'_CPUEvents');

        /* Check if schema exists and create it if needed */
        SET @check_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                WHERE s.name = @schema_name
            )
            BEGIN
                DECLARE
                    @create_schema_sql nvarchar(max) = N''CREATE SCHEMA '' + QUOTENAME(@schema_name);

                EXECUTE ' + QUOTENAME(@log_database_name) + N'.sys.sp_executesql @create_schema_sql;
                IF @debug = 1 BEGIN RAISERROR(''Created schema %s in database %s for logging.'', 0, 1, @schema_name, @db_name) WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @check_sql,
          N'@schema_name sysname,
            @db_name sysname,
            @debug bit',
            @log_schema_name,
            @log_database_name,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_Waits''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_waits + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    server_hours_uptime integer NULL,
                    server_hours_cpu_time decimal(38,2) NULL,
                    wait_type nvarchar(60) NOT NULL,
                    description nvarchar(60) NULL,
                    hours_wait_time decimal(38,2) NULL,
                    avg_ms_per_wait decimal(38,2) NULL,
                    percent_signal_waits decimal(38,2) NULL,
                    waiting_tasks_count bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for wait stats logging.'', 0, 1, ''' + @log_table_waits + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_FileMetrics''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_file_metrics + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    server_hours_uptime integer NULL,
                    drive nvarchar(255) NOT NULL,
                    database_name nvarchar(128) NOT NULL,
                    database_file_details nvarchar(1000) NULL,
                    file_size_gb decimal(38,2) NULL,
                    total_gb_read decimal(38,2) NULL,
                    total_mb_read decimal(38,2) NULL,
                    total_read_count bigint NULL,
                    avg_read_stall_ms decimal(38,2) NULL,
                    total_gb_written decimal(38,2) NULL,
                    total_mb_written decimal(38,2) NULL,
                    total_write_count bigint NULL,
                    avg_write_stall_ms decimal(38,2) NULL,
                    io_stall_read_ms bigint NULL,
                    io_stall_write_ms bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for file metrics logging.'', 0, 1, ''' + @log_table_file_metrics + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_Perfmon''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_perfmon + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    object_name sysname NOT NULL,
                    counter_name sysname NOT NULL,
                    counter_name_clean sysname NULL,
                    instance_name sysname NOT NULL,
                    cntr_value bigint NULL,
                    cntr_type bigint NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for perfmon logging.'', 0, 1, ''' + @log_table_perfmon + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_Memory''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    resource_semaphore_id integer NOT NULL,
                    total_database_size_gb varchar(20) NULL,
                    total_physical_memory_gb bigint NULL,
                    max_server_memory_gb bigint NULL,
                    max_memory_grant_cap xml NULL,
                    memory_model nvarchar(128) NULL,
                    target_memory_gb decimal(38,2) NULL,
                    max_target_memory_gb decimal(38,2) NULL,
                    total_memory_gb decimal(38,2) NULL,
                    available_memory_gb decimal(38,2) NULL,
                    granted_memory_gb decimal(38,2) NULL,
                    used_memory_gb decimal(38,2) NULL,
                    grantee_count integer NULL,
                    waiter_count integer NULL,
                    timeout_error_count integer NULL,
                    forced_grant_count integer NULL,
                    total_reduced_memory_grant_count bigint NULL,
                    pool_id integer NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory logging.'', 0, 1, ''' + @log_table_memory + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_CPU''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_cpu + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    total_threads integer NULL,
                    used_threads integer NULL,
                    available_threads integer NULL,
                    reserved_worker_count varchar(10) NULL,
                    threads_waiting_for_cpu integer NULL,
                    requests_waiting_for_threads integer NULL,
                    current_workers integer NULL,
                    total_active_request_count integer NULL,
                    total_queued_request_count integer NULL,
                    total_blocked_task_count integer NULL,
                    total_active_parallel_thread_count integer NULL,
                    avg_runnable_tasks_count float NULL,
                    high_runnable_percent varchar(100) NULL,
                    cpu_details_output xml NULL,
                    cpu_utilization_over_threshold xml NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for CPU logging.'', 0, 1, ''' + @log_table_cpu + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Memory Consumers table */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_MemoryConsumers''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory_consumers + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    memory_source nvarchar(128) NOT NULL,
                    memory_consumer nvarchar(128) NOT NULL,
                    memory_consumed_gb decimal(38,2) NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory consumers logging.'', 0, 1, ''' + @log_database_schema + QUOTENAME(@log_table_name_prefix + N'_MemoryConsumers') + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Memory Query Grants table */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_MemoryQueries''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_memory_queries + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    session_id integer NOT NULL,
                    database_name nvarchar(128) NULL,
                    duration varchar(30) NULL,
                    request_time datetime NULL,
                    grant_time datetime NULL,
                    wait_time_seconds decimal(38,2) NULL,
                    requested_memory_gb decimal(38,2) NULL,
                    granted_memory_gb decimal(38,2) NULL,
                    used_memory_gb decimal(38,2) NULL,
                    max_used_memory_gb decimal(38,2) NULL,
                    ideal_memory_gb decimal(38,2) NULL,
                    required_memory_gb decimal(38,2) NULL,
                    queue_id integer NULL,
                    wait_order integer NULL,
                    is_next_candidate bit NULL,
                    wait_type nvarchar(60) NULL,
                    wait_duration_seconds decimal(38,2) NULL,
                    dop integer NULL,
                    reserved_worker_count integer NULL,
                    used_worker_count integer NULL,
                    plan_handle varbinary(64) NULL,
                    sql_text xml NULL,
                    query_plan_xml xml NULL,
                    live_query_plan xml NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for memory queries logging.'', 0, 1, ''' + @log_database_schema + QUOTENAME(@log_table_name_prefix + N'_MemoryQueries') + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* CPU Queries table */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_CPUQueries''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_cpu_queries + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    session_id integer NOT NULL,
                    database_name nvarchar(128) NULL,
                    duration varchar(30) NULL,
                    status nvarchar(30) NULL,
                    blocking_session_id integer NULL,
                    wait_type nvarchar(60) NULL,
                    wait_time_ms bigint NULL,
                    wait_resource nvarchar(512) NULL,
                    cpu_time_ms bigint NULL,
                    total_elapsed_time_ms bigint NULL,
                    reads bigint NULL,
                    writes bigint NULL,
                    logical_reads bigint NULL,
                    granted_query_memory_gb decimal(38,2) NULL,
                    transaction_isolation_level sysname NULL,
                    dop integer NULL,
                    parallel_worker_count integer NULL,
                    plan_handle varbinary(64) NULL,
                    sql_text xml NULL,
                    query_plan_xml xml NULL,
                    live_query_plan xml NULL,
                    statement_start_offset integer NULL,
                    statement_end_offset integer NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for CPU queries logging.'', 0, 1, ''' + @log_database_schema + QUOTENAME(@log_table_name_prefix + N'_CPUQueries') + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;


        /* CPU Utilization Events table */
        SET @create_sql = N'
            IF NOT EXISTS
            (
                SELECT
                    1/0
                FROM ' + QUOTENAME(@log_database_name) + N'.sys.tables AS t
                JOIN ' + QUOTENAME(@log_database_name) + N'.sys.schemas AS s
                  ON t.schema_id = s.schema_id
                WHERE t.name = @table_name + N''_CPUEvents''
                AND   s.name = @schema_name
            )
            BEGIN
                CREATE TABLE ' + @log_table_cpu_events + N'
                (
                    id bigint IDENTITY,
                    collection_time datetime2(7) NOT NULL DEFAULT SYSDATETIME(),
                    sample_time datetime NULL,
                    sqlserver_cpu_utilization integer NULL,
                    other_process_cpu_utilization integer NULL,
                    total_cpu_utilization integer NULL,
                    PRIMARY KEY CLUSTERED (collection_time, id)
                );
                IF @debug = 1 BEGIN RAISERROR(''Created table %s for CPU utilization events logging.'', 0, 1, ''' + @log_database_schema + QUOTENAME(@log_table_name_prefix + N'_CPUEvents') + N''') WITH NOWAIT; END;
            END';

        EXECUTE sys.sp_executesql
            @create_sql,
          N'@schema_name sysname,
            @table_name sysname,
            @debug bit',
            @log_schema_name,
            @log_table_name_prefix,
            @debug;

        /* Handle log retention if specified */
        IF @log_to_table = 1 AND @log_retention_days > 0
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Cleaning up log tables older than %i days', 0, 1, @log_retention_days) WITH NOWAIT;
            END;

            SET @cleanup_date =
                DATEADD
                (
                    DAY,
                    -@log_retention_days,
                    SYSDATETIME()
                );

            /* Clean up each log table */
            SET @delete_sql = N'
            DELETE FROM ' + @log_table_waits + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_file_metrics + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_perfmon + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_memory + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_cpu + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_memory_consumers + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_memory_queries + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_cpu_queries + '
            WHERE collection_time < @cleanup_date;

            DELETE FROM ' + @log_table_cpu_events + '
            WHERE collection_time < @cleanup_date;';

            IF @debug = 1 BEGIN PRINT @delete_sql; END;

            EXECUTE sys.sp_executesql
                @delete_sql,
              N'@cleanup_date datetime2(7)',
                @cleanup_date;

            IF @debug = 1
            BEGIN
                RAISERROR('Log cleanup complete', 0, 1) WITH NOWAIT;
            END;
        END;

    END; /*End log to tables validation checks here*/

    DECLARE
        @waits table
    (
        server_hours_uptime integer,
        server_hours_cpu_time decimal(38,2),
        wait_type nvarchar(60),
        description nvarchar(60),
        hours_wait_time decimal(38,2),
        avg_ms_per_wait decimal(38,2),
        percent_signal_waits decimal(38,2),
        waiting_tasks_count_n bigint,
        sample_time datetime,
        sorting bigint,
        waiting_tasks_count AS
            FORMAT(waiting_tasks_count_n, 'N0')
    );

    DECLARE
        @file_metrics table
    (
        server_hours_uptime integer,
        drive nvarchar(255),
        database_name nvarchar(128),
        database_file_details nvarchar(1000),
        file_size_gb decimal(38,2),
        total_gb_read decimal(38,2),
        total_mb_read decimal(38,2),
        total_read_count bigint,
        avg_read_stall_ms decimal(38,2),
        total_gb_written decimal(38,2),
        total_mb_written decimal(38,2),
        total_write_count bigint,
        avg_write_stall_ms decimal(38,2),
        io_stall_read_ms bigint,
        io_stall_write_ms bigint,
        sample_time datetime
    );

    DECLARE
        @dm_os_performance_counters table

    (
        sample_time datetime,
        object_name sysname,
        counter_name sysname,
        counter_name_clean sysname,
        instance_name sysname,
        cntr_value bigint,
        cntr_type bigint
    );

    DECLARE
        @threadpool_waits table
    (
        session_id smallint,
        wait_duration_ms bigint,
        threadpool_waits sysname
    );

    /*Use a GOTO to avoid writing all the code again*/
    DO_OVER:;

    /*
    Check to see if the DAC is enabled.
    If it's not, give people some helpful information.
    */
    IF
    (
        @what_to_check = 'all'
    AND @pass = 1
    AND @log_to_table = 0
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking DAC status, etc.', 0, 1) WITH NOWAIT;
        END;

        IF
        (
            SELECT
                c.value_in_use
            FROM sys.configurations AS c
            WHERE c.name = N'remote admin connections'
        ) = 0
        BEGIN
            SELECT
                message =
                    'This works a lot better on a troublesome server with the DAC enabled',
                command_to_run =
                    'EXECUTE sp_configure ''remote admin connections'', 1; RECONFIGURE;',
                how_to_use_the_dac =
                    'https://bit.ly/RemoteDAC';
        END;

        /*
        See if someone else is using the DAC.
        Return some helpful information if they are.
        */
        IF @azure = 0
        BEGIN
            IF EXISTS
            (
                SELECT
                    1/0
                FROM sys.endpoints AS ep
                JOIN sys.dm_exec_sessions AS ses
                  ON ep.endpoint_id = ses.endpoint_id
                WHERE ep.name = N'Dedicated Admin Connection'
                AND   ses.session_id <> @@SPID
            )
            BEGIN
                SELECT
                    dac_thief =
                       'who stole the dac?',
                    ses.session_id,
                    ses.login_time,
                    ses.host_name,
                    ses.program_name,
                    ses.login_name,
                    ses.nt_domain,
                    ses.nt_user_name,
                    ses.status,
                    ses.last_request_start_time,
                    ses.last_request_end_time
                FROM sys.endpoints AS ep
                JOIN sys.dm_exec_sessions AS ses
                  ON ep.endpoint_id = ses.endpoint_id
                WHERE ep.name = N'Dedicated Admin Connection'
                AND   ses.session_id <> @@SPID
                OPTION(MAXDOP 1, RECOMPILE);
            END;
        END;
    END; /*End DAC section*/

    /*
    Look at wait stats related to performance only
    */
    IF @skip_waits = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking waits stats', 0, 1) WITH NOWAIT;
        END;

        INSERT
            @waits
        (
            server_hours_uptime,
            server_hours_cpu_time,
            wait_type,
            description,
            hours_wait_time,
            avg_ms_per_wait,
            percent_signal_waits,
            waiting_tasks_count_n,
            sample_time,
            sorting
        )
        SELECT
            server_hours_uptime =
                (
                    SELECT
                        DATEDIFF
                        (
                            HOUR,
                            osi.sqlserver_start_time,
                            SYSDATETIME()
                        )
                    FROM sys.dm_os_sys_info AS osi
                ),
            server_hours_cpu_time =
                (
                    SELECT
                        CONVERT
                        (
                            decimal(38, 2),
                            SUM(wg.total_cpu_usage_ms) /
                                CASE
                                    WHEN
                                        @sample_seconds > 0
                                        THEN 1
                                        ELSE (1000. * 60. * 60.)
                                    END
                        )
                    FROM sys.dm_resource_governor_workload_groups AS wg
                ),
            dows.wait_type,
            description =
                CASE
                    WHEN dows.wait_type = N'PAGEIOLATCH_SH'
                    THEN N'Selects reading pages from disk into memory'
                    WHEN dows.wait_type = N'PAGEIOLATCH_EX'
                    THEN N'Modifications reading pages from disk into memory'
                    WHEN dows.wait_type = N'RESOURCE_SEMAPHORE'
                    THEN N'Queries waiting to get memory to run'
                    WHEN dows.wait_type = N'RESOURCE_SEMAPHORE_QUERY_COMPILE'
                    THEN N'Queries waiting to get memory to compile'
                    WHEN dows.wait_type = N'CXPACKET'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXCONSUMER'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXSYNC_PORT'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'CXSYNC_CONSUMER'
                    THEN N'Query parallelism'
                    WHEN dows.wait_type = N'SOS_SCHEDULER_YIELD'
                    THEN N'Query scheduling'
                    WHEN dows.wait_type = N'THREADPOOL'
                    THEN N'Potential worker thread exhaustion'
                    WHEN dows.wait_type = N'RESOURCE_GOVERNOR_IDLE'
                    THEN N'Potential CPU cap waits'
                    WHEN dows.wait_type = N'CMEMTHREAD'
                    THEN N'Tasks waiting on memory objects'
                    WHEN dows.wait_type = N'PAGELATCH_EX'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type = N'PAGELATCH_SH'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type = N'PAGELATCH_UP'
                    THEN N'Potential tempdb contention'
                    WHEN dows.wait_type LIKE N'LCK%'
                    THEN N'Queries waiting to acquire locks'
                    WHEN dows.wait_type = N'WRITELOG'
                    THEN N'Transaction Log writes'
                    WHEN dows.wait_type = N'LOGBUFFER'
                    THEN N'Transaction Log buffering'
                    WHEN dows.wait_type = N'LOG_RATE_GOVERNOR'
                    THEN N'Azure Transaction Log throttling'
                    WHEN dows.wait_type = N'POOL_LOG_RATE_GOVERNOR'
                    THEN N'Azure Transaction Log throttling'
                    WHEN dows.wait_type = N'SLEEP_TASK'
                    THEN N'Potential Hash spills'
                    WHEN dows.wait_type = N'BPSORT'
                    THEN N'Potential batch mode sort performance issues'
                    WHEN dows.wait_type = N'EXECSYNC'
                    THEN N'Potential eager index spool creation'
                    WHEN dows.wait_type = N'IO_COMPLETION'
                    THEN N'Potential sort spills'
                    WHEN dows.wait_type = N'ASYNC_NETWORK_IO'
                    THEN N'Potential client issues'
                    WHEN dows.wait_type = N'SLEEP_BPOOL_STEAL'
                    THEN N'Potential buffer pool pressure'
                    WHEN dows.wait_type = N'PWAIT_QRY_BPMEMORY'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTREPARTITION'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTBUILD'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTMEMO'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTDELETE'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'HTREINIT'
                    THEN N'Potential batch mode performance issues'
                    WHEN dows.wait_type = N'BTREE_INSERT_FLOW_CONTROL'
                    THEN N'Optimize For Sequential Key'
                    WHEN dows.wait_type = N'HADR_SYNC_COMMIT'
                    THEN N'Potential Availability Group Issues'
                    WHEN dows.wait_type = N'HADR_GROUP_COMMIT'
                    THEN N'Potential Availability Group Issues'
                    WHEN dows.wait_type = N'WAIT_ON_SYNC_STATISTICS_REFRESH'
                    THEN N'Waiting on sync stats updates (compilation)'
                    WHEN dows.wait_type = N'IO_QUEUE_LIMIT'
                    THEN N'Azure SQLDB Throttling'
                    WHEN dows.wait_type = N'IO_RETRY'
                    THEN N'I/O Failures retried'
                    WHEN dows.wait_type = N'RESMGR_THROTTLED'
                    THEN N'Azure SQLDB Throttling'
                END,
            hours_wait_time =
                CASE
                    WHEN @sample_seconds > 0
                    THEN dows.wait_time_ms
                    ELSE
                        CONVERT
                        (
                            decimal(38, 2),
                            dows.wait_time_ms / (1000. * 60. * 60.)
                        )
                END,
            avg_ms_per_wait =
                ISNULL
                (
                   CONVERT
                   (
                       decimal(38, 2),
                       dows.wait_time_ms /
                           NULLIF
                           (
                               1.*
                               dows.waiting_tasks_count,
                               0.
                           )
                    ),
                    0.
                ),
            percent_signal_waits =
                CONVERT
                (
                    decimal(38, 2),
                    ISNULL
                    (
                        100.0 * dows.signal_wait_time_ms
                           / NULLIF(dows.wait_time_ms, 0),
                        0.
                    )
                ),
            dows.waiting_tasks_count,
            sample_time =
                SYSDATETIME(),
            sorting =
                ROW_NUMBER() OVER
                (
                    ORDER BY
                        dows.wait_time_ms DESC
                )
        FROM sys.dm_os_wait_stats AS dows
        WHERE
        (
          (
                  dows.waiting_tasks_count > -1
              AND dows.wait_type <> N'SLEEP_TASK'
          )
        OR
          (
                 dows.wait_type = N'SLEEP_TASK'
             AND ISNULL(CONVERT(decimal(38, 2), dows.wait_time_ms /
                   NULLIF(1.* dows.waiting_tasks_count, 0.)), 0.) >=
                     CASE WHEN @sample_seconds > 0 THEN 0. ELSE 1000. END
          )
        )
        AND
        (
            dows.wait_type IN
                 (
                     /*Disk*/
                     N'PAGEIOLATCH_SH',
                     N'PAGEIOLATCH_EX',
                     /*Memory*/
                     N'RESOURCE_SEMAPHORE',
                     N'RESOURCE_SEMAPHORE_QUERY_COMPILE',
                     N'CMEMTHREAD',
                     N'SLEEP_BPOOL_STEAL',
                     /*Parallelism*/
                     N'CXPACKET',
                     N'CXCONSUMER',
                     N'CXSYNC_PORT',
                     N'CXSYNC_CONSUMER',
                     /*CPU*/
                     N'SOS_SCHEDULER_YIELD',
                     N'THREADPOOL',
                     N'RESOURCE_GOVERNOR_IDLE',
                     /*tempdb (potentially)*/
                     N'PAGELATCH_EX',
                     N'PAGELATCH_SH',
                     N'PAGELATCH_UP',
                     /*Transaction log*/
                     N'WRITELOG',
                     N'LOGBUFFER',
                     N'LOG_RATE_GOVERNOR',
                     N'POOL_LOG_RATE_GOVERNOR',
                     /*Some query performance stuff, spills and spools mostly*/
                     N'ASYNC_NETWORK_IO',
                     N'EXECSYNC',
                     N'IO_COMPLETION',
                     N'SLEEP_TASK',
                     /*Batch Mode*/
                     N'HTBUILD',
                     N'HTDELETE',
                     N'HTMEMO',
                     N'HTREINIT',
                     N'HTREPARTITION',
                     N'PWAIT_QRY_BPMEMORY',
                     N'BPSORT',
                     /*Optimize For Sequential Key*/
                     N'BTREE_INSERT_FLOW_CONTROL',
                     /*Availability Group*/
                     N'HADR_SYNC_COMMIT',
                     N'HADR_GROUP_COMMIT',
                     /*Stats/Compilation*/
                     N'WAIT_ON_SYNC_STATISTICS_REFRESH',
                     /*Throttling*/
                     N'IO_QUEUE_LIMIT',
                     N'IO_RETRY',
                     N'RESMGR_THROTTLED'
                 )
            /*Locking*/
            OR dows.wait_type LIKE N'LCK%'
        )
        ORDER BY
            dows.wait_time_ms DESC,
            dows.waiting_tasks_count DESC
        OPTION(MAXDOP 1, RECOMPILE);

        IF @log_to_table = 0
        BEGIN
            IF @sample_seconds = 0
            BEGIN
                SELECT
                    w.wait_type,
                    w.description,
                    w.server_hours_uptime,
                    w.server_hours_cpu_time,
                    w.hours_wait_time,
                    w.avg_ms_per_wait,
                    w.percent_signal_waits,
                    waiting_tasks_count =
                        FORMAT(w.waiting_tasks_count_n, 'N0')
                FROM @waits AS w
                WHERE w.waiting_tasks_count_n > 0
                ORDER BY
                    w.sorting
                OPTION(MAXDOP 1, RECOMPILE);
            END;

            IF
            (
                @sample_seconds > 0
            AND @pass = 1
            )
            BEGIN
                SELECT
                    w.wait_type,
                    w.description,
                    sample_cpu_time_seconds =
                        CONVERT
                        (
                            decimal(38,2),
                            (w2.server_hours_cpu_time - w.server_hours_cpu_time) / 1000.
                        ),
                    wait_time_seconds =
                        CONVERT
                        (
                            decimal(38,2),
                            (w2.hours_wait_time - w.hours_wait_time) / 1000.
                        ),
                    avg_ms_per_wait =
                        CONVERT
                        (
                            decimal(38,1),
                            ISNULL
                            (
                                (w2.hours_wait_time - w.hours_wait_time) /
                                    NULLIF
                                    (
                                        1. * (w2.waiting_tasks_count_n - w.waiting_tasks_count_n),
                                        0.
                                    ),
                                0.
                            )
                        ),
                    percent_signal_waits =
                        CONVERT
                        (
                            decimal(38,1),
                            (w2.percent_signal_waits + w.percent_signal_waits) / 2
                        ),
                    waiting_tasks_count =
                        FORMAT((w2.waiting_tasks_count_n - w.waiting_tasks_count_n), 'N0'),
                    sample_seconds =
                        DATEDIFF
                        (
                            SECOND,
                            w.sample_time,
                            w2.sample_time
                        )
                FROM @waits AS w
                JOIN @waits AS w2
                  ON  w.wait_type = w2.wait_type
                  AND w.sample_time < w2.sample_time
                  AND (w2.waiting_tasks_count_n - w.waiting_tasks_count_n) > 0
                ORDER BY
                    wait_time_seconds DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;
        END;

            IF @log_to_table = 1
            BEGIN

                SELECT
                    w.*
                INTO #waits
                FROM @waits AS w
                OPTION(RECOMPILE);

                SET @insert_sql = N'
                    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    INSERT INTO ' + @log_table_waits + N'
                    (
                        server_hours_uptime,
                        server_hours_cpu_time,
                        wait_type,
                        description,
                        hours_wait_time,
                        avg_ms_per_wait,
                        percent_signal_waits,
                        waiting_tasks_count
                    )
                    SELECT
                        w.server_hours_uptime,
                        w.server_hours_cpu_time,
                        w.wait_type,
                        w.description,
                        w.hours_wait_time,
                        w.avg_ms_per_wait,
                        w.percent_signal_waits,
                        w.waiting_tasks_count_n
                    FROM #waits AS w;
                    ';

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql;

                IF OBJECT_ID(N'tempdb..#waits', N'U') IS NOT NULL
                BEGIN
                    DROP TABLE #waits;
                END;
            END;
    END; /*End wait stats*/
    /*
    This section looks at disk metrics
    */
    IF @what_to_check = 'all'
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking file stats', 0, 1) WITH NOWAIT;
        END;

        SET @disk_check = N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        SELECT
            server_hours_uptime =
                (
                    SELECT
                        DATEDIFF
                        (
                            HOUR,
                            osi.sqlserver_start_time,
                            SYSDATETIME()
                        )
                    FROM sys.dm_os_sys_info AS osi
                ),
            drive =
                CASE
                    WHEN f.physical_name LIKE N''http%''
                    THEN f.physical_name
                    ELSE
                        UPPER
                        (
                            LEFT
                            (
                                f.physical_name,
                                2
                            )
                        )
                    END,
            database_name =
                 DB_NAME(vfs.database_id),
            database_file_details =
                ISNULL
                (
                    f.name COLLATE DATABASE_DEFAULT,
                    N''''
                ) +
                SPACE(1) +
                ISNULL
                (
                    CASE f.type
                         WHEN 0
                         THEN N''(data file)''
                         WHEN 1
                         THEN N''(transaction log)''
                         WHEN 2
                         THEN N''(filestream)''
                         WHEN 4
                         THEN N''(full-text)''
                         ELSE QUOTENAME
                              (
                                  f.type_desc COLLATE DATABASE_DEFAULT,
                                  N''()''
                              )
                    END,
                    N''''
                ) +
                SPACE(1) +
                ISNULL
                (
                    QUOTENAME
                    (
                        f.physical_name COLLATE DATABASE_DEFAULT,
                        N''()''
                    ),
                    N''''
                ),
            file_size_gb =
                CONVERT
                (
                    decimal(38, 2),
                    vfs.size_on_disk_bytes / 1073741824.
                ),
            total_gb_read =
                CASE
                    WHEN vfs.num_of_bytes_read > 0
                    THEN CONVERT
                         (
                             decimal(38, 2),
                             vfs.num_of_bytes_read / 1073741824.
                         )
                    ELSE 0
                END,
            total_mb_read =
                CASE
                    WHEN vfs.num_of_bytes_read > 0
                    THEN CONVERT
                         (
                             decimal(38, 2),
                             vfs.num_of_bytes_read / 1048576.
                         )
                    ELSE 0
                END,
            total_read_count =
                vfs.num_of_reads,
            avg_read_stall_ms =
                CONVERT
                (
                    decimal(38, 2),
                    ISNULL
                    (
                        vfs.io_stall_read_ms /
                          CONVERT
                          (
                              decimal(38, 2),
                              NULLIF(vfs.num_of_reads, 0.)
                          ),
                        0.
                    )
                ),
            total_gb_written =
                CASE
                    WHEN vfs.num_of_bytes_written > 0
                    THEN CONVERT
                         (
                             decimal(38, 2),
                             vfs.num_of_bytes_written / 1073741824.
                         )
                    ELSE 0
                END,
            total_mb_written =
                CASE
                    WHEN vfs.num_of_bytes_written > 0
                    THEN CONVERT
                         (
                             decimal(38, 2),
                             vfs.num_of_bytes_written / 1048576.
                         )
                    ELSE 0
                END,
            total_write_count =
                vfs.num_of_writes,
            avg_write_stall_ms =
                CONVERT
                (
                    decimal(38, 2),
                    ISNULL
                    (
                        vfs.io_stall_write_ms /
                          CONVERT
                          (
                              decimal(38, 2),
                              NULLIF(vfs.num_of_writes, 0.)
                          ),
                        0.
                    )
                ),
            io_stall_read_ms,
            io_stall_write_ms,
            sample_time =
                SYSDATETIME()
        FROM sys.dm_io_virtual_file_stats
        (' +
        CASE
            WHEN @azure = 1
            THEN N'
            DB_ID()'
            ELSE N'
            NULL'
        END + N',
            NULL
        ) AS vfs
        JOIN ' +
        CONVERT
        (
            nvarchar(max),
            CASE
                WHEN @azure = 1
                THEN N'sys.database_files AS f
          ON  vfs.file_id = f.file_id
          AND vfs.database_id = DB_ID()'
                ELSE N'sys.master_files AS f
          ON  vfs.file_id = f.file_id
          AND vfs.database_id = f.database_id'
        END +
        N'
        WHERE
        (
             vfs.num_of_reads  > 0
          OR vfs.num_of_writes > 0
        )
        OPTION(MAXDOP 1, RECOMPILE);'
        );

        IF @debug = 1
        BEGIN
            PRINT SUBSTRING(@disk_check, 1, 4000);
            PRINT SUBSTRING(@disk_check, 4001, 8000);
        END;

        INSERT
            @file_metrics
        (
            server_hours_uptime,
            drive,
            database_name,
            database_file_details,
            file_size_gb,
            total_gb_read,
            total_mb_read,
            total_read_count,
            avg_read_stall_ms,
            total_gb_written,
            total_mb_written,
            total_write_count,
            avg_write_stall_ms,
            io_stall_read_ms,
            io_stall_write_ms,
            sample_time
        )
        EXECUTE sys.sp_executesql
            @disk_check;

        IF @log_to_table = 0
        BEGIN
            IF @sample_seconds = 0
            BEGIN
                WITH
                    file_metrics AS
                (
                    SELECT
                        fm.server_hours_uptime,
                        fm.drive,
                        fm.database_name,
                        fm.database_file_details,
                        fm.file_size_gb,
                        fm.avg_read_stall_ms,
                        fm.avg_write_stall_ms,
                        fm.total_gb_read,
                        fm.total_gb_written,
                        total_read_count =
                            FORMAT(fm.total_read_count, 'N0'),
                        total_write_count =
                            FORMAT(fm.total_write_count, 'N0'),
                        total_avg_stall_ms =
                            fm.avg_read_stall_ms +
                            fm.avg_write_stall_ms
                    FROM @file_metrics AS fm
                    WHERE fm.avg_read_stall_ms  > @minimum_disk_latency_ms
                    OR    fm.avg_write_stall_ms > @minimum_disk_latency_ms
                )
                SELECT
                    fm.drive,
                    fm.database_name,
                    fm.database_file_details,
                    fm.server_hours_uptime,
                    fm.file_size_gb,
                    fm.avg_read_stall_ms,
                    fm.avg_write_stall_ms,
                    fm.total_avg_stall_ms,
                    fm.total_gb_read,
                    fm.total_gb_written,
                    fm.total_read_count,
                    fm.total_write_count
                FROM file_metrics AS fm

                UNION ALL

                SELECT
                    drive = N'Nothing to see here',
                    database_name = N'By default, only >100 ms latency is reported',
                    database_file_details = N'Use the @minimum_disk_latency_ms parameter to adjust what you see',
                    server_hours_uptime = 0,
                    file_size_gb = 0,
                    avg_read_stall_ms = 0,
                    avg_write_stall_ms = 0,
                    total_avg_stall_ms = 0,
                    total_gb_read = 0,
                    total_gb_written = 0,
                    total_read_count = N'0',
                    total_write_count = N'0'
                WHERE NOT EXISTS
                (
                    SELECT
                        1/0
                    FROM file_metrics AS fm
                )
                ORDER BY
                    total_avg_stall_ms DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;

            IF
            (
                @sample_seconds > 0
            AND @pass = 1
            )
            BEGIN
                WITH
                    f AS
                (
                    SELECT
                        fm.drive,
                        fm.database_name,
                        fm.database_file_details,
                        fm.file_size_gb,
                        avg_read_stall_ms =
                            CASE
                                WHEN (fm2.total_read_count - fm.total_read_count) = 0
                                THEN 0.00
                                ELSE
                                    CONVERT
                                    (
                                        decimal(38, 2),
                                        (fm2.io_stall_read_ms - fm.io_stall_read_ms) /
                                        (fm2.total_read_count  - fm.total_read_count)
                                    )
                            END,
                        avg_write_stall_ms =
                            CASE
                                WHEN (fm2.total_write_count - fm.total_write_count) = 0
                                THEN 0.00
                                ELSE
                                    CONVERT
                                    (
                                        decimal(38, 2),
                                        (fm2.io_stall_write_ms - fm.io_stall_write_ms) /
                                        (fm2.total_write_count  - fm.total_write_count)
                                    )
                            END,
                        total_avg_stall =
                            CASE
                                WHEN (fm2.total_read_count  - fm.total_read_count) +
                                     (fm2.total_write_count - fm.total_write_count) = 0
                                THEN 0.00
                                ELSE
                                    CONVERT
                                    (
                                        decimal(38,2),
                                        (
                                            (fm2.io_stall_read_ms  - fm.io_stall_read_ms) +
                                            (fm2.io_stall_write_ms - fm.io_stall_write_ms)
                                        ) /
                                        (
                                            (fm2.total_read_count  - fm.total_read_count) +
                                            (fm2.total_write_count - fm.total_write_count)
                                        )
                                    )
                            END,
                        total_mb_read =
                            (fm2.total_mb_read - fm.total_mb_read),
                        total_mb_written =
                            (fm2.total_mb_written - fm.total_mb_written),
                        total_read_count =
                            (fm2.total_read_count - fm.total_read_count),
                        total_write_count =
                            (fm2.total_write_count - fm.total_write_count),
                        sample_time_o =
                            fm.sample_time,
                        sample_time_t =
                            fm2.sample_time
                    FROM @file_metrics AS fm
                    JOIN @file_metrics AS fm2
                      ON  fm.drive = fm2.drive
                      AND fm.database_name = fm2.database_name
                      AND fm.database_file_details = fm2.database_file_details
                      AND fm.sample_time < fm2.sample_time
                )
                SELECT
                    f.drive,
                    f.database_name,
                    f.database_file_details,
                    f.file_size_gb,
                    f.avg_read_stall_ms,
                    f.avg_write_stall_ms,
                    f.total_avg_stall,
                    total_mb_read =
                        FORMAT(f.total_mb_read, 'N0'),
                    total_mb_written =
                        FORMAT(f.total_mb_written, 'N0'),
                    total_read_count =
                        FORMAT(f.total_read_count, 'N0'),
                    total_write_count =
                        FORMAT(f.total_write_count, 'N0'),
                    sample_seconds =
                        DATEDIFF
                        (
                            SECOND,
                            f.sample_time_o,
                            f.sample_time_t
                        )
                FROM f
                WHERE
                (
                     f.total_read_count  > 0
                  OR f.total_write_count > 0
                )
                ORDER BY
                    f.total_avg_stall DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;
        END;

        IF @log_to_table = 1
        BEGIN

           SELECT
               fm.*
           INTO #file_metrics
           FROM @file_metrics AS fm
           OPTION(RECOMPILE);

           SET @insert_sql = N'
               SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
               INSERT INTO ' + @log_table_file_metrics + N'
               (
                   server_hours_uptime,
                   drive,
                   database_name,
                   database_file_details,
                   file_size_gb,
                   total_gb_read,
                   total_mb_read,
                   total_read_count,
                   avg_read_stall_ms,
                   total_gb_written,
                   total_mb_written,
                   total_write_count,
                   avg_write_stall_ms,
                   io_stall_read_ms,
                   io_stall_write_ms
               )
               SELECT
                   fm.server_hours_uptime,
                   fm.drive,
                   fm.database_name,
                   fm.database_file_details,
                   fm.file_size_gb,
                   fm.total_gb_read,
                   fm.total_mb_read,
                   fm.total_read_count,
                   fm.avg_read_stall_ms,
                   fm.total_gb_written,
                   fm.total_mb_written,
                   fm.total_write_count,
                   fm.avg_write_stall_ms,
                   fm.io_stall_read_ms,
                   fm.io_stall_write_ms
               FROM #file_metrics AS fm;
               ';

           IF @debug = 1
           BEGIN
               PRINT @insert_sql;
           END;

           EXECUTE sys.sp_executesql
               @insert_sql;

           IF OBJECT_ID(N'tempdb..#file_metrics', N'U') IS NOT NULL
           BEGIN
               DROP TABLE #file_metrics;
           END;
        END;
    END; /*End file stats*/

    /*
    This section looks at perfmon stuff I care about
    */
    IF @skip_perfmon = 0
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking perfmon counters', 0, 1) WITH NOWAIT;
        END;

        WITH
            p AS
        (
            SELECT
                sample_time =
                    CASE
                        WHEN @sample_seconds = 0
                        THEN
                            (
                                SELECT
                                    dosi.sqlserver_start_time
                                FROM sys.dm_os_sys_info AS dosi
                            )
                        ELSE SYSDATETIME()
                    END,
                object_name =
                    RTRIM(LTRIM(dopc.object_name)),
                counter_name =
                    RTRIM(LTRIM(dopc.counter_name)),
                counter_name_clean =
                    REPLACE(RTRIM(LTRIM(dopc.counter_name)),' (ms)', ''),
                instance_name =
                    RTRIM(LTRIM(dopc.instance_name)),
                dopc.cntr_value,
                dopc.cntr_type
            FROM sys.dm_os_performance_counters AS dopc
        )
        INSERT
            @dm_os_performance_counters
        (
            sample_time,
            object_name,
            counter_name,
            counter_name_clean,
            instance_name,
            cntr_value,
            cntr_type
        )
        SELECT
            p.sample_time,
            p.object_name,
            p.counter_name,
            p.counter_name_clean,
            instance_name =
                CASE
                    WHEN LEN(p.instance_name) > 0
                    THEN p.instance_name
                    ELSE N'_Total'
                END,
            p.cntr_value,
            p.cntr_type
        FROM p
        WHERE p.cntr_value > 0
        AND   p.object_name LIKE @prefix
        AND   p.instance_name NOT IN
        (
            N'internal', N'master', N'model', N'msdb', N'model_msdb',
            N'model_replicatedmaster', N'mssqlsystemresource'
        )
        AND   p.counter_name IN
        (
            N'Forwarded Records/sec', N'Table Lock Escalations/sec', N'Page reads/sec', N'Page writes/sec', N'Checkpoint pages/sec', N'Requests completed/sec',
            N'Transactions/sec', N'Lock Requests/sec', N'Lock Wait Time (ms)', N'Lock Waits/sec', N'Number of Deadlocks/sec', N'Log Flushes/sec', N'Page lookups/sec',
            N'Granted Workspace Memory (KB)', N'Lock Memory (KB)', N'Memory Grants Pending', N'SQL Cache Memory (KB)', N'Background writer pages/sec',
            N'Stolen Server Memory (KB)', N'Target Server Memory (KB)', N'Total Server Memory (KB)', N'Lazy writes/sec', N'Readahead pages/sec',
            N'Batch Requests/sec', N'SQL Compilations/sec', N'SQL Re-Compilations/sec', N'Longest Transaction Running Time', N'Log Bytes Flushed/sec',
            N'Lock waits', N'Log buffer waits', N'Log write waits', N'Memory grant queue waits', N'Network IO waits', N'Log Flush Write Time (ms)',
            N'Non-Page latch waits', N'Page IO latch waits', N'Page latch waits', N'Thread-safe memory objects waits', N'Wait for the worker',
            N'Active parallel threads', N'Active requests', N'Blocked tasks', N'Query optimizations/sec', N'Queued requests', N'Reduced memory grants/sec',
            N'Version Store Size (KB)', N'Free Space in tempdb (KB)', N'Active Temp Tables', N'Processes blocked', N'Full Scans/sec', N'Index Searches/sec',
            N'Page Splits/sec', N'Free list stalls/sec', N'Workfiles Created/sec', N'Worktables Created/sec', N'Temp Tables Creation Rate', N'Version Generation rate (KB/s)',
            N'Version Cleanup rate (KB/s)', N'Lock Timeouts/sec'
        );


        IF @log_to_table = 0
        BEGIN
            IF @sample_seconds = 0
            BEGIN
                WITH
                    p AS
                (
                    SELECT
                        server_hours_uptime =
                            (
                                SELECT
                                    DATEDIFF
                                    (
                                        HOUR,
                                        dopc.sample_time,
                                        SYSDATETIME()
                                    )
                            ),
                        dopc.object_name,
                        dopc.counter_name,
                        dopc.instance_name,
                        dopc.cntr_value,
                        total =
                            FORMAT(dopc.cntr_value, 'N0'),
                        total_per_second =
                            FORMAT
                            (
                                dopc.cntr_value /
                                ISNULL
                                (
                                    NULLIF
                                    (
                                        DATEDIFF
                                        (
                                            SECOND,
                                            dopc.sample_time,
                                            SYSDATETIME()
                                        ),
                                        0
                                    ),
                                    1
                                ),
                                'N0'
                            )
                    FROM @dm_os_performance_counters AS dopc
                )
                SELECT
                    p.object_name,
                    p.counter_name,
                    p.instance_name,
                    p.server_hours_uptime,
                    p.total,
                    p.total_per_second
                FROM p
                WHERE p.total_per_second <> N'0'
                ORDER BY
                    p.object_name,
                    p.counter_name,
                    p.cntr_value DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;

            IF
            (
                @sample_seconds > 0
            AND @pass = 1
            )
            BEGIN
                WITH
                    p AS
                (
                    SELECT
                        dopc.object_name,
                        dopc.counter_name,
                        dopc.instance_name,
                        first_cntr_value =
                            FORMAT(dopc.cntr_value, 'N0'),
                        second_cntr_value =
                            FORMAT(dopc2.cntr_value, 'N0'),
                        total_difference =
                            FORMAT((dopc2.cntr_value - dopc.cntr_value), 'N0'),
                        total_difference_per_second =
                            FORMAT((dopc2.cntr_value - dopc.cntr_value) /
                            ISNULL(DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time), 1), 'N0'),
                        sample_seconds =
                            DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time),
                        first_sample_time =
                            dopc.sample_time,
                        second_sample_time =
                            dopc2.sample_time,
                        total_difference_i =
                            (dopc2.cntr_value - dopc.cntr_value)
                    FROM @dm_os_performance_counters AS dopc
                    JOIN @dm_os_performance_counters AS dopc2
                      ON  dopc.object_name = dopc2.object_name
                      AND dopc.counter_name = dopc2.counter_name
                      AND dopc.instance_name = dopc2.instance_name
                      AND dopc.sample_time < dopc2.sample_time
                    WHERE (dopc2.cntr_value - dopc.cntr_value) <> 0
                )
                SELECT
                    p.object_name,
                    p.counter_name,
                    p.instance_name,
                    p.first_cntr_value,
                    p.second_cntr_value,
                    p.total_difference,
                    p.total_difference_per_second,
                    p.sample_seconds
                FROM p
                ORDER BY
                    p.object_name,
                    p.counter_name,
                    p.total_difference_i DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;
        END;

       IF @log_to_table = 1
       BEGIN

           SELECT
               dopc.*
           INTO #dm_os_performance_counters
           FROM @dm_os_performance_counters AS dopc
           OPTION(RECOMPILE);

           SET @insert_sql = N'
               SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
               INSERT INTO ' + @log_table_perfmon + N'
               (
                   object_name,
                   counter_name,
                   counter_name_clean,
                   instance_name,
                   cntr_value,
                   cntr_type
               )
               SELECT
                   dopc.object_name,
                   dopc.counter_name,
                   dopc.counter_name_clean,
                   dopc.instance_name,
                   dopc.cntr_value,
                   dopc.cntr_type
               FROM #dm_os_performance_counters AS dopc;
               ';

           IF @debug = 1
           BEGIN
               PRINT @insert_sql;
           END;

           EXECUTE sys.sp_executesql
               @insert_sql;


           IF OBJECT_ID(N'tempdb..#dm_os_performance_counters', N'U') IS NOT NULL
           BEGIN
               DROP TABLE #dm_os_performance_counters;
           END;
       END;
    END; /*End Perfmon*/

    /*
    This section looks at tempdb config and usage
    */
    IF
    (
        @azure = 0
    AND @what_to_check = 'all'
    AND @pass = 1
    AND @log_to_table = 0
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking tempdb config and usage', 0, 1) WITH NOWAIT;
        END;

        SELECT
            tempdb_info =
                (
                    SELECT
                        tempdb_configuration =
                            (
                                SELECT
                                    total_data_files =
                                        COUNT_BIG(*),
                                    min_size_gb =
                                        MIN(mf.size * 8) / 1024 / 1024,
                                    max_size_gb =
                                        MAX(mf.size * 8) / 1024 / 1024,
                                    min_growth_increment_gb =
                                        MIN(mf.growth * 8) / 1024 / 1024,
                                    max_growth_increment_gb =
                                        MAX(mf.growth * 8) / 1024 / 1024,
                                    scheduler_total_count =
                                        (
                                            SELECT
                                                osi.cpu_count
                                            FROM sys.dm_os_sys_info AS osi
                                        )
                                FROM sys.master_files AS mf
                                WHERE mf.database_id = 2
                                AND   mf.type = 0
                                FOR XML
                                    PATH('tempdb_configuration'),
                                    TYPE
                            ),
                        tempdb_space_used =
                            (
                                SELECT
                                    free_space_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(d.unallocated_extent_page_count * 8.) / 1024. / 1024.
                                        ),
                                    user_objects_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(d.user_object_reserved_page_count * 8.) / 1024. / 1024.
                                        ),
                                    version_store_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(d.version_store_reserved_page_count * 8.) / 1024. / 1024.
                                        ),
                                    internal_objects_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(d.internal_object_reserved_page_count * 8.) / 1024. / 1024.
                                        )
                                FROM tempdb.sys.dm_db_file_space_usage AS d
                                WHERE d.database_id = 2
                                FOR XML
                                    PATH('tempdb_space_used'),
                                    TYPE
                            ),
                        tempdb_query_activity =
                            (
                                SELECT
                                    t.session_id,
                                    tempdb_allocations_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(t.tempdb_allocations * 8.) / 1024. / 1024.
                                        ),
                                    tempdb_current_gb =
                                        CONVERT
                                        (
                                            decimal(38, 2),
                                            SUM(t.tempdb_current * 8.) / 1024. / 1024.
                                        )
                                FROM
                                (
                                    SELECT
                                        t.session_id,
                                        tempdb_allocations =
                                            t.user_objects_alloc_page_count +
                                            t.internal_objects_alloc_page_count,
                                        tempdb_current =
                                            t.user_objects_alloc_page_count +
                                            t.internal_objects_alloc_page_count -
                                            t.user_objects_dealloc_page_count -
                                            t.internal_objects_dealloc_page_count
                                    FROM sys.dm_db_task_space_usage AS t

                                    UNION ALL

                                    SELECT
                                        s.session_id,
                                        tempdb_allocations =
                                            s.user_objects_alloc_page_count +
                                            s.internal_objects_alloc_page_count,
                                        tempdb_current =
                                            s.user_objects_alloc_page_count +
                                            s.internal_objects_alloc_page_count -
                                            s.user_objects_dealloc_page_count -
                                            s.internal_objects_dealloc_page_count
                                    FROM sys.dm_db_session_space_usage AS s
                                ) AS t
                                WHERE t.session_id > 50
                                GROUP BY
                                    t.session_id
                                HAVING
                                    (SUM(t.tempdb_allocations) * 8.) / 1024. > 0.
                                ORDER BY
                                    SUM(t.tempdb_allocations) DESC
                                FOR XML
                                    PATH('tempdb_query_activity'),
                                    TYPE

                            )
                        FOR XML
                            PATH('tempdb'),
                            TYPE
                )
        OPTION(RECOMPILE, MAXDOP 1);
    END; /*End tempdb check*/

    /*Memory info, utilization and usage*/
    IF
    (
        @what_to_check IN ('all', 'memory')
    AND @pass = 1
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking memory pressure', 0, 1) WITH NOWAIT;
        END;

        /*
        See buffer pool size, along with stolen memory
        and top non-buffer pool consumers
        */
        SET @pool_sql += N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        SELECT
            memory_source =
                N''Buffer Pool Memory'',
            memory_consumer =
                domc.type,
            memory_consumed_gb =
                CONVERT
                (
                    decimal(38, 2),
                    SUM
                    (
                        ' +
            CONVERT
               (
                   nvarchar(max),
                          CASE @pages_kb
                               WHEN 1
                               THEN
                        N'domc.pages_kb + '
                               ELSE
                        N'domc.single_pages_kb +
                        domc.multi_pages_kb + '
                          END
               )
                        + N'
                        domc.virtual_memory_committed_kb +
                        domc.awe_allocated_kb +
                        domc.shared_memory_committed_kb
                    ) / 1024. / 1024.
                )
        FROM sys.dm_os_memory_clerks AS domc
        WHERE domc.type = N''MEMORYCLERK_SQLBUFFERPOOL''
        AND   domc.memory_node_id < 64
        GROUP BY
            domc.type

        UNION ALL

        SELECT
            memory_source =
                N''Non-Buffer Pool Memory: Total'',
            memory_consumer =
                REPLACE
                (
                    dopc.counter_name,
                    N'' (KB)'',
                    N''''
                ),
            memory_consumed_gb =
                CONVERT
                (
                    decimal(38, 2),
                    dopc.cntr_value / 1024. / 1024.
                )
        FROM sys.dm_os_performance_counters AS dopc
        WHERE dopc.counter_name LIKE N''Stolen Server%''

        UNION ALL

        SELECT
            memory_source =
                N''Non-Buffer Pool Memory: Top Five'',
            memory_consumer =
                x.type,
            memory_consumed_gb =
                x.memory_used_gb
        FROM
        (
            SELECT TOP (5)
                domc.type,
                memory_used_gb =
                    CONVERT
                    (
                        decimal(38, 2),
                        SUM
                        (
                        ' + CONVERT
                            (
                                nvarchar(max),
                            CASE @pages_kb
                                 WHEN 1
                                 THEN
                        N'    domc.pages_kb '
                                 ELSE
                        N'    domc.single_pages_kb +
                            domc.multi_pages_kb '
                            END + N'
                        ) / 1024. / 1024.
                    )
            FROM sys.dm_os_memory_clerks AS domc
            WHERE domc.type <> N''MEMORYCLERK_SQLBUFFERPOOL''
            GROUP BY
                domc.type
            HAVING
               SUM
               (
                   ' +
                      CASE @pages_kb
                           WHEN 1
                           THEN
                    N'domc.pages_kb '
                           ELSE
                    N'domc.single_pages_kb +
                    domc.multi_pages_kb '
                      END ) + N'
               ) / 1024. / 1024. > 0.
            ORDER BY
                memory_used_gb DESC
        ) AS x
        OPTION(MAXDOP 1, RECOMPILE);
        ';

        IF @debug = 1
        BEGIN
            PRINT @pool_sql;
        END;

        IF @log_to_table = 0
        BEGIN
            EXECUTE sys.sp_executesql
                @pool_sql;
        END;

        IF @log_to_table = 1
        BEGIN
            SET @insert_sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                INSERT INTO ' + @log_table_memory_consumers + N'
                (
                    memory_source,
                    memory_consumer,
                    memory_consumed_gb
                )
                ' +
                REPLACE
                (
                    @pool_sql,
                    N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;',
                    N''
                );

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql;
        END;

        /*Checking total database size*/
        IF @azure = 1
        BEGIN
            SELECT
                @database_size_out = N'
                SELECT
                    @database_size_out_gb =
                        SUM
                        (
                            CONVERT
                            (
                                bigint,
                                df.size
                            )
                        ) * 8 / 1024 / 1024
                FROM sys.database_files AS df
                OPTION(MAXDOP 1, RECOMPILE);';
        END;
        IF @azure = 0
        BEGIN
            SELECT
                @database_size_out = N'
                SELECT
                    @database_size_out_gb =
                        SUM
                        (
                            CONVERT
                            (
                                bigint,
                                mf.size
                            )
                        ) * 8 / 1024 / 1024
                FROM sys.master_files AS mf
                WHERE mf.database_id > 4
                OPTION(MAXDOP 1, RECOMPILE);';
        END;

        EXECUTE sys.sp_executesql
            @database_size_out,
          N'@database_size_out_gb nvarchar(10) OUTPUT',
            @database_size_out_gb OUTPUT;

        /*Check physical memory in the server*/
        IF @azure = 0
        BEGIN
            SELECT
                @total_physical_memory_gb =
                    CEILING(dosm.total_physical_memory_kb / 1024. / 1024.)
                FROM sys.dm_os_sys_memory AS dosm
                OPTION(MAXDOP 1, RECOMPILE);
        END;
        IF @azure = 1
        BEGIN
            SELECT
                @total_physical_memory_gb =
                    SUM(osi.committed_target_kb / 1024. / 1024.)
            FROM sys.dm_os_sys_info AS osi
            OPTION(MAXDOP 1, RECOMPILE);
        END;

        /*Checking for low memory indicators*/
        SELECT
            @low_memory =
                x.low_memory
        FROM
        (
            SELECT
                sample_time =
                    CONVERT
                    (
                        datetime,
                        DATEADD
                        (
                            SECOND,
                            (t.timestamp - osi.ms_ticks) / 1000,
                            SYSDATETIME()
                        )
                    ),
                notification_type =
                    t.record.value('(/Record/ResourceMonitor/Notification)[1]', 'varchar(50)'),
                indicators_process =
                    t.record.value('(/Record/ResourceMonitor/IndicatorsProcess)[1]', 'integer'),
                indicators_system =
                    t.record.value('(/Record/ResourceMonitor/IndicatorsSystem)[1]', 'integer'),
                physical_memory_available_gb =
                    t.record.value('(/Record/MemoryRecord/AvailablePhysicalMemory)[1]', 'bigint') / 1024 / 1024,
                virtual_memory_available_gb =
                    t.record.value('(/Record/MemoryRecord/AvailableVirtualAddressSpace)[1]', 'bigint') / 1024 / 1024
            FROM sys.dm_os_sys_info AS osi
            CROSS JOIN
            (
                SELECT
                    dorb.timestamp,
                    record =
                        CONVERT(xml, dorb.record)
                FROM sys.dm_os_ring_buffers AS dorb
                WHERE dorb.ring_buffer_type = N'RING_BUFFER_RESOURCE_MONITOR'
            ) AS t
            WHERE
              (
                  t.record.exist('(Record/ResourceMonitor/Notification[. = "RESOURCE_MEMPHYSICAL_LOW"])') = 1
               OR t.record.exist('(Record/ResourceMonitor/Notification[. = "RESOURCE_MEMVIRTUAL_LOW"])') = 1
              )
            AND
              (
                  t.record.exist('(Record/ResourceMonitor/IndicatorsProcess[. > 1])') = 1
               OR t.record.exist('(Record/ResourceMonitor/IndicatorsSystem[. > 1])') = 1
              )
            ORDER BY
                sample_time DESC
            FOR XML
                PATH('memory'),
                TYPE
        ) AS x (low_memory)
        OPTION(MAXDOP 1, RECOMPILE);

        IF @low_memory IS NULL
        BEGIN
            SELECT
                @low_memory =
                (
                    SELECT
                        N'No RESOURCE_MEMPHYSICAL_LOW indicators detected'
                    FOR XML
                        PATH(N'memory'),
                        TYPE
                );
        END;

        SELECT
            @cache_sql += N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

        SELECT
            @cache_xml =
                x.c
        FROM
        (
            SELECT TOP (20)
                name =
                    CASE
                        WHEN domcc.name LIKE N''%UserStore%''
                        THEN N''UserStore''
                        WHEN domcc.name LIKE N''ObjPerm%''
                        THEN N''ObjPerm''
                        ELSE domcc.name
                    END,
                pages_gb =
                    CONVERT
                    (
                        decimal(38, 2),
                        SUM
                        (' +
                            CASE
                                @pages_kb
                                WHEN 1
                                THEN N'
                            domcc.pages_kb'
                                ELSE N'
                            domcc.single_pages_kb +
                            domcc.multi_pages_kb'
                            END + N'
                        ) / 1024. / 1024.
                    ),
                pages_in_use_gb =
                    ISNULL
                    (
                        CONVERT
                        (
                            decimal(38, 2),
                            SUM
                            (' +
                                CASE
                                    @pages_kb
                                    WHEN 1
                                    THEN N'
                                domcc.pages_in_use_kb'
                                    ELSE N'
                                domcc.single_pages_in_use_kb +
                                domcc.multi_pages_in_use_kb'
                                END + N'
                            ) / 1024. / 1024.
                        ),
                        N''0.00''
                    ),
                entries_count =
                    FORMAT(SUM(domcc.entries_count), ''N0''),
                entries_in_use_count =
                    FORMAT(SUM(domcc.entries_in_use_count), ''N0'')
            FROM sys.dm_os_memory_cache_counters AS domcc
            WHERE domcc.name NOT IN
            (
                N''msdb'',
                N''model_replicatedmaster'',
                N''model_msdb'',
                N''model'',
                N''master'',
                N''mssqlsystemresource''
            )
            GROUP BY
                    CASE
                        WHEN domcc.name LIKE N''%UserStore%''
                        THEN N''UserStore''
                        WHEN domcc.name LIKE N''ObjPerm%''
                        THEN N''ObjPerm''
                        ELSE domcc.name
                    END
            HAVING
                SUM
                (' +
                    CASE
                        @pages_kb
                        WHEN 1
                        THEN N'
                    domcc.pages_in_use_kb'
                        ELSE N'
                    domcc.single_pages_in_use_kb +
                    domcc.multi_pages_in_use_kb'
                    END + N'
                ) / 1024. / 1024. > 0
            ORDER BY
                pages_gb DESC
            FOR XML
                PATH(''cache''),
                TYPE
        ) AS x (c)
        OPTION(MAXDOP 1, RECOMPILE);
        ';

        IF @debug = 1
        BEGIN
            PRINT @cache_sql;
        END;

        IF @log_to_table = 0
        BEGIN
        EXECUTE sys.sp_executesql
            @cache_sql,
          N'@cache_xml xml OUTPUT',
            @cache_xml OUTPUT;
        END;

        IF @cache_xml IS NULL
        BEGIN
            SELECT
                @cache_xml =
                (
                    SELECT
                        N'No significant caches detected'
                    FOR XML
                        PATH(N'cache'),
                        TYPE
                );
        END;

        IF @health_history = 1
        BEGIN
            EXECUTE sys.sp_executesql
                N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

                SELECT
                    @health_history_xml =
                    (
                        SELECT TOP (9223372036854775807)
                            hh.snapshot_time,
                            hh.severity_level,
                            hh.allocation_potential_memory_mb,
                            hh.reclaimable_cache_memory_mb,
                            oj.clerk_type,
                            oj.pages_allocated_kb
                        FROM sys.dm_os_memory_health_history AS hh
                        CROSS APPLY
                            OPENJSON
                                (hh.top_memory_clerks)
                            WITH
                            (
                                clerk_type sysname N''$.clerk_type'',
                                pages_allocated_kb bigint N''$.pages_allocated_kb''
                            ) AS oj
                        WHERE hh.severity_level > 1
                        ORDER BY
                            hh.snapshot_time DESC,
                            oj.pages_allocated_kb DESC
                        FOR XML
                            PATH(''health_history''),
                            TYPE
                    );
                ',
                N'@health_history_xml xml OUTPUT',
                @health_history_xml OUTPUT;
        END;

        IF @health_history_xml IS NULL
        BEGIN
            SELECT
                @health_history_xml =
                (
                    SELECT
                        N'No memory health history available'
                    FOR XML
                        PATH(N'history'),
                        TYPE
                );
        END;

        IF @log_to_table = 0
        BEGIN
            SELECT
                low_memory =
                    @low_memory,
                cache_memory =
                    @cache_xml,
                memory_health_history =
                    @health_history_xml;
        END;

        SELECT
            @memory_grant_cap =
            (
                SELECT
                    group_name =
                        drgwg.name,
                    max_grant_percent =
                        drgwg.request_max_memory_grant_percent
                FROM sys.dm_resource_governor_workload_groups AS drgwg
                FOR XML
                    PATH(''),
                    TYPE
            )
        OPTION(MAXDOP 1, RECOMPILE);

        IF @memory_grant_cap IS NULL
        BEGIN
            SELECT
                @memory_grant_cap =
                (

                    SELECT
                        x.*
                    FROM
                    (
                        SELECT
                            group_name =
                                N'internal',
                            max_grant_percent =
                                25

                        UNION ALL

                        SELECT
                            group_name =
                                N'default',
                            max_grant_percent =
                                25
                    ) AS x
                    FOR XML
                        PATH(''),
                        TYPE
                );
        END;

        SELECT
            @resource_semaphores += N'
        SELECT
            deqrs.resource_semaphore_id,
            total_database_size_gb =
                @database_size_out_gb,
            total_physical_memory_gb =
                @total_physical_memory_gb,
            max_server_memory_gb =
                (
                    SELECT
                        CONVERT
                        (
                            bigint,
                            c.value_in_use
                        )
                    FROM sys.configurations AS c
                    WHERE c.name = N''max server memory (MB)''
                ) / 1024,
            max_memory_grant_cap =
                @memory_grant_cap,
            memory_model =
                (
                    SELECT
                        osi.sql_memory_model_desc
                    FROM sys.dm_os_sys_info AS osi
                ),
            target_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    (deqrs.target_memory_kb / 1024. / 1024.)
                ),
            max_target_memory_gb =
                CONVERT(
                    decimal(38, 2),
                    (deqrs.max_target_memory_kb / 1024. / 1024.)
                ),
            total_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    (deqrs.total_memory_kb / 1024. / 1024.)
                ),
            available_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    (deqrs.available_memory_kb / 1024. / 1024.)
                ),
            granted_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    (deqrs.granted_memory_kb / 1024. / 1024.)
                ),
            used_memory_gb =
                CONVERT
                (
                    decimal(38, 2),
                    (deqrs.used_memory_kb / 1024. / 1024.)
                ),
            deqrs.grantee_count,
            deqrs.waiter_count,
            deqrs.timeout_error_count,
            deqrs.forced_grant_count,
            wg.total_reduced_memory_grant_count,
            deqrs.pool_id
        FROM sys.dm_exec_query_resource_semaphores AS deqrs
        CROSS APPLY
        (
            SELECT TOP (1)
                total_reduced_memory_grant_count =
                    wg.total_reduced_memgrant_count
            FROM sys.dm_resource_governor_workload_groups AS wg
            WHERE wg.pool_id = deqrs.pool_id
            ORDER BY
                wg.total_reduced_memgrant_count DESC
        ) AS wg
        WHERE deqrs.max_target_memory_kb IS NOT NULL
        ORDER BY
            deqrs.pool_id
        OPTION(MAXDOP 1, RECOMPILE);
        ';

        IF @log_to_table = 0
        BEGIN
            EXECUTE sys.sp_executesql
                @resource_semaphores,
              N'@database_size_out_gb nvarchar(10),
                @total_physical_memory_gb bigint,
                @memory_grant_cap xml',
                @database_size_out_gb,
                @total_physical_memory_gb,
                @memory_grant_cap;
        END

        IF @log_to_table = 1
        BEGIN
            SET @insert_sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                INSERT INTO ' + @log_table_memory + N'
                (
                    resource_semaphore_id,
                    total_database_size_gb,
                    total_physical_memory_gb,
                    max_server_memory_gb,
                    max_memory_grant_cap,
                    memory_model,
                    target_memory_gb,
                    max_target_memory_gb,
                    total_memory_gb,
                    available_memory_gb,
                    granted_memory_gb,
                    used_memory_gb,
                    grantee_count,
                    waiter_count,
                    timeout_error_count,
                    forced_grant_count,
                    total_reduced_memory_grant_count,
                    pool_id
                )' +
                @resource_semaphores;

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql,
              N'@database_size_out_gb nvarchar(10),
                @total_physical_memory_gb bigint,
                @memory_grant_cap xml',
                @database_size_out_gb,
                @total_physical_memory_gb,
                @memory_grant_cap;
        END;
    END; /*End memory checks*/

    /*
    Track down queries currently asking for memory grants
    */
    IF
    (
        @skip_queries = 0
    AND @what_to_check IN ('all', 'memory')
    AND @pass = 1
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking queries with memory grants', 0, 1) WITH NOWAIT;
        END;

        SET @mem_sql += N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
        SET LOCK_TIMEOUT 1000;

        SELECT
            deqmg.session_id,
            database_name =
                DB_NAME(deqp.dbid),
            [dd hh:mm:ss.mss] =
                CASE
                    WHEN e.elapsed_time_ms < 0
                    THEN RIGHT(REPLICATE(''0'', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
                         '' '' +
                         RIGHT(CONVERT(varchar(30), DATEADD(second, (-1 * e.elapsed_time_ms), 0), 120), 9) +
                         ''.000''
                    ELSE RIGHT(REPLICATE(''0'', 2) +
                         CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
                         '' '' +
                         RIGHT(CONVERT(varchar(30), DATEADD(second, e.elapsed_time_ms / 1000, 0), 120), 9) +
                         ''.'' +
                         RIGHT(''000'' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
                END,
            query_text =
                (
                    SELECT
                        [processing-instruction(query)] =
                            SUBSTRING
                            (
                                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                REPLACE(
                                    dest.text COLLATE Latin1_General_BIN2,
                                NCHAR(31),N''?''),NCHAR(30),N''?''),NCHAR(29),N''?''),NCHAR(28),N''?''),NCHAR(27),N''?''),NCHAR(26),N''?''),NCHAR(25),N''?''),NCHAR(24),N''?''),NCHAR(23),N''?''),NCHAR(22),N''?''),
                                NCHAR(21),N''?''),NCHAR(20),N''?''),NCHAR(19),N''?''),NCHAR(18),N''?''),NCHAR(17),N''?''),NCHAR(16),N''?''),NCHAR(15),N''?''),NCHAR(14),N''?''),NCHAR(12),N''?''),
                                NCHAR(11),N''?''),NCHAR(8),N''?''),NCHAR(7),N''?''),NCHAR(6),N''?''),NCHAR(5),N''?''),NCHAR(4),N''?''),NCHAR(3),N''?''),NCHAR(2),N''?''),NCHAR(1),N''?''),NCHAR(0),N''''),
                                N''<?'', N''??''), N''?>'', N''??''),
                                (der.statement_start_offset / 2) + 1,
                                (
                                    (
                                        CASE
                                            der.statement_end_offset
                                            WHEN -1
                                            THEN DATALENGTH(dest.text)
                                            ELSE der.statement_end_offset
                                        END
                                        - der.statement_start_offset
                                    ) / 2
                                ) + 1
                            )
                            FOR XML
                                PATH(''''),
                                TYPE
                ),'
            + CONVERT
              (
                  nvarchar(max),
              CASE
                  WHEN @skip_plan_xml = 0
                  THEN N'
            query_plan =
                 CASE
                     WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
                     THEN TRY_CAST(deqp.query_plan AS xml)
                     WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
                     THEN
                         (
                             SELECT
                                 [processing-instruction(query_plan)] =
                                     N''-- '' + NCHAR(13) + NCHAR(10) +
                                     N''-- This is a huge query plan.'' + NCHAR(13) + NCHAR(10) +
                                     N''-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.'' + NCHAR(13) + NCHAR(10) +
                                     NCHAR(13) + NCHAR(10) +
                                     REPLACE(deqp.query_plan, N''<RelOp'', NCHAR(13) + NCHAR(10) + N''<RelOp'') +
                                     NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
                             FOR XML PATH(N''''),
                                     TYPE
                         )
                 END,' +
                  CASE
                      WHEN @live_plans = 1
                      THEN N'
            live_query_plan =
                deqs.query_plan,'
                      ELSE N''
                  END
              END +
                      N'
            deqmg.request_time,
            deqmg.grant_time,
            wait_time_seconds =
                (deqmg.wait_time_ms / 1000.),
            requested_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.requested_memory_kb / 1024. / 1024.)),
            granted_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.granted_memory_kb / 1024. / 1024.)),
            used_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.used_memory_kb / 1024. / 1024.)),
            max_used_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.max_used_memory_kb / 1024. / 1024.)),
            ideal_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.ideal_memory_kb / 1024. / 1024.)),
            required_memory_gb =
                CONVERT(decimal(38, 2), (deqmg.required_memory_kb / 1024. / 1024.)),
            deqmg.queue_id,
            deqmg.wait_order,
            deqmg.is_next_candidate,
            waits.wait_type,
            wait_duration_seconds =
                (waits.wait_duration_ms / 1000.),
            deqmg.dop,' +
                CASE
                    WHEN @helpful_new_columns = 1
                    THEN N'
            deqmg.reserved_worker_count,
            deqmg.used_worker_count,'
                    ELSE N''
                END + N'
            deqmg.plan_handle
        FROM sys.dm_exec_query_memory_grants AS deqmg
        LEFT JOIN sys.dm_exec_requests AS der
          ON der.session_id = deqmg.session_id
        OUTER APPLY
        (
            SELECT
                elapsed_time_ms =
                    CASE
                        WHEN DATEDIFF(HOUR, der.start_time, SYSDATETIME()) > 576
                        THEN DATEDIFF(SECOND, SYSDATETIME(), der.start_time)
                        ELSE DATEDIFF(MILLISECOND, der.start_time, SYSDATETIME())
                    END
        ) AS e
        OUTER APPLY
        (
            SELECT TOP (1)
                dowt.*
            FROM sys.dm_os_waiting_tasks AS dowt
            WHERE dowt.session_id = deqmg.session_id
            ORDER BY
                dowt.wait_duration_ms DESC
        ) AS waits
        OUTER APPLY sys.dm_exec_text_query_plan
        (
            deqmg.plan_handle,
            der.statement_start_offset,
            der.statement_end_offset
        ) AS deqp
        OUTER APPLY sys.dm_exec_sql_text(COALESCE(deqmg.sql_handle, deqmg.plan_handle)) AS dest' +
            CASE
                WHEN @live_plans = 1
                THEN N'
        OUTER APPLY sys.dm_exec_query_statistics_xml(deqmg.session_id) AS deqs'
                ELSE N''
            END +
       N'
        WHERE deqmg.session_id <> @@SPID
        ORDER BY
            requested_memory_gb DESC,
            deqmg.request_time
        OPTION(MAXDOP 1, RECOMPILE);

        SET LOCK_TIMEOUT -1;
        '
                  );

        IF @debug = 1
        BEGIN
            PRINT SUBSTRING(@mem_sql, 1, 4000);
            PRINT SUBSTRING(@mem_sql, 4001, 8000);
        END;

        IF @log_to_table = 0
        BEGIN
        EXECUTE sys.sp_executesql
            @mem_sql;
        END

        IF @log_to_table = 1
        BEGIN
            SET @insert_sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                INSERT INTO ' + @log_table_memory_queries + N'
                (
                    session_id,
                    database_name,
                    duration,
                    sql_text,
                    query_plan_xml' +
                    CASE
                        WHEN @live_plans = 1
                        THEN N',
                    live_query_plan'
                        ELSE N''
                    END + N',
                    request_time,
                    grant_time,
                    wait_time_seconds,
                    requested_memory_gb,
                    granted_memory_gb,
                    used_memory_gb,
                    max_used_memory_gb,
                    ideal_memory_gb,
                    required_memory_gb,
                    queue_id,
                    wait_order,
                    is_next_candidate,
                    wait_type,
                    wait_duration_seconds,
                    dop' +
                    CASE
                        WHEN @helpful_new_columns = 1
                        THEN N',
                    reserved_worker_count,
                    used_worker_count'
                        ELSE N''
                    END + N',
                    plan_handle
                ) ' +
                REPLACE
                (
                    REPLACE
                    (
                        REPLACE
                        (
                            @mem_sql,
                            N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;',
                            N''
                        ),
                        N'SET LOCK_TIMEOUT 1000;',
                        N''
                    ),
                    N'SET LOCK_TIMEOUT -1;',
                    N''
                );

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql;
        END;
    END;

    /*
    Looking at CPU config and indicators
    */
    IF
    (
        @what_to_check IN ('all', 'cpu')
    AND @pass = 1
    )
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR('Checking CPU config', 0, 1) WITH NOWAIT;
        END;

        IF @helpful_new_columns = 1
        BEGIN
            IF @debug = 1
            BEGIN
                PRINT @reserved_worker_count;
            END;

            EXECUTE sys.sp_executesql
                @reserved_worker_count,
              N'@reserved_worker_count_out varchar(10) OUTPUT',
                @reserved_worker_count_out OUTPUT;
        END;

        SELECT
            @cpu_details_columns += N'' +
                CASE
                    WHEN ac.name = N'socket_count'
                    THEN N'                osi.socket_count, ' + NCHAR(10)
                    WHEN ac.name = N'numa_node_count'
                    THEN N'                osi.numa_node_count, ' + NCHAR(10)
                    WHEN ac.name = N'cpu_count'
                    THEN N'                osi.cpu_count, ' + NCHAR(10)
                    WHEN ac.name = N'cores_per_socket'
                    THEN N'                osi.cores_per_socket, ' + NCHAR(10)
                    WHEN ac.name = N'hyperthread_ratio'
                    THEN N'                osi.hyperthread_ratio, ' + NCHAR(10)
                    WHEN ac.name = N'softnuma_configuration_desc'
                    THEN N'                osi.softnuma_configuration_desc, ' + NCHAR(10)
                    WHEN ac.name = N'scheduler_total_count'
                    THEN N'                osi.scheduler_total_count, ' + NCHAR(10)
                    WHEN ac.name = N'scheduler_count'
                    THEN N'                osi.scheduler_count, ' + NCHAR(10)
                    ELSE N''
                END
        FROM
        (
            SELECT
                ac.name
            FROM sys.all_columns AS ac
            WHERE ac.object_id = OBJECT_ID(N'sys.dm_os_sys_info')
            AND   ac.name IN
                  (
                      N'socket_count',
                      N'numa_node_count',
                      N'cpu_count',
                      N'cores_per_socket',
                      N'hyperthread_ratio',
                      N'softnuma_configuration_desc',
                      N'scheduler_total_count',
                      N'scheduler_count'
                  )
        ) AS ac
        OPTION(MAXDOP 1, RECOMPILE);

        SELECT
            @cpu_details =
                @cpu_details_select +
                SUBSTRING
                (
                    @cpu_details_columns,
                    1,
                    LEN(@cpu_details_columns) -3
                ) +
                @cpu_details_from;

        IF @debug = 1
        BEGIN
            PRINT @cpu_details;
        END;

        EXECUTE sys.sp_executesql
            @cpu_details,
          N'@cpu_details_output xml OUTPUT',
            @cpu_details_output OUTPUT;

        /*
        Checking for high CPU utilization periods
        */
        SELECT
            @cpu_utilization =
                x.cpu_utilization
        FROM
        (
            SELECT
                sample_time =
                    CONVERT
                    (
                        datetime,
                        DATEADD
                        (
                            SECOND,
                            (t.timestamp - osi.ms_ticks) / 1000,
                            SYSDATETIME()
                        )
                    ),
                sqlserver_cpu_utilization =
                    t.record.value('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]','integer'),
                other_process_cpu_utilization =
                    (100 - t.record.value('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]','integer')
                     - t.record.value('(Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]','integer')),
                total_cpu_utilization =
                    (100 - t.record.value('(Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'integer'))
            FROM sys.dm_os_sys_info AS osi
            CROSS JOIN
            (
                SELECT
                    dorb.timestamp,
                    record =
                        CONVERT(xml, dorb.record)
                FROM sys.dm_os_ring_buffers AS dorb
                WHERE dorb.ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
            ) AS t
            WHERE t.record.exist('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization[.>= sql:variable("@cpu_utilization_threshold")])') = 1
            ORDER BY
                sample_time DESC
            FOR XML
                PATH('cpu_utilization'),
                TYPE
        ) AS x (cpu_utilization)
        OPTION(MAXDOP 1, RECOMPILE);

        IF @cpu_utilization IS NULL
        BEGIN
            SELECT
                @cpu_utilization =
                (
                    SELECT
                        N'No significant CPU usage data available.'
                    FOR XML
                        PATH(N'cpu_utilization'),
                        TYPE
                );
        END;

        IF @log_to_table = 0
        BEGIN
            SELECT
                cpu_details_output =
                    @cpu_details_output,
                cpu_utilization_over_threshold =
                    @cpu_utilization;
        END;
        IF @log_to_table = 1
        BEGIN
            /* Get the maximum sample_time from the CPU events table */
            SET @insert_sql = N'
                SELECT
                    @max_sample_time_out =
                        ISNULL
                        (
                            MAX(sample_time),
                            ''19000101''
                        )
                FROM ' + @log_table_cpu_events + N'
                OPTION(RECOMPILE);';

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql,
                N'@max_sample_time_out datetime OUTPUT',
                @max_sample_time OUTPUT;

            SET @insert_sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                INSERT INTO ' + @log_table_cpu_events + N'
                (
                    sample_time,
                    sqlserver_cpu_utilization,
                    other_process_cpu_utilization,
                    total_cpu_utilization
                )
                SELECT
                    sample_time = event.value(''(./sample_time)[1]'', ''datetime''),
                    sqlserver_cpu_utilization = event.value(''(./sqlserver_cpu_utilization)[1]'', ''integer''),
                    other_process_cpu_utilization = event.value(''(./other_process_cpu_utilization)[1]'', ''integer''),
                    total_cpu_utilization = event.value(''(./total_cpu_utilization)[1]'', ''integer'')
                FROM @cpu_utilization.nodes(''/cpu_utilization'') AS cpu(event)
                WHERE event.exist(''(./sample_time)[. > sql:variable("@max_sample_time")]'') = 1;';

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql,
              N'@cpu_utilization xml,
                @max_sample_time datetime',
                @cpu_utilization,
                @max_sample_time;
        END;

        /*Thread usage*/
        SELECT
            @cpu_threads += N'
        SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
        SELECT
            total_threads =
                MAX(osi.max_workers_count),
            used_threads =
                SUM(dos.active_workers_count),
            available_threads =
                MAX(osi.max_workers_count) - SUM(dos.active_workers_count),
            reserved_worker_count = ' +
                CASE @helpful_new_columns
                     WHEN 1
                     THEN ISNULL
                          (
                              @reserved_worker_count_out,
                              N'0'
                          )
                     ELSE N'''N/A'''
                END + N',
            threads_waiting_for_cpu =
                SUM(dos.runnable_tasks_count),
            requests_waiting_for_threads =
                SUM(dos.work_queue_count),
            current_workers =
                SUM(dos.current_workers_count),
            total_active_request_count =
                MAX(wg.active_request_count),
            total_queued_request_count =
                MAX(wg.queued_request_count),
            total_blocked_task_count =
                MAX(wg.blocked_task_count),
            total_active_parallel_thread_count =
                MAX(wg.active_parallel_thread_count),
            avg_runnable_tasks_count =
                AVG(dos.runnable_tasks_count),
            high_runnable_percent =
                MAX(ISNULL(r.high_runnable_percent, 0))
        FROM sys.dm_os_schedulers AS dos
        CROSS JOIN sys.dm_os_sys_info AS osi
        CROSS JOIN
        (
            SELECT
                active_request_count =
                    SUM(wg.active_request_count),
                queued_request_count =
                    SUM(wg.queued_request_count),
                blocked_task_count =
                    SUM(wg.blocked_task_count),
                active_parallel_thread_count =
                    SUM(wg.active_parallel_thread_count)
            FROM sys.dm_resource_governor_workload_groups AS wg
        ) AS wg
        OUTER APPLY
        (
            SELECT
                high_runnable_percent =
                    '''' +
                    RTRIM(y.runnable_pct) +
                    ''% of '' +
                    RTRIM(y.total) +
                    '' queries are waiting to get on a CPU.''
            FROM
            (
                SELECT
                    x.total,
                    x.runnable,
                    runnable_pct =
                        CONVERT
                        (
                            decimal(38,2),
                            (
                                x.runnable /
                                (1. * NULLIF(x.total, 0))
                            )
                        ) * 100.
                FROM
                (
                    SELECT
                        total = COUNT_BIG(*),
                        runnable =
                            SUM
                            (
                                CASE
                                    WHEN der.status = N''runnable''
                                    THEN 1
                                    ELSE 0
                                END
                            )
                    FROM sys.dm_exec_requests AS der
                    WHERE der.session_id > 50
                    AND   der.session_id <> @@SPID
                    AND   der.status NOT IN (N''background'', N''sleeping'')
                ) AS x
            ) AS y
            WHERE y.runnable_pct >= 10
            AND   y.total >= 4
        ) AS r
        WHERE dos.status = N''VISIBLE ONLINE''
        OPTION(MAXDOP 1, RECOMPILE);
        ';

        IF @log_to_table = 0
        BEGIN
            IF @debug = 1
            BEGIN
                PRINT @cpu_threads;
            END;

            EXECUTE sys.sp_executesql
                @cpu_threads;
        END;

        IF @log_to_table = 1
        BEGIN
            SET @insert_sql = N'
                SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                INSERT INTO ' + @log_table_cpu + N'
                (
                    total_threads,
                    used_threads,
                    available_threads,
                    reserved_worker_count,
                    threads_waiting_for_cpu,
                    requests_waiting_for_threads,
                    current_workers,
                    total_active_request_count,
                    total_queued_request_count,
                    total_blocked_task_count,
                    total_active_parallel_thread_count,
                    avg_runnable_tasks_count,
                    high_runnable_percent
                )' +
                REPLACE
                (
                    @cpu_threads,
                    N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;',
                    N''
                );

            IF @debug = 1
            BEGIN
                PRINT @insert_sql;
            END;

            EXECUTE sys.sp_executesql
                @insert_sql;
        END;


        /*
        Any current threadpool waits?
        */
        IF @log_to_table = 0
        BEGIN
            INSERT
                @threadpool_waits
            (
                session_id,
                wait_duration_ms,
                threadpool_waits
            )
            SELECT
                dowt.session_id,
                dowt.wait_duration_ms,
                threadpool_waits =
                    dowt.wait_type
            FROM sys.dm_os_waiting_tasks AS dowt
            WHERE dowt.wait_type = N'THREADPOOL'
            ORDER BY
                dowt.wait_duration_ms DESC
            OPTION(MAXDOP 1, RECOMPILE);

            IF ROWCOUNT_BIG() = 0
            BEGIN
                SELECT
                    THREADPOOL = N'No current THREADPOOL waits';
            END;
            ELSE
            BEGIN
                SELECT
                    dowt.session_id,
                    dowt.wait_duration_ms,
                    threadpool_waits =
                        dowt.wait_type
                FROM sys.dm_os_waiting_tasks AS dowt
                WHERE dowt.wait_type = N'THREADPOOL'
                ORDER BY
                    dowt.wait_duration_ms DESC
                OPTION(MAXDOP 1, RECOMPILE);
            END;
        END;


        /*
        Figure out who's using a lot of CPU
        */
        IF
        (
            @skip_queries = 0
        AND @what_to_check IN ('all', 'cpu')
        )
        BEGIN
            IF @debug = 1
            BEGIN
                RAISERROR('Checking CPU queries', 0, 1) WITH NOWAIT;
            END;

            SET @cpu_sql += N'
            SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
            SET LOCK_TIMEOUT 1000;

            SELECT
                der.session_id,
                database_name =
                    DB_NAME(der.database_id),
                [dd hh:mm:ss.mss] =
                    CASE
                        WHEN e.elapsed_time_ms < 0
                        THEN RIGHT(REPLICATE(''0'', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
                             '' '' +
                             RIGHT(CONVERT(varchar(30), DATEADD(second, (-1 * e.elapsed_time_ms), 0), 120), 9) +
                             ''.000''
                        ELSE RIGHT(REPLICATE(''0'', 2) +
                             CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
                             '' '' +
                             RIGHT(CONVERT(varchar(30), DATEADD(second, e.elapsed_time_ms / 1000, 0), 120), 9) +
                             ''.'' +
                             RIGHT(''000'' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
                    END,
                query_text =
                    (
                        SELECT
                            [processing-instruction(query)] =
                                SUBSTRING
                                (
                                    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                                    REPLACE(
                                        dest.text COLLATE Latin1_General_BIN2,
                                    NCHAR(31),N''?''),NCHAR(30),N''?''),NCHAR(29),N''?''),NCHAR(28),N''?''),NCHAR(27),N''?''),NCHAR(26),N''?''),NCHAR(25),N''?''),NCHAR(24),N''?''),NCHAR(23),N''?''),NCHAR(22),N''?''),
                                    NCHAR(21),N''?''),NCHAR(20),N''?''),NCHAR(19),N''?''),NCHAR(18),N''?''),NCHAR(17),N''?''),NCHAR(16),N''?''),NCHAR(15),N''?''),NCHAR(14),N''?''),NCHAR(12),N''?''),
                                    NCHAR(11),N''?''),NCHAR(8),N''?''),NCHAR(7),N''?''),NCHAR(6),N''?''),NCHAR(5),N''?''),NCHAR(4),N''?''),NCHAR(3),N''?''),NCHAR(2),N''?''),NCHAR(1),N''?''),NCHAR(0),N''''),
                                    N''<?'', N''??''), N''?>'', N''??''),
                                    (der.statement_start_offset / 2) + 1,
                                    (
                                        (
                                            CASE
                                                der.statement_end_offset
                                                WHEN -1
                                                THEN DATALENGTH(dest.text)
                                                ELSE der.statement_end_offset
                                            END
                                            - der.statement_start_offset
                                        ) / 2
                                    ) + 1
                                )
                                FOR XML PATH(''''),
                                TYPE
                    ),'
                +
                CONVERT
                (
                    nvarchar(max),
                CASE
                      WHEN @skip_plan_xml = 0
                      THEN N'
                query_plan =
                     CASE
                         WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
                         THEN TRY_CAST(deqp.query_plan AS xml)
                         WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
                         THEN
                             (
                                 SELECT
                                     [processing-instruction(query_plan)] =
                                         N''-- '' + NCHAR(13) + NCHAR(10) +
                                         N''-- This is a huge query plan.'' + NCHAR(13) + NCHAR(10) +
                                         N''-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.'' + NCHAR(13) + NCHAR(10) +
                                         NCHAR(13) + NCHAR(10) +
                                         REPLACE(deqp.query_plan, N''<RelOp'', NCHAR(13) + NCHAR(10) + N''<RelOp'') +
                                         NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
                                 FOR XML PATH(N''''),
                                         TYPE
                             )
                     END,' +
                          CASE
                              WHEN @live_plans = 1
                              THEN
                           N'
                live_query_plan =
                    deqs.query_plan,'
                              ELSE N''
                          END
                      ELSE N''
                  END
                )
                + CONVERT
                  (
                      nvarchar(max),
                      N'
                statement_start_offset =
                    (der.statement_start_offset / 2) + 1,
                statement_end_offset =
                    (
                        (
                            CASE der.statement_end_offset
                                 WHEN -1
                                 THEN DATALENGTH(dest.text)
                                 ELSE der.statement_end_offset
                            END
                            - der.statement_start_offset
                        ) / 2
                    ) + 1,
                der.plan_handle,
                der.status,
                der.blocking_session_id,
                der.wait_type,
                wait_time_ms = der.wait_time,
                der.wait_resource,
                cpu_time_ms = der.cpu_time,
                total_elapsed_time_ms = der.total_elapsed_time,
                der.reads,
                der.writes,
                der.logical_reads,
                granted_query_memory_gb =
                    CONVERT(decimal(38, 2), (der.granted_query_memory / 128. / 1024.)),
                transaction_isolation_level =
                    CASE
                        WHEN der.transaction_isolation_level = 0
                        THEN ''Unspecified''
                        WHEN der.transaction_isolation_level = 1
                        THEN ''Read Uncommitted''
                        WHEN der.transaction_isolation_level = 2
                        AND  EXISTS
                             (
                                 SELECT
                                     1/0
                                 FROM sys.dm_tran_active_snapshot_database_transactions AS trn
                                 WHERE der.session_id = trn.session_id
                                 AND   trn.is_snapshot = 0
                             )
                        THEN ''Read Committed Snapshot Isolation''
                        WHEN der.transaction_isolation_level = 2
                        AND  NOT EXISTS
                             (
                                 SELECT
                                     1/0
                                 FROM sys.dm_tran_active_snapshot_database_transactions AS trn
                                 WHERE der.session_id = trn.session_id
                                 AND   trn.is_snapshot = 0
                             )
                        THEN ''Read Committed''
                        WHEN der.transaction_isolation_level = 3
                        THEN ''Repeatable Read''
                        WHEN der.transaction_isolation_level = 4
                        THEN ''Serializable''
                        WHEN der.transaction_isolation_level = 5
                        THEN ''Snapshot''
                        ELSE ''???''
                    END'
                  )
                + CASE
                      WHEN @cool_new_columns = 1
                      THEN CONVERT
                           (
                               nvarchar(max),
                               N',
                der.dop,
                der.parallel_worker_count'
                           )
                      ELSE N''
                  END
                + CONVERT
                  (
                      nvarchar(max),
                      N'
            FROM sys.dm_exec_requests AS der
            OUTER APPLY
            (
                SELECT
                    elapsed_time_ms =
                        CASE
                            WHEN DATEDIFF(HOUR, der.start_time, SYSDATETIME()) > 576
                            THEN DATEDIFF(SECOND, SYSDATETIME(), der.start_time)
                            ELSE DATEDIFF(MILLISECOND, der.start_time, SYSDATETIME())
                        END
            ) AS e
            OUTER APPLY sys.dm_exec_sql_text(COALESCE(der.sql_handle, der.plan_handle)) AS dest
            OUTER APPLY sys.dm_exec_text_query_plan
            (
                der.plan_handle,
                der.statement_start_offset,
                der.statement_end_offset
            ) AS deqp' +
                CASE
                    WHEN @live_plans = 1
                    THEN N'
            OUTER APPLY sys.dm_exec_query_statistics_xml(der.session_id) AS deqs'
                    ELSE N''
                END +
            N'
            WHERE der.session_id <> @@SPID
            AND   der.session_id >= 50
            AND   dest.text LIKE N''_%''
            ORDER BY '
            + CASE
                  WHEN @cool_new_columns = 1
                  THEN N'
                der.cpu_time DESC,
                der.parallel_worker_count DESC
            OPTION(MAXDOP 1, RECOMPILE);

            SET LOCK_TIMEOUT -1;
            '
                  ELSE N'
                der.cpu_time DESC
            OPTION(MAXDOP 1, RECOMPILE);

            SET LOCK_TIMEOUT -1;
            '
              END
                  );

            IF @debug = 1
            BEGIN
                PRINT SUBSTRING(@cpu_sql, 1, 4000);
                PRINT SUBSTRING(@cpu_sql, 4001, 8000);
            END;

            IF @log_to_table = 0
            BEGIN
                EXECUTE sys.sp_executesql
                    @cpu_sql;
            END;

            IF @log_to_table = 1
            BEGIN
                SET @insert_sql = N'
                    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                    INSERT INTO ' + @log_table_cpu_queries + N'
                    (
                        session_id,
                        database_name,
                        duration,
                        sql_text,
                        query_plan_xml' +
                        CASE
                            WHEN @live_plans = 1
                            THEN N',
                        live_query_plan'
                            ELSE N''
                        END + N',
                        statement_start_offset,
                        statement_end_offset,
                        plan_handle,
                        status,
                        blocking_session_id,
                        wait_type,
                        wait_time_ms,
                        wait_resource,
                        cpu_time_ms,
                        total_elapsed_time_ms,
                        reads,
                        writes,
                        logical_reads,
                        granted_query_memory_gb,
                        transaction_isolation_level' +
                        CASE
                            WHEN @cool_new_columns = 1
                            THEN N',
                        dop,
                        parallel_worker_count'
                            ELSE N''
                        END + N'
                    )' +
                    REPLACE
                    (
                        REPLACE
                        (
                            REPLACE
                            (
                                @cpu_sql,
                                N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;',
                                N''
                            ),
                            N'SET LOCK_TIMEOUT 1000;',
                            N''
                        ),
                        N'SET LOCK_TIMEOUT -1;',
                        N''
                    );

                IF @debug = 1
                BEGIN
                    PRINT @insert_sql;
                END;

                EXECUTE sys.sp_executesql
                    @insert_sql;
            END;
        END; /*End not skipping queries*/
    END; /*End CPU checks*/

    IF
    (
        @sample_seconds > 0
    AND @pass = 0
    )
    BEGIN
        SELECT
            @pass = 1;

        WAITFOR DELAY @waitfor;
        GOTO DO_OVER;
    END;

    troubleshoot_blocking:
    IF @troubleshoot_blocking = 1
    BEGIN
        /*
        Blocking chain analysis - mimics sp_WhoIsActive @find_block_leaders = 1
        Uses sys.sysprocesses to find both active and idle blockers
        Uses recursive CTE to walk blocking chains
        */

        /*
        Table variable to hold lead blockers (anchor rows)
        This improves performance by materializing the anchor set once
        */
        DECLARE
            @lead_blockers table
        (
            session_id smallint NOT NULL
                PRIMARY KEY WITH (IGNORE_DUP_KEY = ON),
            blocking_session_id smallint NOT NULL,
            blocking_chain nvarchar(4000) NOT NULL
        );

        /*
        Find lead blockers: sessions that are blocking others
        but not blocked themselves (includes idle sessions with open transactions)
        */
        INSERT
            @lead_blockers
        (
            session_id,
            blocking_session_id,
            blocking_chain
        )
        SELECT
            session_id = sp.spid,
            blocking_session_id = sp.blocked,
            blocking_chain =
                CONVERT
                (
                    nvarchar(4000),
                    CONVERT(nvarchar(20), sp.spid) + N' lead blocker'
                )
        FROM sys.sysprocesses AS sp
        WHERE sp.blocked = 0
        AND   EXISTS
              (
                  SELECT
                      1/0
                  FROM sys.sysprocesses AS sp2
                  WHERE sp2.blocked = sp.spid
              );

        /*
        Recursive CTE to walk blocking chains
        Anchor: lead blockers from table variable
        Recursive: sessions blocked by current level
        */
        WITH
            blockers
        (
            session_id,
            blocking_session_id,
            blocking_level,
            top_level_blocker,
            blocking_chain,
            visited_path
        ) AS
        (
            /*
            Anchor: Lead blockers from table variable
            */
            SELECT
                session_id = lb.session_id,
                blocking_session_id = lb.blocking_session_id,
                blocking_level = 0,
                top_level_blocker = lb.session_id,
                blocking_chain = lb.blocking_chain,
                visited_path =
                    CONVERT
                    (
                        nvarchar(4000),
                        N'.' + CONVERT(nvarchar(20), lb.session_id) + N'.'
                    )
            FROM @lead_blockers AS lb

            UNION ALL

            /*
            Recursive: Walk down the blocking chain
            */
            SELECT
                session_id = sp.spid,
                blocking_session_id = sp.blocked,
                blocking_level = b.blocking_level + 1,
                top_level_blocker = b.top_level_blocker,
                blocking_chain =
                    CONVERT
                    (
                        nvarchar(4000),
                        REPLICATE(N' > ', b.blocking_level + 1) +
                        CONVERT(nvarchar(20), sp.blocked) +
                        N' blocking ' +
                        CONVERT(nvarchar(20), sp.spid)
                    ),
                visited_path =
                    CONVERT
                    (
                        nvarchar(4000),
                        b.visited_path + CONVERT(nvarchar(20), sp.spid) + N'.'
                    )
            FROM blockers AS b
            JOIN sys.sysprocesses AS sp
              ON sp.blocked = b.session_id
            WHERE b.visited_path NOT LIKE
                  N'%.' + CONVERT(nvarchar(20), sp.spid) + N'.%'
        ),
            blocking_info
        (
            session_id,
            blocking_session_id,
            blocking_level,
            top_level_blocker,
            blocking_chain,
            blocked_session_count,
            last_batch,
            status,
            wait_type,
            wait_time,
            wait_resource,
            cpu_time,
            physical_io,
            memusage,
            open_transaction_count,
            database_name,
            command,
            sql_handle,
            statement_start_offset,
            statement_end_offset,
            login_name,
            host_name,
            program_name,
            login_time
        ) AS
        (
            /*
            Join blocking chain results to sysprocesses for session details
            SQL text and query plans are NOT retrieved here - done in final SELECT
            */
            SELECT
                b.session_id,
                b.blocking_session_id,
                b.blocking_level,
                b.top_level_blocker,
                b.blocking_chain,
                blocked_session_count =
                (
                    SELECT
                        COUNT_BIG(*)
                    FROM blockers AS b2
                    WHERE b2.visited_path LIKE
                          N'%.' + CONVERT(nvarchar(20), b.session_id) + N'.%'
                    AND   b2.session_id <> b.session_id
                ),
                sp.last_batch,
                sp.status,
                wait_type = sp.lastwaittype,
                wait_time = sp.waittime,
                wait_resource = sp.waitresource,
                cpu_time = sp.cpu,
                physical_io = sp.physical_io,
                sp.memusage,
                open_transaction_count = sp.open_tran,
                database_name = DB_NAME(sp.dbid),
                command = sp.cmd,
                sp.sql_handle,
                statement_start_offset = sp.stmt_start,
                statement_end_offset = sp.stmt_end,
                login_name = sp.loginame,
                host_name = sp.hostname,
                program_name = sp.program_name,
                sp.login_time
            FROM blockers AS b
            JOIN sys.sysprocesses AS sp
              ON sp.spid = b.session_id
        )
        /*
        Final SELECT: retrieve SQL text and query plans last for performance
        Column order matches sp_WhoIsActive where possible
        */
        SELECT
            [dd hh:mm:ss.mss] =
                CASE
                    WHEN e.elapsed_time_ms < 0
                    THEN RIGHT(REPLICATE('0', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
                         ' ' +
                         RIGHT(CONVERT(varchar(30), DATEADD(SECOND, (-1 * e.elapsed_time_ms), 0), 120), 9) +
                         '.000'
                    ELSE RIGHT(REPLICATE('0', 2) +
                         CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
                         ' ' +
                         RIGHT(CONVERT(varchar(30), DATEADD(SECOND, e.elapsed_time_ms / 1000, 0), 120), 9) +
                         '.' +
                         RIGHT('000' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
                END,
            bi.session_id,
            blocking_session_id = NULLIF(bi.blocking_session_id, 0),
            bi.blocking_level,
            bi.top_level_blocker,
            bi.blocking_chain,
            bi.blocked_session_count,
            query_text =
            (
                SELECT
                    [processing-instruction(query)] =
                        SUBSTRING
                        (
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                            REPLACE(
                                dest.text COLLATE Latin1_General_BIN2,
                            NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
                            NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'),
                            NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N''),
                            N'<?', N'??'), N'?>', N'??'),
                            (bi.statement_start_offset / 2) + 1,
                            (
                                (
                                    CASE bi.statement_end_offset
                                        WHEN -1
                                        THEN DATALENGTH(dest.text)
                                        ELSE bi.statement_end_offset
                                    END - bi.statement_start_offset
                                ) / 2
                            ) + 1
                        )
                FOR XML PATH(''),
                    TYPE
            ),
            query_plan =
                CASE
                    WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
                    THEN TRY_CAST(deqp.query_plan AS xml)
                    WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
                    THEN
                    (
                        SELECT
                            [processing-instruction(query_plan)] =
                                N'-- ' + NCHAR(13) + NCHAR(10) +
                                N'-- This is a huge query plan.' + NCHAR(13) + NCHAR(10) +
                                N'-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR(13) + NCHAR(10) +
                                NCHAR(13) + NCHAR(10) +
                                REPLACE(deqp.query_plan, N'<RelOp', NCHAR(13) + NCHAR(10) + N'<RelOp') +
                                NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
                        FOR XML PATH(N''),
                            TYPE
                    )
                END,
            bi.login_name,
            wait_time_ms = bi.wait_time,
            wait_type = NULLIF(bi.wait_type, N'MISCELLANEOUS'),
            bi.wait_resource,
            reads = ISNULL(der.reads, 0),
            writes = ISNULL(der.writes, 0),
            logical_reads = ISNULL(der.logical_reads, bi.physical_io),
            cpu_time = ISNULL(der.cpu_time, bi.cpu_time),
            used_memory = ISNULL(der.granted_query_memory, bi.memusage),
            bi.status,
            bi.open_transaction_count,
            bi.host_name,
            bi.database_name,
            bi.program_name,
            bi.last_batch,
            bi.login_time
        FROM blocking_info AS bi
        LEFT JOIN sys.dm_exec_requests AS der
          ON der.session_id = bi.session_id
        OUTER APPLY
        (
            SELECT
                elapsed_time_ms =
                    CASE
                        WHEN DATEDIFF(HOUR, ISNULL(der.start_time, bi.last_batch), SYSDATETIME()) > 576
                        THEN DATEDIFF(SECOND, SYSDATETIME(), ISNULL(der.start_time, bi.last_batch))
                        ELSE DATEDIFF(MILLISECOND, ISNULL(der.start_time, bi.last_batch), SYSDATETIME())
                    END
        ) AS e
        OUTER APPLY sys.dm_exec_sql_text(bi.sql_handle) AS dest
        OUTER APPLY sys.dm_exec_text_query_plan
        (
            der.plan_handle,
            der.statement_start_offset,
            der.statement_end_offset
        ) AS deqp
        ORDER BY
            bi.top_level_blocker,
            bi.blocking_level,
            bi.session_id
        OPTION(MAXRECURSION 0);

        RETURN;
    END; /*End troubleshoot_blocking*/

    IF @debug = 1
    BEGIN
        SELECT
            table_name = '@waits',
            x.*
        FROM @waits AS x
        ORDER BY
            x.wait_type
        OPTION(RECOMPILE);

        SELECT
            table_name = '@file_metrics',
            x.*
        FROM @file_metrics AS x
        ORDER BY
            x.database_name,
            x.sample_time
        OPTION(RECOMPILE);

        SELECT
            table_name = '@dm_os_performance_counters',
            x.*
        FROM @dm_os_performance_counters AS x
        ORDER BY
            x.counter_name
        OPTION(RECOMPILE);

        SELECT
            table_name = '@threadpool_waits',
            x.*
        FROM @threadpool_waits AS x
        ORDER BY
            x.wait_duration_ms DESC
        OPTION(RECOMPILE);

        SELECT
            pattern =
                'parameters',
            what_to_check =
                @what_to_check,
            skip_queries =
                @skip_queries,
            skip_plan_xml =
                @skip_plan_xml,
            minimum_disk_latency_ms =
                @minimum_disk_latency_ms,
            cpu_utilization_threshold =
                @cpu_utilization_threshold,
            skip_waits =
                @skip_waits,
            skip_perfmon =
                @skip_perfmon,
            sample_seconds =
                @sample_seconds,
            help =
                @help,
            debug =
                @debug,
            version =
                @version,
            version_date =
                @version_date;

        SELECT
            pattern =
                'variables',
            azure =
                @azure,
            pool_sql =
                @pool_sql,
            pages_kb =
                @pages_kb,
            mem_sql =
                @mem_sql,
            helpful_new_columns =
                @helpful_new_columns,
            cpu_sql =
                @cpu_sql,
            cool_new_columns =
                @cool_new_columns,
            reserved_worker_count_out =
                @reserved_worker_count_out,
            reserved_worker_count =
                @reserved_worker_count,
            cpu_details =
                @cpu_details,
            cpu_details_output =
                @cpu_details_output,
            cpu_details_columns =
                @cpu_details_columns,
            cpu_details_select =
                @cpu_details_select,
            cpu_details_from =
                @cpu_details_from,
            database_size_out =
                @database_size_out,
            database_size_out_gb =
                @database_size_out_gb,
            total_physical_memory_gb =
                @total_physical_memory_gb,
            cpu_utilization =
                @cpu_utilization,
            low_memory =
                @low_memory,
            disk_check =
                @disk_check,
            live_plans =
                @live_plans,
            pass =
                @pass,
            [waitfor] =
                @waitfor,
            prefix =
                @prefix,
            memory_grant_cap =
                @memory_grant_cap;

        SELECT
            pattern =
                'logging parameters',
            log_to_table =
                @log_to_table,
            log_database_name =
                @log_database_name,
            log_schema_name =
                @log_schema_name,
            log_table_name_prefix =
                @log_table_name_prefix,
            log_database_schema =
                @log_database_schema,
            log_table_waits =
                @log_table_waits,
            log_table_file_metrics =
                @log_table_file_metrics,
            log_table_perfmon =
                @log_table_perfmon,
            log_table_memory =
                @log_table_memory,
            log_table_cpu =
                @log_table_cpu,
            log_table_memory_consumers =
                @log_table_memory_consumers,
            log_table_memory_queries =
                @log_table_memory_queries,
            log_table_cpu_queries =
                @log_table_cpu_queries,
            log_table_cpu_events =
                @log_table_cpu_events;

    END; /*End Debug*/
END; /*Final End*/
GO
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*
 ██████╗ ██╗   ██╗███████╗██████╗ ██╗   ██╗
██╔═══██╗██║   ██║██╔════╝██╔══██╗╚██╗ ██╔╝
██║   ██║██║   ██║█████╗  ██████╔╝ ╚████╔╝
██║▄▄ ██║██║   ██║██╔══╝  ██╔══██╗  ╚██╔╝
╚██████╔╝╚██████╔╝███████╗██║  ██║   ██║
 ╚══▀▀═╝  ╚═════╝ ╚══════╝╚═╝  ╚═╝   ╚═╝

██████╗ ███████╗██████╗ ██████╗  ██████╗
██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔═══██╗
██████╔╝█████╗  ██████╔╝██████╔╝██║   ██║
██╔══██╗██╔══╝  ██╔═══╝ ██╔══██╗██║   ██║
██║  ██║███████╗██║     ██║  ██║╚██████╔╝
╚═╝  ╚═╝╚══════╝╚═╝     ╚═╝  ╚═╝ ╚═════╝

██████╗ ██╗   ██╗██╗██╗     ██████╗ ███████╗██████╗
██╔══██╗██║   ██║██║██║     ██╔══██╗██╔════╝██╔══██╗
██████╔╝██║   ██║██║██║     ██║  ██║█████╗  ██████╔╝
██╔══██╗██║   ██║██║██║     ██║  ██║██╔══╝  ██╔══██╗
██████╔╝╚██████╔╝██║███████╗██████╔╝███████╗██║  ██║
╚═════╝  ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚══════╝╚═╝  ╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_QueryReproBuilder
    @help = 1;

For working through errors:
EXECUTE sp_QueryReproBuilder
    @debug = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/

IF OBJECT_ID(N'dbo.sp_QueryReproBuilder', N'P') IS NULL
    EXECUTE (N'CREATE PROCEDURE dbo.sp_QueryReproBuilder AS RETURN 138;');
GO

ALTER PROCEDURE
    dbo.sp_QueryReproBuilder
(
    @database_name sysname = NULL, /*the name of the database you want to look at query store in*/
    @start_date datetimeoffset(7) = NULL, /*the begin date of your search, will be converted to UTC internally*/
    @end_date datetimeoffset(7) = NULL, /*the end date of your search, will be converted to UTC internally*/
    @include_plan_ids nvarchar(4000) = NULL, /*a list of plan ids to search for*/
    @include_query_ids nvarchar(4000) = NULL, /*a list of query ids to search for*/
    @ignore_plan_ids nvarchar(4000) = NULL, /*a list of plan ids to ignore*/
    @ignore_query_ids nvarchar(4000) = NULL, /*a list of query ids to ignore*/
    @procedure_schema sysname = NULL, /*the schema of the procedure you're searching for*/
    @procedure_name sysname = NULL, /*the name of the programmable object you're searching for*/
    @query_text_search nvarchar(4000) = NULL, /*query text to search for*/
    @query_text_search_not nvarchar(4000) = NULL, /*query text to exclude*/
    @help bit = 0, /*return available parameter details, etc.*/
    @debug bit = 0, /*prints dynamic sql, statement length, parameter and variable values, and raw temp table contents*/
    @version varchar(30) = NULL OUTPUT, /*OUTPUT; for support*/
    @version_date datetime = NULL OUTPUT /*OUTPUT; for support*/
)
WITH
    RECOMPILE
AS
BEGIN
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

BEGIN TRY

/*Version*/
SELECT
    @version = '1.3',
    @version_date = '20260301';

/*Help*/
IF @help = 1
BEGIN
    /*Introduction*/
    SELECT
        introduction =
           'hi, i''m sp_QueryReproBuilder!' UNION ALL
    SELECT 'you got me from https://code.erikdarling.com' UNION ALL
    SELECT 'i help you build repro scripts from query store data' UNION ALL
    SELECT 'i extract query text and parameters from query plans' UNION ALL
    SELECT 'and set them up to run with sp_executesql' UNION ALL
    SELECT '' UNION ALL
    SELECT 'from your loving sql server consultant, erik darling: erikdarling@hey.com';

    /*Parameters*/
    SELECT
        parameter_name = ap.name,
        data_type = t.name,
        description =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'the name of the database you want to look at query store in'
                WHEN N'@start_date' THEN 'the begin date of your search, will be converted to UTC internally'
                WHEN N'@end_date' THEN 'the end date of your search, will be converted to UTC internally'
                WHEN N'@include_plan_ids' THEN 'a list of plan ids to search for'
                WHEN N'@include_query_ids' THEN 'a list of query ids to search for'
                WHEN N'@ignore_plan_ids' THEN 'a list of plan ids to ignore'
                WHEN N'@ignore_query_ids' THEN 'a list of query ids to ignore'
                WHEN N'@procedure_schema' THEN 'the schema of the procedure you''re searching for'
                WHEN N'@procedure_name' THEN 'the name of the programmable object you''re searching for'
                WHEN N'@query_text_search' THEN 'query text to search for'
                WHEN N'@query_text_search_not' THEN 'query text to exclude'
                WHEN N'@help' THEN 'how you got here'
                WHEN N'@debug' THEN 'prints dynamic sql, statement length, parameter and variable values'
                WHEN N'@version' THEN 'OUTPUT; for support'
                WHEN N'@version_date' THEN 'OUTPUT; for support'
                ELSE 'not documented'
            END,
        valid_inputs =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'a database name with query store enabled'
                WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@include_plan_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@include_query_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@ignore_plan_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@ignore_query_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@procedure_schema' THEN 'a valid schema in your database'
                WHEN N'@procedure_name' THEN 'a valid programmable object in your database'
                WHEN N'@query_text_search' THEN 'a string; leading and trailing wildcards will be added if missing'
                WHEN N'@query_text_search_not' THEN 'a string; leading and trailing wildcards will be added if missing'
                WHEN N'@help' THEN '0 or 1'
                WHEN N'@debug' THEN '0 or 1'
                WHEN N'@version' THEN 'none; OUTPUT'
                WHEN N'@version_date' THEN 'none; OUTPUT'
                ELSE 'not documented'
            END,
        defaults =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'NULL; current database name if NULL'
                WHEN N'@start_date' THEN 'the last seven days'
                WHEN N'@end_date' THEN 'NULL'
                WHEN N'@include_plan_ids' THEN 'NULL'
                WHEN N'@include_query_ids' THEN 'NULL'
                WHEN N'@ignore_plan_ids' THEN 'NULL'
                WHEN N'@ignore_query_ids' THEN 'NULL'
                WHEN N'@procedure_schema' THEN 'NULL; dbo if NULL and procedure name is not NULL'
                WHEN N'@procedure_name' THEN 'NULL'
                WHEN N'@query_text_search' THEN 'NULL'
                WHEN N'@query_text_search_not' THEN 'NULL'
                WHEN N'@help' THEN '0'
                WHEN N'@debug' THEN '0'
                WHEN N'@version' THEN 'none; OUTPUT'
                WHEN N'@version_date' THEN 'none; OUTPUT'
                ELSE 'not documented'
            END
    FROM sys.all_parameters AS ap
    INNER JOIN sys.all_objects AS o
      ON ap.object_id = o.object_id
    INNER JOIN sys.types AS t
      ON  ap.system_type_id = t.system_type_id
      AND ap.user_type_id = t.user_type_id
    WHERE o.name = N'sp_QueryReproBuilder'
    OPTION(RECOMPILE);

    RETURN;
END;

/*Variables*/
DECLARE
    @sql nvarchar(MAX) = N'',
    @database_id integer,
    @database_name_quoted sysname =
        QUOTENAME(@database_name),
    @collation sysname,
    @query_store_exists bit = 'true',
    @procedure_name_quoted nvarchar(1024),
    @procedure_exists bit = 0,
    @isolation_level nvarchar(100) =
        N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' +
        NCHAR(10),
    @nc10 nchar(1) = NCHAR(10),
    @start_date_original datetimeoffset(7),
    @end_date_original datetimeoffset(7),
    @utc_minutes_difference bigint,
    @product_version integer,
    @azure bit = 0,
    @sql_2017 bit = 0,
    @sql_2022_views bit = 0,
    @new bit = 0,
    @current_table nvarchar(100)

/*Fix NULL @database_name*/
IF
  (
      @database_name IS NULL
      AND LOWER(DB_NAME())
          NOT IN
          (
              N'master',
              N'model',
              N'msdb',
              N'tempdb',
              N'dbatools',
              N'dbadmin',
              N'dbmaintenance',
              N'rdsadmin',
              N'other_memes'
          )
  )
BEGIN
    SELECT
        @database_name =
            DB_NAME();
END;


/*Initialize database variables*/
SELECT
    @database_id =
        DB_ID(@database_name),
    @database_name_quoted =
        QUOTENAME(@database_name),
    @collation =
        CONVERT
        (
            sysname,
            DATABASEPROPERTYEX
            (
                @database_name,
                'Collation'
            )
        );

/*Check if database exists*/
IF
(
    @database_id IS NULL
 OR @collation IS NULL
)
BEGIN
    RAISERROR('Database %s does not exist', 10, 1, @database_name) WITH NOWAIT;
    RETURN;
END;

/*Check for Azure and get SQL Server version*/
SELECT
    @azure =
        CASE
            WHEN
                CONVERT
                (
                    sysname,
                    SERVERPROPERTY('EDITION')
                ) = N'SQL Azure'
            THEN 1
            ELSE 0
        END,
    @product_version =
        CONVERT
        (
            integer,
            SUBSTRING
            (
                CONVERT
                (
                    varchar(128),
                    SERVERPROPERTY('ProductVersion')
                ),
                1,
                CHARINDEX
                (
                    '.',
                    CONVERT
                    (
                        varchar(128),
                        SERVERPROPERTY('ProductVersion')
                    )
                ) - 1
            )
        );

/*Check for SQL Server 2017+ features (wait stats)*/
IF
(
    @product_version >= 14
 OR @azure = 1
)
BEGIN
    SELECT
        @sql_2017 = 1;
END;

/*Check for SQL Server 2019+ features*/
IF
(
    @product_version >= 15
 OR @azure = 1
)
BEGIN
    SELECT
        @new = 1;
END;

/*
See if our cool new 2022 views exist.
May have to tweak this if views aren't present in some cloudy situations.
*/
SELECT
    @sql_2022_views =
        CASE
            WHEN COUNT_BIG(*) = 5
            THEN 1
            ELSE 0
        END
FROM sys.all_objects AS ao
WHERE ao.name IN
      (
          N'query_store_plan_feedback',
          N'query_store_query_hints',
          N'query_store_query_variant',
          N'query_store_replicas',
          N'query_store_plan_forcing_locations'
      )
OPTION(RECOMPILE);

/*Check database state*/
SELECT
    @sql += N'
SELECT
    @query_store_exists =
        CASE
            WHEN EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
                     WHERE
                     (
                          dqso.actual_state = 0
                       OR dqso.actual_state IS NULL
                     )
                 )
            OR   NOT EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
                 )
            THEN 0
            ELSE 1
        END
OPTION(RECOMPILE);
';

EXECUTE sys.sp_executesql
    @sql,
  N'@query_store_exists bit OUTPUT',
    @query_store_exists OUTPUT;

IF @query_store_exists = 0
BEGIN
    RAISERROR('Query Store doesn''t seem to be enabled for database: %s', 10, 1, @database_name) WITH NOWAIT;
    RETURN;
END;

/*
Initialize date variables
*/
SELECT
    @start_date_original =
        ISNULL
        (
            @start_date,
            DATEADD
            (
                DAY,
                -7,
                DATEDIFF
                (
                    DAY,
                    '19000101',
                    SYSUTCDATETIME()
                )
            )
        ),
    @end_date_original =
        ISNULL
        (
            @end_date,
            DATEADD
            (
                DAY,
                1,
                DATEADD
                (
                    MINUTE,
                    0,
                    DATEDIFF
                    (
                        DAY,
                        '19000101',
                        SYSUTCDATETIME()
                    )
                )
            )
        ),
    @utc_minutes_difference =
        DATEDIFF
        (
            MINUTE,
            SYSDATETIME(),
            SYSUTCDATETIME()
        );

/*
Convert dates to UTC for filtering
*/
SELECT
    @start_date =
        CASE
            WHEN @start_date IS NULL
            THEN
                DATEADD
                (
                    DAY,
                    -7,
                    DATEDIFF
                    (
                        DAY,
                        '19000101',
                        SYSUTCDATETIME()
                    )
                )
            WHEN @start_date IS NOT NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    @utc_minutes_difference,
                    @start_date_original
                )
        END,
    @end_date =
        CASE
            WHEN @end_date IS NULL
            THEN
                DATEADD
                (
                    DAY,
                    1,
                    DATEADD
                    (
                        MINUTE,
                        0,
                        DATEDIFF
                        (
                            DAY,
                            '19000101',
                            SYSUTCDATETIME()
                        )
                    )
                )
            WHEN @end_date IS NOT NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    @utc_minutes_difference,
                    @end_date_original
                )
        END;

/*
Validate date range
*/
IF @start_date >= @end_date
BEGIN
    SELECT
        @end_date =
            DATEADD
            (
                DAY,
                7,
                @start_date
            ),
        @end_date_original =
            DATEADD
            (
                DAY,
                1,
                @start_date_original
            );
END;

/*
NULLIF blank strings to NULL for consistent handling
*/
SELECT
    @procedure_schema =
        NULLIF(@procedure_schema, ''),
    @procedure_name =
        NULLIF(@procedure_name, ''),
    @include_plan_ids =
        NULLIF(@include_plan_ids, ''),
    @include_query_ids =
        NULLIF(@include_query_ids, ''),
    @ignore_plan_ids =
        NULLIF(@ignore_plan_ids, ''),
    @ignore_query_ids =
        NULLIF(@ignore_query_ids, ''),
    @query_text_search =
        NULLIF(@query_text_search, ''),
    @query_text_search_not =
        NULLIF(@query_text_search_not, '');

/*
Parse schema from procedure name if provided in schema.procedure format
*/
IF
(
      @procedure_name LIKE N'[[]%].[[]%]'
  AND @procedure_schema IS NULL
)
BEGIN
    SELECT
        @procedure_schema = PARSENAME(@procedure_name, 2),
        @procedure_name   = PARSENAME(@procedure_name, 1);
END;

/*Initialize procedure variables*/
IF @procedure_name IS NOT NULL
BEGIN
    IF @procedure_schema IS NULL
    BEGIN
        SELECT
            @procedure_schema = N'dbo';
    END;

    SELECT
        @procedure_name_quoted =
            QUOTENAME(@database_name) +
            N'.' +
            QUOTENAME
            (
                ISNULL
                (
                    @procedure_schema,
                    N'dbo'
                )
            ) +
            N'.' +
            QUOTENAME(@procedure_name);

    /*Create temp table for procedure object IDs (needed before wildcard check)*/
    CREATE TABLE
        #procedure_object_ids
    (
        object_id bigint NOT NULL,
        INDEX object_id CLUSTERED (object_id)
    );

    /*Check if procedure exists in Query Store - wildcard procedure name*/
    IF CHARINDEX(N'%', @procedure_name) > 0
    BEGIN
        SELECT
            @sql = @isolation_level;

        SELECT
            @sql += N'
SELECT
    p.object_id
FROM ' + @database_name_quoted + N'.sys.procedures AS p
JOIN ' + @database_name_quoted + N'.sys.schemas AS s
  ON s.schema_id = p.schema_id
WHERE s.name = @procedure_schema
AND   p.name LIKE @procedure_name
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #procedure_object_ids
        WITH
            (TABLOCK)
        (
            object_id
        )
        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_schema sysname,
            @procedure_name sysname',
            @procedure_schema,
            @procedure_name;

        IF ROWCOUNT_BIG() = 0
        BEGIN
            RAISERROR('No procedures matching %s were found in schema %s', 11, 1, @procedure_name, @procedure_schema) WITH NOWAIT;
            RETURN;
        END;

        /*Check if any of the matching procedures exist in Query Store*/
        SELECT
            @sql = @isolation_level;

        SELECT
            @sql += N'
SELECT
    @procedure_exists =
        MAX(x.procedure_exists)
FROM
(
    SELECT
        procedure_exists =
            CASE
                WHEN EXISTS
                     (
                         SELECT
                             1/0
                         FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                         WHERE EXISTS
                         (
                             SELECT
                                 1/0
                             FROM #procedure_object_ids AS p
                             WHERE qsq.object_id = p.object_id
                         )
                     )
                THEN 1
                ELSE 0
            END
) AS x
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_exists bit OUTPUT',
            @procedure_exists OUTPUT;

        IF @procedure_exists = 0
        BEGIN
            RAISERROR('The stored procedures matching %s do not appear to have any entries in Query Store for database %s
Check that you spelled everything correctly and you''re in the right database',
                       10, 1, @procedure_name, @database_name) WITH NOWAIT;
            RETURN;
        END;
    END;
    ELSE
    /*Check if procedure exists in Query Store - single procedure (no wildcards)*/
    BEGIN
        SELECT
            @sql = @isolation_level;

        SELECT
            @sql += N'
SELECT
    @procedure_exists =
        CASE
            WHEN EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                     WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
                 )
            THEN 1
            ELSE 0
        END
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_exists bit OUTPUT,
            @procedure_name_quoted sysname',
            @procedure_exists OUTPUT,
            @procedure_name_quoted;

        IF @procedure_exists = 0
        BEGIN
            RAISERROR('The stored procedure %s does not appear to have any entries in Query Store for database %s
Check that you spelled everything correctly and you''re in the right database',
                       10, 1, @procedure_name, @database_name) WITH NOWAIT;
            RETURN;
        END;
    END;
END;

/*
Create temp tables for filter parameters
*/
CREATE TABLE
    #include_plan_ids
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id)
);


CREATE TABLE
    #include_query_ids
(
    query_id bigint NOT NULL,
    INDEX query_id CLUSTERED (query_id)
);

CREATE TABLE
    #ignore_plan_ids
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id)
);

CREATE TABLE
    #ignore_query_ids
(
    query_id bigint NOT NULL,
    INDEX query_id CLUSTERED (query_id)
);

CREATE TABLE
    #query_text_search
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id)
);


CREATE TABLE
    #query_text_search_not
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id)
);

/*
Create Query Store temp tables
*/
CREATE TABLE
    #query_store_runtime_stats
(
    database_id integer NOT NULL,
    runtime_stats_id bigint NOT NULL,
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id),
    runtime_stats_interval_id bigint NOT NULL,
    execution_type_desc nvarchar(60) NULL,
    first_execution_time datetimeoffset(7) NOT NULL,
    last_execution_time datetimeoffset(7) NOT NULL,
    count_executions bigint NOT NULL,
    executions_per_second AS
        ISNULL
        (
            count_executions /
                NULLIF
                (
                    DATEDIFF
                    (
                        SECOND,
                        first_execution_time,
                        last_execution_time
                    ),
                    0
                ),
            0
        ),
    avg_duration_ms float NULL,
    last_duration_ms bigint NOT NULL,
    min_duration_ms bigint NOT NULL,
    max_duration_ms bigint NOT NULL,
    total_duration_ms AS
        (avg_duration_ms * count_executions),
    avg_cpu_time_ms float NULL,
    last_cpu_time_ms bigint NOT NULL,
    min_cpu_time_ms bigint NOT NULL,
    max_cpu_time_ms bigint NOT NULL,
    total_cpu_time_ms AS
        (avg_cpu_time_ms * count_executions),
    avg_logical_io_reads_mb float NULL,
    last_logical_io_reads_mb bigint NOT NULL,
    min_logical_io_reads_mb bigint NOT NULL,
    max_logical_io_reads_mb bigint NOT NULL,
    total_logical_io_reads_mb AS
        (avg_logical_io_reads_mb * count_executions),
    avg_logical_io_writes_mb float NULL,
    last_logical_io_writes_mb bigint NOT NULL,
    min_logical_io_writes_mb bigint NOT NULL,
    max_logical_io_writes_mb bigint NOT NULL,
    total_logical_io_writes_mb AS
        (avg_logical_io_writes_mb * count_executions),
    avg_physical_io_reads_mb float NULL,
    last_physical_io_reads_mb bigint NOT NULL,
    min_physical_io_reads_mb bigint NOT NULL,
    max_physical_io_reads_mb bigint NOT NULL,
    total_physical_io_reads_mb AS
        (avg_physical_io_reads_mb * count_executions),
    avg_clr_time_ms float NULL,
    last_clr_time_ms bigint NOT NULL,
    min_clr_time_ms bigint NOT NULL,
    max_clr_time_ms bigint NOT NULL,
    total_clr_time_ms AS
        (avg_clr_time_ms * count_executions),
    last_dop bigint NOT NULL,
    min_dop bigint NOT NULL,
    max_dop bigint NOT NULL,
    avg_query_max_used_memory_mb float NULL,
    last_query_max_used_memory_mb bigint NOT NULL,
    min_query_max_used_memory_mb bigint NOT NULL,
    max_query_max_used_memory_mb bigint NOT NULL,
    total_query_max_used_memory_mb AS
        (avg_query_max_used_memory_mb * count_executions),
    avg_rowcount float NULL,
    last_rowcount bigint NOT NULL,
    min_rowcount bigint NOT NULL,
    max_rowcount bigint NOT NULL,
    total_rowcount AS
        (avg_rowcount * count_executions),
    avg_num_physical_io_reads_mb float NULL,
    last_num_physical_io_reads_mb bigint NULL,
    min_num_physical_io_reads_mb bigint NULL,
    max_num_physical_io_reads_mb bigint NULL,
    total_num_physical_io_reads_mb AS
        (avg_num_physical_io_reads_mb * count_executions),
    avg_log_bytes_used_mb float NULL,
    last_log_bytes_used_mb bigint NULL,
    min_log_bytes_used_mb bigint NULL,
    max_log_bytes_used_mb bigint NULL,
    total_log_bytes_used_mb AS
        (avg_log_bytes_used_mb * count_executions),
    avg_tempdb_space_used_mb float NULL,
    last_tempdb_space_used_mb bigint NULL,
    min_tempdb_space_used_mb bigint NULL,
    max_tempdb_space_used_mb bigint NULL,
    total_tempdb_space_used_mb AS
        (avg_tempdb_space_used_mb * count_executions),
    context_settings nvarchar(256) NULL
);

CREATE TABLE
    #query_store_wait_stats
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id),
    wait_category_desc nvarchar(60) NOT NULL,
    total_query_wait_time_ms bigint NOT NULL,
    avg_query_wait_time_ms float NULL,
    last_query_wait_time_ms bigint NOT NULL,
    min_query_wait_time_ms bigint NOT NULL,
    max_query_wait_time_ms bigint NOT NULL
);

CREATE TABLE
    #query_store_plan_feedback
(
    database_id integer NOT NULL,
    plan_feedback_id bigint NOT NULL,
    plan_id bigint NULL,
    INDEX plan_id CLUSTERED (plan_id),
    feature_desc nvarchar(120) NULL,
    feedback_data nvarchar(max) NULL,
    state_desc nvarchar(120) NULL,
    create_time datetimeoffset(7) NOT NULL,
    last_updated_time datetimeoffset(7) NULL
);

CREATE TABLE
    #query_store_query_hints
(
    database_id integer NOT NULL,
    query_hint_id bigint NOT NULL,
    query_id bigint NOT NULL,
    INDEX query_id CLUSTERED (query_id),
    query_hint_text nvarchar(max) NULL,
    last_query_hint_failure_reason_desc nvarchar(256) NULL,
    query_hint_failure_count bigint NOT NULL,
    source_desc nvarchar(256) NULL
);

CREATE TABLE
    #query_store_query_variant
(
    database_id integer NOT NULL,
    query_variant_query_id bigint NOT NULL,
    INDEX query_variant_query_id CLUSTERED (query_variant_query_id),
    parent_query_id bigint NOT NULL,
    dispatcher_plan_id bigint NOT NULL
);

CREATE TABLE
    #query_context_settings
(
    database_id integer NOT NULL,
    context_settings_id bigint NOT NULL,
    INDEX context_settings_id CLUSTERED (context_settings_id),
    set_options varbinary(8) NULL,
    language_id smallint NOT NULL,
    date_format smallint NOT NULL,
    date_first tinyint NOT NULL,
    status varbinary(2) NULL,
    required_cursor_options integer NOT NULL,
    acceptable_cursor_options integer NOT NULL,
    merge_action_type smallint NOT NULL,
    default_schema_id integer NOT NULL,
    is_replication_specific bit NOT NULL,
    is_contained varbinary(1) NULL
);

CREATE TABLE
    #query_store_query
(
    database_id integer NOT NULL,
    query_id bigint NOT NULL,
    INDEX query_id CLUSTERED (query_id),
    query_text_id bigint NOT NULL,
    context_settings_id bigint NOT NULL,
    object_id bigint NULL,
    object_name AS
        ISNULL
        (
            QUOTENAME
            (
                OBJECT_SCHEMA_NAME
                (
                    object_id,
                    database_id
                )
            ) +
            N'.' +
            QUOTENAME
            (
                OBJECT_NAME
                (
                    object_id,
                    database_id
                )
            ),
            CASE
                WHEN object_id > 0
                THEN N'Unknown object_id: ' +
                     RTRIM(object_id)
                ELSE N'Adhoc'
            END
        ),
    query_hash binary(8) NOT NULL,
    initial_compile_start_time datetimeoffset(7) NOT NULL,
    last_compile_start_time datetimeoffset(7) NULL,
    last_execution_time datetimeoffset(7) NULL
);

CREATE TABLE
    #query_store_query_text
(
    database_id integer NOT NULL,
    query_text_id bigint NOT NULL,
    INDEX query_text_id CLUSTERED (query_text_id),
    query_sql_text nvarchar(max) NULL,
    query_sql_text_normalized AS
        REPLACE
        (
            REPLACE
            (
                REPLACE
                (
                    REPLACE
                    (
                        REPLACE
                        (
                            REPLACE
                            (
                                query_sql_text,
                                NCHAR(13),
                                N' '
                            ),
                            NCHAR(10),
                            N' '
                        ),
                        NCHAR(9),
                        N' '
                    ),
                    N' ',
                    NCHAR(1) + NCHAR(2)
                ),
                NCHAR(2) + NCHAR(1),
                N''
            ),
            NCHAR(1) + NCHAR(2),
            N' '
        ) PERSISTED,
    query_sql_text_clickable xml NULL,
    statement_sql_handle varbinary(64) NULL,
    is_part_of_encrypted_module bit NOT NULL,
    has_restricted_text bit NOT NULL
);

CREATE TABLE
    #query_store_plan
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    query_id bigint NOT NULL,
    all_plan_ids varchar(max),
    plan_group_id bigint NULL,
    engine_version nvarchar(32) NULL,
    compatibility_level smallint NOT NULL,
    query_plan_hash binary(8) NOT NULL,
    query_plan nvarchar(max) NULL,
    is_online_index_plan bit NOT NULL,
    is_trivial_plan bit NOT NULL,
    is_parallel_plan bit NOT NULL,
    is_forced_plan bit NOT NULL,
    is_natively_compiled bit NOT NULL,
    force_failure_count bigint NOT NULL,
    last_force_failure_reason_desc nvarchar(128) NULL,
    count_compiles bigint NULL,
    initial_compile_start_time datetimeoffset(7) NOT NULL,
    last_compile_start_time datetimeoffset(7) NULL,
    last_execution_time datetimeoffset(7) NULL,
    avg_compile_duration_ms float NULL,
    last_compile_duration_ms bigint NULL,
    plan_forcing_type_desc nvarchar(60) NULL,
    has_compile_replay_script bit NULL,
    is_optimized_plan_forcing_disabled bit NULL,
    plan_type_desc nvarchar(120) NULL,
    INDEX plan_id_query_id CLUSTERED (plan_id, query_id)
);

CREATE TABLE
    #query_parameters
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id),
    parameter_name sysname NULL,
    parameter_data_type sysname NULL,
    parameter_compiled_value nvarchar(max) NULL
);

CREATE TABLE
    #reproduction_warnings
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id),
    warning_type nvarchar(50) NULL,
    warning_message nvarchar(max) NULL
);

CREATE TABLE
    #repro_queries
(
    plan_id bigint NOT NULL,
    query_id bigint NOT NULL,
    executable_query nvarchar(MAX) NULL,
    INDEX plan_id_query_id CLUSTERED (plan_id, query_id)
);

CREATE TABLE
    #embedded_constants
(
    plan_id bigint NOT NULL,
    INDEX plan_id CLUSTERED (plan_id),
    constant_value nvarchar(max) NULL
);

/*
Populate filter temp tables using XML-based string splitting for compatibility
*/
IF @include_plan_ids IS NOT NULL
BEGIN
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #include_plan_ids
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT
        ids.plan_id
    FROM
    (
        SELECT
            plan_id =
                x.x.value
                (
                    ''(./text())[1]'',
                    ''bigint''
                )
        FROM
        (
            SELECT
                ids =
                    CONVERT
                    (
                        xml,
                        ''<x>'' +
                        REPLACE
                        (
                            @include_plan_ids,
                            '','',
                            ''</x><x>''
                        ) +
                        ''</x>''
                    )
        ) AS ids
        CROSS APPLY ids.ids.nodes(''x'') AS x (x)
    ) AS ids
    WHERE ids.plan_id IS NOT NULL
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
        N'@include_plan_ids nvarchar(4000)',
        @include_plan_ids;
END;

IF @include_query_ids IS NOT NULL
BEGIN
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #include_query_ids
    WITH
        (TABLOCK)
    (
        query_id
    )
    SELECT
        ids.query_id
    FROM
    (
        SELECT
            query_id =
                x.x.value
                (
                    ''(./text())[1]'',
                    ''bigint''
                )
        FROM
        (
            SELECT
                ids =
                    CONVERT
                    (
                        xml,
                        ''<x>'' +
                        REPLACE
                        (
                            @include_query_ids,
                            '','',
                            ''</x><x>''
                        ) +
                        ''</x>''
                    )
        ) AS ids
        CROSS APPLY ids.ids.nodes(''x'') AS x (x)
    ) AS ids
    WHERE ids.query_id IS NOT NULL
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
        N'@include_query_ids nvarchar(4000)',
        @include_query_ids;

    /*Convert query IDs to plan IDs for filtering*/
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #include_plan_ids
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM #include_query_ids AS iqi
              WHERE iqi.query_id = qsp.query_id
          )
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql;
END;

IF @ignore_plan_ids IS NOT NULL
BEGIN
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #ignore_plan_ids
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT
        x.plan_id
    FROM
    (
        SELECT
            plan_id =
                x.value
                (
                    ''(./text())[1]'',
                    ''bigint''
                )
        FROM
        (
            SELECT
                x =
                    CONVERT
                    (
                        xml,
                        ''<x>'' +
                        REPLACE
                        (
                            @ignore_plan_ids,
                            '','',
                            ''</x><x>''
                        ) +
                        ''</x>''
                    )
        ) AS a
        CROSS APPLY a.x.nodes(''x'') AS b (x)
    ) AS x
    WHERE x.plan_id IS NOT NULL
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
        N'@ignore_plan_ids nvarchar(4000)',
        @ignore_plan_ids;
END;

IF @ignore_query_ids IS NOT NULL
BEGIN
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #ignore_query_ids
    WITH
        (TABLOCK)
    (
        query_id
    )
    SELECT
        x.query_id
    FROM
    (
        SELECT
            query_id =
                x.value
                (
                    ''(./text())[1]'',
                    ''bigint''
                )
        FROM
        (
            SELECT
                x =
                    CONVERT
                    (
                        xml,
                        ''<x>'' +
                        REPLACE
                        (
                            @ignore_query_ids,
                            '','',
                            ''</x><x>''
                        ) +
                        ''</x>''
                    )
        ) AS a
        CROSS APPLY a.x.nodes(''x'') AS b (x)
    ) AS x
    WHERE x.query_id IS NOT NULL
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
        N'@ignore_query_ids nvarchar(4000)',
        @ignore_query_ids;

    /*Convert query IDs to plan IDs for filtering*/
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #ignore_plan_ids
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM #ignore_query_ids AS iqi
              WHERE iqi.query_id = qsp.query_id
          )
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql;
END;

/*Process @query_text_search parameter*/
IF @query_text_search IS NOT NULL
BEGIN
    /*Add leading wildcard if missing*/
    IF
    (
        LEFT
        (
            @query_text_search,
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search =
                N'%' + @query_text_search;
    END;

    /*Add trailing wildcard if missing*/
    IF
    (
        LEFT
        (
            REVERSE
            (
                @query_text_search
            ),
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search =
                @query_text_search + N'%';
    END;

    /*Populate #query_text_search with plan IDs matching the search text*/
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #query_text_search
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE qsp.query_id = qsq.query_id
              AND EXISTS
                  (
                      SELECT
                          1/0
                      FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
                      WHERE qsqt.query_text_id = qsq.query_text_id
                      AND   qsqt.query_sql_text LIKE @query_text_search
                  )
          )';

    /*Add procedure filter if specified - single procedure*/
    IF
    (
        @procedure_name IS NOT NULL
    AND @procedure_exists = 1
    AND CHARINDEX(N'%', @procedure_name) = 0
    )
    BEGIN
        SELECT
            @sql += N'
    AND   qsp.query_id IN
          (
              SELECT
                  qsq.query_id
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
          )';
    END;

    /*Add procedure filter if specified - wildcard*/
    IF
    (
        @procedure_name IS NOT NULL
    AND @procedure_exists = 1
    AND CHARINDEX(N'%', @procedure_name) > 0
    )
    BEGIN
        SELECT
            @sql += N'
    AND   qsp.query_id IN
          (
              SELECT
                  qsq.query_id
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE EXISTS
              (
                  SELECT
                      1/0
                  FROM #procedure_object_ids AS poi
                  WHERE poi.object_id = qsq.object_id
              )
          )';
    END;

    SELECT
        @sql += N'
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
      N'@query_text_search nvarchar(4000),
        @procedure_name_quoted nvarchar(1024)',
        @query_text_search,
        @procedure_name_quoted;
END;

/*Process @query_text_search_not parameter*/
IF @query_text_search_not IS NOT NULL
BEGIN
    /*Add leading wildcard if missing*/
    IF
    (
        LEFT
        (
            @query_text_search_not,
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search_not =
                N'%' + @query_text_search_not;
    END;

    /*Add trailing wildcard if missing*/
    IF
    (
        LEFT
        (
            REVERSE
            (
                @query_text_search_not
            ),
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search_not =
                @query_text_search_not + N'%';
    END;

    /*Populate #query_text_search_not with plan IDs to exclude*/
    SELECT
        @sql = @isolation_level;

    SELECT
        @sql += N'
    INSERT
        #query_text_search_not
    WITH
        (TABLOCK)
    (
        plan_id
    )
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE qsp.query_id = qsq.query_id
              AND EXISTS
                  (
                      SELECT
                          1/0
                      FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
                      WHERE qsqt.query_text_id = qsq.query_text_id
                      AND   qsqt.query_sql_text LIKE @query_text_search_not
                  )
          )';

    /*Add procedure filter if specified - single procedure*/
    IF
    (
        @procedure_name IS NOT NULL
    AND @procedure_exists = 1
    AND CHARINDEX(N'%', @procedure_name) = 0
    )
    BEGIN
        SELECT
            @sql += N'
    AND   qsp.query_id IN
          (
              SELECT
                  qsq.query_id
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
          )';
    END;

    /*Add procedure filter if specified - wildcard*/
    IF
    (
        @procedure_name IS NOT NULL
    AND @procedure_exists = 1
    AND CHARINDEX(N'%', @procedure_name) > 0
    )
    BEGIN
        SELECT
            @sql += N'
    AND   qsp.query_id IN
          (
              SELECT
                  qsq.query_id
              FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
              WHERE EXISTS
              (
                  SELECT
                      1/0
                  FROM #procedure_object_ids AS poi
                  WHERE poi.object_id = qsq.object_id
              )
          )';
    END;

    SELECT
        @sql += N'
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
      N'@query_text_search_not nvarchar(4000),
        @procedure_name_quoted nvarchar(1024)',
        @query_text_search_not,
        @procedure_name_quoted;
END;

/*
Populate #query_store_runtime_stats from sys.query_store_runtime_stats
This aggregates runtime stats for all filtered plan IDs
*/
SELECT
    @sql = @isolation_level;

SELECT
    @sql += N'
INSERT
    #query_store_runtime_stats
WITH
    (TABLOCK)
(
    database_id,
    runtime_stats_id,
    plan_id,
    runtime_stats_interval_id,
    execution_type_desc,
    first_execution_time,
    last_execution_time,
    count_executions,
    avg_duration_ms,
    last_duration_ms,
    min_duration_ms,
    max_duration_ms,
    avg_cpu_time_ms,
    last_cpu_time_ms,
    min_cpu_time_ms,
    max_cpu_time_ms,
    avg_logical_io_reads_mb,
    last_logical_io_reads_mb,
    min_logical_io_reads_mb,
    max_logical_io_reads_mb,
    avg_logical_io_writes_mb,
    last_logical_io_writes_mb,
    min_logical_io_writes_mb,
    max_logical_io_writes_mb,
    avg_physical_io_reads_mb,
    last_physical_io_reads_mb,
    min_physical_io_reads_mb,
    max_physical_io_reads_mb,
    avg_clr_time_ms,
    last_clr_time_ms,
    min_clr_time_ms,
    max_clr_time_ms,
    last_dop,
    min_dop,
    max_dop,
    avg_query_max_used_memory_mb,
    last_query_max_used_memory_mb,
    min_query_max_used_memory_mb,
    max_query_max_used_memory_mb,
    avg_rowcount,
    last_rowcount,
    min_rowcount,
    max_rowcount,
    avg_num_physical_io_reads_mb,
    last_num_physical_io_reads_mb,
    min_num_physical_io_reads_mb,
    max_num_physical_io_reads_mb,
    avg_log_bytes_used_mb,
    last_log_bytes_used_mb,
    min_log_bytes_used_mb,
    max_log_bytes_used_mb,
    avg_tempdb_space_used_mb,
    last_tempdb_space_used_mb,
    min_tempdb_space_used_mb,
    max_tempdb_space_used_mb,
    context_settings
)
SELECT
    @database_id,
    MAX(qsrs_with_lasts.runtime_stats_id),
    qsrs_with_lasts.plan_id,
    MAX(qsrs_with_lasts.runtime_stats_interval_id),
    MAX(qsrs_with_lasts.execution_type_desc),
    MIN(qsrs_with_lasts.first_execution_time),
    MAX(qsrs_with_lasts.partitioned_last_execution_time),
    SUM(qsrs_with_lasts.count_executions),
    AVG((qsrs_with_lasts.avg_duration / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_duration / 1000.)),
    MIN((qsrs_with_lasts.min_duration / 1000.)),
    MAX((qsrs_with_lasts.max_duration / 1000.)),
    AVG((qsrs_with_lasts.avg_cpu_time / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_cpu_time / 1000.)),
    MIN((qsrs_with_lasts.min_cpu_time / 1000.)),
    MAX((qsrs_with_lasts.max_cpu_time / 1000.)),
    AVG((qsrs_with_lasts.avg_logical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_logical_io_reads * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_logical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_logical_io_reads * 8.) / 1024.),
    AVG((qsrs_with_lasts.avg_logical_io_writes * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_logical_io_writes * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_logical_io_writes * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_logical_io_writes * 8.) / 1024.),
    AVG((qsrs_with_lasts.avg_physical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_physical_io_reads * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_physical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_physical_io_reads * 8.) / 1024.),
    AVG((qsrs_with_lasts.avg_clr_time / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_clr_time / 1000.)),
    MIN((qsrs_with_lasts.min_clr_time / 1000.)),
    MAX((qsrs_with_lasts.max_clr_time / 1000.)),
    MAX(qsrs_with_lasts.partitioned_last_dop),
    MIN(qsrs_with_lasts.min_dop),
    MAX(qsrs_with_lasts.max_dop),
    AVG((qsrs_with_lasts.avg_query_max_used_memory * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_query_max_used_memory * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_query_max_used_memory * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_query_max_used_memory * 8.) / 1024.),
    AVG(qsrs_with_lasts.avg_rowcount * 1.),
    MAX(qsrs_with_lasts.partitioned_last_rowcount),
    MIN(qsrs_with_lasts.min_rowcount),
    MAX(qsrs_with_lasts.max_rowcount),';

/*Add SQL 2017+ columns*/
IF @sql_2017 = 1
BEGIN
    SELECT @sql += N'
    AVG((qsrs_with_lasts.avg_num_physical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_num_physical_io_reads * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_num_physical_io_reads * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_num_physical_io_reads * 8.) / 1024.),
    AVG(qsrs_with_lasts.avg_log_bytes_used / 1000000.),
    MAX(qsrs_with_lasts.partitioned_last_log_bytes_used / 1000000.),
    MIN(qsrs_with_lasts.min_log_bytes_used / 1000000.),
    MAX(qsrs_with_lasts.max_log_bytes_used / 1000000.),
    AVG((qsrs_with_lasts.avg_tempdb_space_used * 8.) / 1024.),
    MAX((qsrs_with_lasts.partitioned_last_tempdb_space_used * 8.) / 1024.),
    MIN((qsrs_with_lasts.min_tempdb_space_used * 8.) / 1024.),
    MAX((qsrs_with_lasts.max_tempdb_space_used * 8.) / 1024.),';
END;
ELSE
BEGIN
    SELECT @sql += N'
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,';
END;

SELECT @sql += N'
    context_settings = NULL
FROM
(
    SELECT
        qsrs.*,
        partitioned_last_execution_time =
            LAST_VALUE(qsrs.last_execution_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_duration =
            LAST_VALUE(qsrs.last_duration) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_cpu_time =
            LAST_VALUE(qsrs.last_cpu_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_logical_io_reads =
            LAST_VALUE(qsrs.last_logical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_logical_io_writes =
            LAST_VALUE(qsrs.last_logical_io_writes) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_physical_io_reads =
            LAST_VALUE(qsrs.last_physical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_clr_time =
            LAST_VALUE(qsrs.last_clr_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_dop =
            LAST_VALUE(qsrs.last_dop) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_query_max_used_memory =
            LAST_VALUE(qsrs.last_query_max_used_memory) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_rowcount =
            LAST_VALUE(qsrs.last_rowcount) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),';

/*Add SQL 2017+ windowing columns*/
IF @sql_2017 = 1
BEGIN
    SELECT @sql += N'
        partitioned_last_num_physical_io_reads =
            LAST_VALUE(qsrs.last_num_physical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_log_bytes_used =
            LAST_VALUE(qsrs.last_log_bytes_used) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_tempdb_space_used =
            LAST_VALUE(qsrs.last_tempdb_space_used) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            )';
END;
ELSE
BEGIN
    SELECT @sql += N'
        partitioned_last_num_physical_io_reads = NULL,
        partitioned_last_log_bytes_used = NULL,
        partitioned_last_tempdb_space_used = NULL';
END;

SELECT @sql += N'
    FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
              WHERE qsp.plan_id = qsrs.plan_id
              AND   qsp.is_online_index_plan = 0
          )';

/*Add date filtering if specified*/
IF @start_date <= @end_date
BEGIN
    SELECT
        @sql += N'
    AND   qsrs.last_execution_time >= @start_date
    AND   qsrs.last_execution_time < @end_date';
END;

/*Add include plan IDs filter if specified*/
IF
(
    @include_plan_ids IS NOT NULL
 OR @include_query_ids IS NOT NULL
)
BEGIN
    SELECT
        @sql += N'
    AND   EXISTS
          (
              SELECT
                  1/0
              FROM #include_plan_ids AS ipi
              WHERE ipi.plan_id = qsrs.plan_id
          )';
END;

/*Add ignore plan IDs filter if specified*/
IF
(
    @ignore_plan_ids IS NOT NULL
 OR @ignore_query_ids IS NOT NULL
)
BEGIN
    SELECT
        @sql += N'
    AND   NOT EXISTS
          (
              SELECT
                  1/0
              FROM #ignore_plan_ids AS ipi
              WHERE ipi.plan_id = qsrs.plan_id
          )';
END;

/*Add query text search filter if specified*/
IF @query_text_search IS NOT NULL
BEGIN
    SELECT
        @sql += N'
    AND   EXISTS
          (
              SELECT
                  1/0
              FROM #query_text_search AS qts
              WHERE qts.plan_id = qsrs.plan_id
          )';
END;

/*Add query text exclusion filter if specified*/
IF @query_text_search_not IS NOT NULL
BEGIN
    SELECT
        @sql += N'
    AND   NOT EXISTS
          (
              SELECT
                  1/0
              FROM #query_text_search_not AS qtsn
              WHERE qtsn.plan_id = qsrs.plan_id
          )';
END;

/*Add procedure filter if specified - single procedure*/
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
AND CHARINDEX(N'%', @procedure_name) = 0
)
BEGIN
    SELECT
        @sql += N'
    AND   qsrs.plan_id IN
          (
              SELECT
                  qsp.plan_id
              FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
              WHERE qsp.query_id IN
                    (
                        SELECT
                            qsq.query_id
                        FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                        WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
                    )
          )';
END;

/*Add procedure filter if specified - wildcard*/
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
AND CHARINDEX(N'%', @procedure_name) > 0
)
BEGIN
    SELECT
        @sql += N'
    AND   qsrs.plan_id IN
          (
              SELECT
                  qsp.plan_id
              FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
              WHERE qsp.query_id IN
                    (
                        SELECT
                            qsq.query_id
                        FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                        WHERE EXISTS
                        (
                            SELECT
                                1/0
                            FROM #procedure_object_ids AS poi
                            WHERE poi.object_id = qsq.object_id
                        )
                    )
          )';
END;

SELECT @sql += N'
) AS qsrs_with_lasts
GROUP BY
    qsrs_with_lasts.plan_id
ORDER BY
    MAX(qsrs_with_lasts.partitioned_last_execution_time) DESC
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

/*Execute the population query*/
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer,
    @start_date datetimeoffset(7),
    @end_date datetimeoffset(7),
    @procedure_name_quoted nvarchar(1024)',
    @database_id,
    @start_date,
    @end_date,
    @procedure_name_quoted;

/*
Populate #query_store_plan from sys.query_store_plan
This will pull plan details for all filtered plan IDs
*/
SELECT
    @sql = @isolation_level;

SELECT
    @sql += N'
INSERT
    #query_store_plan
WITH
    (TABLOCK)
(
    database_id,
    plan_id,
    query_id,
    all_plan_ids,
    plan_group_id,
    engine_version,
    compatibility_level,
    query_plan_hash,
    query_plan,
    is_online_index_plan,
    is_trivial_plan,
    is_parallel_plan,
    is_forced_plan,
    is_natively_compiled,
    force_failure_count,
    last_force_failure_reason_desc,
    count_compiles,
    initial_compile_start_time,
    last_compile_start_time,
    last_execution_time,
    avg_compile_duration_ms,
    last_compile_duration_ms,
    plan_forcing_type_desc,
    has_compile_replay_script,
    is_optimized_plan_forcing_disabled,
    plan_type_desc
)
SELECT
    @database_id,
    qsp.plan_id,
    qsp.query_id,
    all_plan_ids =
        STUFF
        (
            (
                SELECT DISTINCT
                    '', '' +
                    RTRIM
                        (qsp_plans.plan_id)
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp_plans
                WHERE qsp_plans.query_id = qsp.query_id
                FOR XML
                    PATH(''''),
                    TYPE
            ).value(''./text()[1]'', ''varchar(max)''),
            1,
            2,
            ''''
        ),
    qsp.plan_group_id,
    qsp.engine_version,
    qsp.compatibility_level,
    qsp.query_plan_hash,
    qsp.query_plan,
    qsp.is_online_index_plan,
    qsp.is_trivial_plan,
    qsp.is_parallel_plan,
    qsp.is_forced_plan,
    qsp.is_natively_compiled,
    qsp.force_failure_count,
    qsp.last_force_failure_reason_desc,
    qsp.count_compiles,
    qsp.initial_compile_start_time,
    qsp.last_compile_start_time,
    qsp.last_execution_time,
    (qsp.avg_compile_duration / 1000.),
    (qsp.last_compile_duration / 1000.),';

/*Add version-specific columns*/
IF @sql_2022_views = 1
BEGIN
    SELECT @sql += N'
    qsp.plan_forcing_type_desc,
    qsp.has_compile_replay_script,
    qsp.is_optimized_plan_forcing_disabled,
    qsp.plan_type_desc';
END;
ELSE IF @sql_2017 = 1
BEGIN
    SELECT @sql += N'
    qsp.plan_forcing_type_desc,
    NULL,
    NULL,
    NULL';
END;
ELSE
BEGIN
    SELECT @sql += N'
    NULL,
    NULL,
    NULL,
    NULL';
END;

/*Add FROM clause and filtering*/
SELECT
    @sql += N'
FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
WHERE qsp.is_online_index_plan = 0';

/*Add date filtering if specified*/
IF @start_date <= @end_date
BEGIN
    SELECT
        @sql += N'
AND   qsp.last_execution_time >= @start_date
AND   qsp.last_execution_time < @end_date';
END;

/*Add include plan IDs filter if specified*/
IF
(
    @include_plan_ids IS NOT NULL
 OR @include_query_ids IS NOT NULL
)
BEGIN
    SELECT
        @sql += N'
AND   EXISTS
      (
          SELECT
              1/0
          FROM #include_plan_ids AS ipi
          WHERE ipi.plan_id = qsp.plan_id
      )';
END;

/*Add ignore plan IDs filter if specified*/
IF
(
    @ignore_plan_ids IS NOT NULL
 OR @ignore_query_ids IS NOT NULL
)
BEGIN
    SELECT
        @sql += N'
AND   NOT EXISTS
      (
          SELECT
              1/0
          FROM #ignore_plan_ids AS ipi
          WHERE ipi.plan_id = qsp.plan_id
      )';
END;

/*Add query text search filter if specified*/
IF @query_text_search IS NOT NULL
BEGIN
    SELECT
        @sql += N'
AND   EXISTS
      (
          SELECT
              1/0
          FROM #query_text_search AS qts
          WHERE qts.plan_id = qsp.plan_id
      )';
END;

/*Add query text exclusion filter if specified*/
IF @query_text_search_not IS NOT NULL
BEGIN
    SELECT
        @sql += N'
AND   NOT EXISTS
      (
          SELECT
              1/0
          FROM #query_text_search_not AS qtsn
          WHERE qtsn.plan_id = qsp.plan_id
      )';
END;

/*Add procedure filter if specified - single procedure*/
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
AND CHARINDEX(N'%', @procedure_name) = 0
)
BEGIN
    SELECT
        @sql += N'
AND   qsp.query_id IN
      (
          SELECT
              qsq.query_id
          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
          WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
      )';
END;

/*Add procedure filter if specified - wildcard*/
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
AND CHARINDEX(N'%', @procedure_name) > 0
)
BEGIN
    SELECT
        @sql += N'
AND   qsp.query_id IN
      (
          SELECT
              qsq.query_id
          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
          WHERE EXISTS
          (
              SELECT
                  1/0
              FROM #procedure_object_ids AS poi
              WHERE poi.object_id = qsq.object_id
          )
      )';
END;

/*Add final ORDER BY and options*/
SELECT
    @sql += N'
ORDER BY
    qsp.last_execution_time DESC
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

/*Execute the population query*/
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer,
    @start_date datetimeoffset(7),
    @end_date datetimeoffset(7),
    @procedure_name_quoted nvarchar(1024)',
    @database_id,
    @start_date,
    @end_date,
    @procedure_name_quoted;

/*
Populate the #query_store_query table with query metadata
*/
SELECT
    @sql = @isolation_level,
    @current_table = N'inserting #query_store_query';

SELECT
    @sql += N'
SELECT
    @database_id,
    qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.object_id,
    qsq.query_hash,
    qsq.initial_compile_start_time,
    qsq.last_compile_start_time,
    qsq.last_execution_time
FROM #query_store_plan AS qsp
CROSS APPLY
(
    SELECT TOP (1)
        qsq.*
    FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
    WHERE qsq.query_id = qsp.query_id
    ORDER BY
        qsq.last_execution_time DESC
) AS qsq
WHERE qsp.database_id = @database_id
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_query
WITH
    (TABLOCK)
(
    database_id,
    query_id,
    query_text_id,
    context_settings_id,
    object_id,
    query_hash,
    initial_compile_start_time,
    last_compile_start_time,
    last_execution_time
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

/*
Populate the #query_store_query_text table with query text
*/
SELECT
    @sql = @isolation_level,
    @current_table = N'inserting #query_store_query_text';

SELECT
    @sql += N'
SELECT
    @database_id,
    qsqt.query_text_id,
    qsqt.query_sql_text,
    qsqt.statement_sql_handle,
    qsqt.is_part_of_encrypted_module,
    qsqt.has_restricted_text
FROM #query_store_query AS qsq
CROSS APPLY
(
    SELECT TOP (1)
        qsqt.*
    FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
    WHERE qsqt.query_text_id = qsq.query_text_id
) AS qsqt
WHERE qsq.database_id = @database_id
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_query_text
WITH
    (TABLOCK)
(
    database_id,
    query_text_id,
    query_sql_text,
    statement_sql_handle,
    is_part_of_encrypted_module,
    has_restricted_text
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

/*
Populate the clickable XML column
*/
UPDATE qsqt
SET qsqt.query_sql_text_clickable =
    (
        SELECT
            [processing-instruction(_)] =
                qsqt.query_sql_text
        FOR XML
            PATH(N''),
            TYPE
    )
FROM #query_store_query_text AS qsqt
OPTION(RECOMPILE);

/*
Populate the #query_context_settings table with context settings
*/
SELECT
    @sql = @isolation_level,
    @current_table = N'inserting #query_context_settings';

SELECT
    @sql += N'
SELECT
    @database_id,
    qcs.context_settings_id,
    qcs.set_options,
    qcs.language_id,
    qcs.date_format,
    qcs.date_first,
    qcs.status,
    qcs.required_cursor_options,
    qcs.acceptable_cursor_options,
    qcs.merge_action_type,
    qcs.default_schema_id,
    qcs.is_replication_specific,
    qcs.is_contained
FROM ' + @database_name_quoted + N'.sys.query_context_settings AS qcs
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_query AS qsq
          WHERE qsq.context_settings_id = qcs.context_settings_id
      )
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_context_settings
WITH
    (TABLOCK)
(
    database_id,
    context_settings_id,
    set_options,
    language_id,
    date_format,
    date_first,
    status,
    required_cursor_options,
    acceptable_cursor_options,
    merge_action_type,
    default_schema_id,
    is_replication_specific,
    is_contained
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

/*
Update things to get the context settings for each query
*/
SELECT
    @current_table = N'updating context_settings in #query_store_runtime_stats';

UPDATE
    qsrs
SET
    qsrs.context_settings =
        SUBSTRING
        (
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 1 = 1
                THEN ', ANSI_PADDING'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 8 = 8
                THEN ', CONCAT_NULL_YIELDS_NULL'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 16 = 16
                THEN ', ANSI_WARNINGS'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 32 = 32
                THEN ', ANSI_NULLS'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 64 = 64
                THEN ', QUOTED_IDENTIFIER'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 4096 = 4096
                THEN ', ARITHABORT'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 8192 = 8192
                THEN ', NUMERIC_ROUNDABORT'
                ELSE ''
            END,
            2,
            256
        )
FROM #query_store_runtime_stats AS qsrs
JOIN #query_store_plan AS qsp
  ON  qsrs.plan_id = qsp.plan_id
  AND qsrs.database_id = qsp.database_id
JOIN #query_store_query AS qsq
  ON  qsp.query_id = qsq.query_id
  AND qsp.database_id = qsq.database_id
JOIN #query_context_settings AS qcs
  ON  qsq.context_settings_id = qcs.context_settings_id
  AND qsq.database_id = qcs.database_id
OPTION(RECOMPILE);

/*
Populate the #query_store_wait_stats table with wait statistics (SQL 2017+)
*/
IF @sql_2017 = 1
BEGIN
    SELECT
        @sql = @isolation_level,
        @current_table = N'inserting #query_store_wait_stats';

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsws_with_lasts.plan_id,
    qsws_with_lasts.wait_category_desc,
    total_query_wait_time_ms =
        SUM(qsws_with_lasts.total_query_wait_time_ms),
    avg_query_wait_time_ms =
        SUM(qsws_with_lasts.avg_query_wait_time_ms),
    last_query_wait_time_ms =
        MAX(qsws_with_lasts.partitioned_last_query_wait_time_ms),
    min_query_wait_time_ms =
        SUM(qsws_with_lasts.min_query_wait_time_ms),
    max_query_wait_time_ms =
        SUM(qsws_with_lasts.max_query_wait_time_ms)
FROM
(
    SELECT
        qsws.*,
        partitioned_last_query_wait_time_ms =
            LAST_VALUE(qsws.last_query_wait_time_ms) OVER
            (
                PARTITION BY
                    qsws.plan_id,
                    qsws.execution_type,
                    qsws.wait_category_desc
                ORDER BY
                    qsws.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            )
    FROM #query_store_runtime_stats AS qsrs
    CROSS APPLY
    (
        SELECT TOP (5)
            qsws.*
        FROM ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws
        WHERE qsws.runtime_stats_interval_id = qsrs.runtime_stats_interval_id
        AND   qsws.plan_id = qsrs.plan_id
        AND   qsws.wait_category > 0
        AND   qsws.min_query_wait_time_ms > 0
        ORDER BY
            qsws.avg_query_wait_time_ms DESC
    ) AS qsws
    WHERE qsrs.database_id = @database_id
) AS qsws_with_lasts
GROUP BY
    qsws_with_lasts.plan_id,
    qsws_with_lasts.wait_category_desc
HAVING
    SUM(qsws_with_lasts.min_query_wait_time_ms) > 0.
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_wait_stats
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        wait_category_desc,
        total_query_wait_time_ms,
        avg_query_wait_time_ms,
        last_query_wait_time_ms,
        min_query_wait_time_ms,
        max_query_wait_time_ms
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;
END;

/*
Populate SQL 2022+ Query Store tables
*/
IF @sql_2022_views = 1
BEGIN
    /*
    Populate the #query_store_plan_feedback table
    */
    SELECT
        @sql = @isolation_level,
        @current_table = N'inserting #query_store_plan_feedback';

    SELECT
        @sql += N'
SELECT
    @database_id,
    qspf.plan_feedback_id,
    qspf.plan_id,
    qspf.feature_desc,
    qspf.feedback_data,
    qspf.state_desc,
    qspf.create_time,
    qspf.last_updated_time
FROM ' + @database_name_quoted + N'.sys.query_store_plan_feedback AS qspf
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qspf.plan_id = qsp.plan_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_plan_feedback
    WITH
        (TABLOCK)
    (
        database_id,
        plan_feedback_id,
        plan_id,
        feature_desc,
        feedback_data,
        state_desc,
        create_time,
        last_updated_time
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    /*
    Populate the #query_store_query_variant table
    */
    SELECT
        @sql = @isolation_level,
        @current_table = N'inserting #query_store_query_variant';

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsqv.query_variant_query_id,
    qsqv.parent_query_id,
    qsqv.dispatcher_plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_query_variant AS qsqv
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qsqv.query_variant_query_id = qsp.query_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_query_variant
    WITH
        (TABLOCK)
    (
        database_id,
        query_variant_query_id,
        parent_query_id,
        dispatcher_plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    /*
    Populate the #query_store_query_hints table
    */
    SELECT
        @sql = @isolation_level,
        @current_table = N'inserting #query_store_query_hints';

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsqh.query_hint_id,
    qsqh.query_id,
    qsqh.query_hint_text,
    qsqh.last_query_hint_failure_reason_desc,
    qsqh.query_hint_failure_count,
    qsqh.source_desc
FROM ' + @database_name_quoted + N'.sys.query_store_query_hints AS qsqh
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qsqh.query_id = qsp.query_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_query_hints
    WITH
        (TABLOCK)
    (
        database_id,
        query_hint_id,
        query_id,
        query_hint_text,
        last_query_hint_failure_reason_desc,
        query_hint_failure_count,
        source_desc
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;
END;

/*
Extract parameters from query plans
Uses substring extraction for performance,
avoids casting the full plan to XML
*/
SELECT
    @current_table = N'extracting parameters from query plans';

INSERT
    #query_parameters
WITH
    (TABLOCK)
(
    plan_id,
    parameter_name,
    parameter_data_type,
    parameter_compiled_value
)
SELECT
    qsp.plan_id,
    parameter_name =
        LTRIM(RTRIM(cr.c.value(N'@Column', N'sysname'))),
    parameter_data_type =
        LTRIM(RTRIM(cr.c.value(N'@ParameterDataType', N'sysname'))),
    parameter_compiled_value =
        CASE
            /*Strip parentheses from values like (13)*/
            WHEN cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)') LIKE N'(%)'
            THEN SUBSTRING
                 (
                     cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)'),
                     2,
                     LEN(cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)')) - 2
                 )
            /*Strip guid wrapper from values like {guid'...'}*/
            WHEN cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)') LIKE N'{guid''%''}'
            THEN N'''' +
                 SUBSTRING
                 (
                     cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)'),
                     7,
                     LEN(cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)')) - 8
                 ) +
                 N''''
            ELSE cr.c.value(N'@ParameterCompiledValue', N'nvarchar(MAX)')
        END
FROM #query_store_plan AS qsp
CROSS APPLY
(
    SELECT
        parameter_list_xml =
            CASE
                WHEN CHARINDEX(N'<ParameterList>', qsp.query_plan) > 0
                THEN TRY_CAST
                     (
                         SUBSTRING
                         (
                             qsp.query_plan,
                             CHARINDEX(N'<ParameterList>', qsp.query_plan),
                             CHARINDEX
                             (
                                 N'</ParameterList>',
                                 qsp.query_plan,
                                 CHARINDEX(N'<ParameterList>', qsp.query_plan)
                             )
                             + LEN(N'</ParameterList>')
                             - CHARINDEX(N'<ParameterList>', qsp.query_plan)
                         ) AS xml
                     )
            END
) AS x
CROSS APPLY x.parameter_list_xml.nodes(N'//ParameterList/ColumnReference') AS cr(c)
WHERE CHARINDEX(N'<ParameterList>', qsp.query_plan) > 0
AND   x.parameter_list_xml IS NOT NULL
OPTION(RECOMPILE);

/*
Fill in parameters declared in query text
but missing from the plan's ParameterList.
Uses ? as compiled value so the query cannot
be accidentally executed with incorrect values.
*/
SELECT
    @current_table = N'filling in missing parameters from query text';

INSERT
    #query_parameters
WITH
    (TABLOCK)
(
    plan_id,
    parameter_name,
    parameter_data_type,
    parameter_compiled_value
)
SELECT DISTINCT
    qsp.plan_id,
    parameter_name =
        LTRIM(RTRIM
        (
            LEFT
            (
                LTRIM(p.param_text.value(N'.', N'nvarchar(max)')),
                CHARINDEX
                (
                    N' ',
                    LTRIM(p.param_text.value(N'.', N'nvarchar(max)'))
                ) - 1
            )
        )),
    parameter_data_type =
        LTRIM(RTRIM
        (
            SUBSTRING
            (
                LTRIM(p.param_text.value(N'.', N'nvarchar(max)')),
                CHARINDEX
                (
                    N' ',
                    LTRIM(p.param_text.value(N'.', N'nvarchar(max)'))
                ) + 1,
                LEN(LTRIM(p.param_text.value(N'.', N'nvarchar(max)')))
            )
        )),
    parameter_compiled_value =
        N'?'
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
CROSS APPLY
(
    SELECT
        param_prefix =
            SUBSTRING
            (
                qsqt.query_sql_text,
                2,
                PATINDEX(N'%)[^,]%', qsqt.query_sql_text) - 2 +
                    CASE
                        WHEN SUBSTRING
                             (
                                 qsqt.query_sql_text,
                                 PATINDEX(N'%)[^,]%', qsqt.query_sql_text),
                                 2
                             ) = N'))'
                        THEN 1
                        ELSE 0
                    END
            )
    WHERE qsqt.query_sql_text LIKE N'(@%'
    AND   PATINDEX(N'%)[^,]%', qsqt.query_sql_text) > 0
) AS prefix
CROSS APPLY
(
    SELECT
        param_xml =
            TRY_CAST
            (
                N'<p>' +
                REPLACE(prefix.param_prefix, N',', N'</p><p>') +
                N'</p>' AS xml
            )
) AS px
CROSS APPLY px.param_xml.nodes(N'/p') AS p(param_text)
WHERE NOT EXISTS
(
    SELECT
        1/0
    FROM #query_parameters AS qp
    WHERE qp.plan_id = qsp.plan_id
    AND   qp.parameter_name = LTRIM(RTRIM
          (
              LEFT
              (
                  LTRIM(p.param_text.value(N'.', N'nvarchar(max)')),
                  CHARINDEX
                  (
                      N' ',
                      LTRIM(p.param_text.value(N'.', N'nvarchar(max)'))
                  ) - 1
              )
          ))
)
OPTION(RECOMPILE);

/*
Warn when parameters were missing from the plan ParameterList
*/
INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qp.plan_id,
    warning_type = N'missing parameter values',
    warning_message =
        N'Some parameters declared in the query text were not found in the plan ParameterList. ' +
        N'Their values are set to ? and must be filled in manually before executing.'
FROM #query_parameters AS qp
WHERE qp.parameter_compiled_value = N'?'
OPTION(RECOMPILE);

/*
Check for plans too large to cast to XML
*/
SELECT
    @current_table = N'checking for plans too large to cast to XML';

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qsp.plan_id,
    warning_type = N'plan too large for XML parsing',
    warning_message =
        N'Query plan could not be cast to XML (likely too large). ' +
        N'Parameters and embedded constants cannot be extracted using XQuery. ' +
        N'Manual review of plan text required.'
FROM #query_store_plan AS qsp
WHERE TRY_CAST(qsp.query_plan AS xml) IS NULL
OPTION(RECOMPILE);

/*
Detect OPTION(RECOMPILE) usage and extract embedded constants
*/
SELECT
    @current_table = N'checking for OPTION(RECOMPILE) and extracting embedded constants';

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qsp.plan_id,
    warning_type = N'parameter embedding optimization',
    warning_message =
        N'Query uses OPTION(RECOMPILE) with parameter embedding optimization. ' +
        N'Literal values are embedded throughout the plan instead of using parameters. ' +
        N'Parameter alignment may be incomplete. Review embedded constants and original query text.'
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
WHERE qsqt.query_sql_text LIKE N'%OPTION%(%RECOMPILE%)%'
OPTION(RECOMPILE);

/*
Extract embedded constants from plans with OPTION(RECOMPILE)
For plans that can be cast to XML, use XQuery
*/
SELECT
    @current_table = N'extracting embedded constants from plans';

INSERT
    #embedded_constants
WITH
    (TABLOCK)
(
    plan_id,
    constant_value
)
SELECT DISTINCT
    qsp.plan_id,
    constant_value =
        CASE
            WHEN c.const.value(N'@ConstValue', N'nvarchar(max)') LIKE N'(%)'
            THEN SUBSTRING
                 (
                     c.const.value(N'@ConstValue', N'nvarchar(max)'),
                     2,
                     LEN(c.const.value(N'@ConstValue', N'nvarchar(max)')) - 2
                 )
            ELSE LTRIM(RTRIM(c.const.value(N'@ConstValue', N'nvarchar(max)')))
        END
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
CROSS APPLY
(
    SELECT
        query_plan_xml =
            TRY_CAST(qsp.query_plan AS xml)
) AS x
CROSS APPLY x.query_plan_xml.nodes(N'declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan"; //p:RelOp[@PhysicalOp="Index Scan" or @PhysicalOp="Index Seek" or @PhysicalOp="Clustered Index Scan" or @PhysicalOp="Clustered Index Seek"]//p:Const[@ConstValue]') AS c(const)
WHERE x.query_plan_xml IS NOT NULL
AND   qsqt.query_sql_text LIKE N'%OPTION%(%RECOMPILE%)%'
OPTION(RECOMPILE);

/*
Check for encrypted modules and restricted text
*/
SELECT
    @current_table = N'checking for encrypted modules and restricted text';

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qsp.plan_id,
    warning_type =
        CASE
            WHEN qsqt.is_part_of_encrypted_module = 1
            THEN N'encrypted module'
            WHEN qsqt.has_restricted_text = 1
            THEN N'restricted text'
        END,
    warning_message =
        CASE
            WHEN qsqt.is_part_of_encrypted_module = 1
            THEN N'Query is part of an encrypted module. Full query text may not be available.'
            WHEN qsqt.has_restricted_text = 1
            THEN N'Query has restricted text. Full query text may not be available due to permissions or other restrictions.'
        END
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
WHERE qsqt.is_part_of_encrypted_module = 1
OR    qsqt.has_restricted_text = 1
OPTION(RECOMPILE);

/*
Check for dispatcher plans (parameter sensitive plan optimization)
If a query is a dispatcher, warn and provide the variant query IDs
*/
IF @sql_2022_views = 1
BEGIN
    SELECT
        @current_table = N'checking for dispatcher plans';

    INSERT
        #reproduction_warnings
    WITH
        (TABLOCK)
    (
        plan_id,
        warning_type,
        warning_message
    )
    SELECT DISTINCT
        qsp.plan_id,
        warning_type = N'dispatcher plan',
        warning_message =
            N'This is a dispatcher plan for parameter sensitive plan optimization. ' +
            N'No executable query can be generated for dispatcher plans. ' +
            N'Use the following variant query IDs to generate repro scripts: ' +
            STUFF
            (
                (
                    SELECT
                        N', ' +
                        RTRIM(qsqv.query_variant_query_id)
                    FROM #query_store_query_variant AS qsqv
                    WHERE qsqv.parent_query_id = qsq.query_id
                    ORDER BY
                        qsqv.query_variant_query_id
                    FOR XML
                        PATH(N''),
                        TYPE
                ).value(N'./text()[1]', N'nvarchar(max)'),
                1,
                2,
                N''
            )
    FROM #query_store_plan AS qsp
    JOIN #query_store_query AS qsq
      ON qsp.query_id = qsq.query_id
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM #query_store_query_variant AS qsqv
              WHERE qsqv.parent_query_id = qsq.query_id
          )
    OPTION(RECOMPILE);
END;

/*
Check for temp tables and table variables in query text
*/
SELECT
    @current_table = N'checking for temp tables and table variables';

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qsp.plan_id,
    warning_type = N'#temp table',
    warning_message = N'Query contains temp table(s) - reproduction may require temp table creation'
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
WHERE qsqt.query_sql_text_normalized LIKE N'% FROM #%'
OR    qsqt.query_sql_text_normalized LIKE N'% JOIN #%'
OR    qsqt.query_sql_text_normalized LIKE N'% INTO #%'
OR    qsqt.query_sql_text_normalized LIKE N'%INSERT #%'
OR    qsqt.query_sql_text_normalized LIKE N'%UPDATE #%'
OR    qsqt.query_sql_text_normalized LIKE N'%DELETE #%'
OR    qsqt.query_sql_text_normalized LIKE N'%MERGE #%'
OR    qsqt.query_sql_text_normalized LIKE N'%MERGE INTO #%'
OPTION(RECOMPILE);

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT DISTINCT
    qsp.plan_id,
    warning_type = N'@table variable',
    warning_message = N'Query may contain table variable(s) - reproduction may require table variable creation'
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
WHERE qsqt.query_sql_text_normalized LIKE N'% FROM @%'
OR    qsqt.query_sql_text_normalized LIKE N'% JOIN @%'
OR    qsqt.query_sql_text_normalized LIKE N'% INTO @%'
OR    qsqt.query_sql_text_normalized LIKE N'%INSERT @%'
OR    qsqt.query_sql_text_normalized LIKE N'%UPDATE @%'
OR    qsqt.query_sql_text_normalized LIKE N'%DELETE @%'
OR    qsqt.query_sql_text_normalized LIKE N'%MERGE @%'
OR    qsqt.query_sql_text_normalized LIKE N'%MERGE INTO @%'
OPTION(RECOMPILE);

/*
Check for parameter count mismatch
Only warn if query text starts with parameters but none found in plan
*/
SELECT
    @current_table = N'checking for parameter count mismatch';

INSERT
    #reproduction_warnings
WITH
    (TABLOCK)
(
    plan_id,
    warning_type,
    warning_message
)
SELECT
    qsp.plan_id,
    warning_type = N'parameter count mismatch',
    warning_message =
        N'Query text has parameter declarations but no parameters found in plan XML. ' +
        N'This typically indicates local variables that do not have sniffed values cached in the plan. ' +
        N'Review the stored procedure or batch text to determine how these variables are assigned values.'
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
CROSS APPLY
(
    SELECT
        plan_param_count =
            COUNT_BIG(*)
    FROM #query_parameters AS qp
    WHERE qp.plan_id = qsp.plan_id
) AS ppc
WHERE qsqt.query_sql_text LIKE N'(@%'
AND   ppc.plan_param_count = 0
OPTION(RECOMPILE);

/*
Build reproduction queries with sp_executesql
*/
SELECT
    @current_table = N'building reproduction queries';

INSERT
    #repro_queries
WITH
    (TABLOCK)
(
    plan_id,
    query_id,
    executable_query
)
SELECT
    qsp.plan_id,
    qsp.query_id,
    executable_query =
        (
            SELECT
                N'/*' + NCHAR(10) +
                N'Query ID: ' +
                RTRIM(qsp.query_id) + NCHAR(10) +
                N'Plan ID: ' + RTRIM(qsp.plan_id) + NCHAR(10) +
                ISNULL(
                    (
                        SELECT
                            N'Warnings:' + NCHAR(10) +
                            STUFF
                            (
                                (
                                    SELECT
                                        NCHAR(10) +
                                        N' - ' +
                                        rw.warning_message
                                    FROM #reproduction_warnings AS rw
                                    WHERE rw.plan_id = qsp.plan_id
                                    ORDER BY
                                        rw.warning_type
                                    FOR XML
                                        PATH(N''),
                                        TYPE
                                ).value(N'./text()[1]', N'nvarchar(max)'),
                                1,
                                0,
                                N''
                            ) + NCHAR(10)
                    ),
                    N''
                ) +
                N'*/' +
                NCHAR(10) +
                CASE
                    WHEN @azure = 0
                    THEN
                        N'USE ' +
                        QUOTENAME(@database_name) +
                        N';'
                    ELSE N''
                END +
                NCHAR(10) +
                ISNULL
                (
                    N'SET' +
                    REPLACE(qsrs.context_settings, N', ', N' ON;' + NCHAR(10) + N'SET ') +
                    N' ON;' +
                    NCHAR(10),
                    N''
                ) +
                ISNULL
                (
                    N'SET LANGUAGE ' +
                    lang.name +
                    N';' +
                    NCHAR(10),
                    N''
                ) +
                ISNULL
                (
                    N'SET DATEFORMAT ' +
                    CASE qcs.date_format
                         WHEN 0 THEN N'mdy'
                         WHEN 1 THEN N'dmy'
                         WHEN 2 THEN N'ymd'
                         WHEN 3 THEN N'ydm'
                         WHEN 4 THEN N'myd'
                         WHEN 5 THEN N'dym'
                         ELSE N'mdy'
                    END +
                    N';' +
                    NCHAR(10),
                    N''
                ) +
                ISNULL
                (
                    N'SET DATEFIRST ' +
                    RTRIM(qcs.date_first) +
                    N';' +
                    NCHAR(10),
                    N''
                ) +
                NCHAR(10) +
                CASE
                    WHEN qsqt.query_sql_text LIKE N'(@%'
                    THEN
                        N'EXECUTE sys.sp_executesql' +
                        NCHAR(10) +
                        N'    N''' +
                        REPLACE
                        (
                            clean_query.query_text_cleaned,
                            N'''',
                            N''''''
                        ) +
                        N''',' +
                        NCHAR(10) +
                        CASE
                            WHEN EXISTS
                                 (
                                     SELECT
                                         1/0
                                     FROM #query_parameters AS qp
                                     WHERE qp.plan_id = qsp.plan_id
                                 )
                            THEN
                                N'N''' +
                                STUFF
                                (
                                    (
                                        SELECT
                                            N', ' +
                                            qp.parameter_name +
                                            N' ' +
                                            qp.parameter_data_type
                                        FROM #query_parameters AS qp
                                        WHERE qp.plan_id = qsp.plan_id
                                        ORDER BY
                                            qp.parameter_name
                                        FOR XML
                                            PATH(N''),
                                            TYPE
                                    ).value(N'./text()[1]', N'nvarchar(max)'),
                                    1,
                                    2,
                                    N''
                                ) +
                                N''',' +
                                NCHAR(10) +
                                STUFF
                                (
                                    (
                                        SELECT
                                            N', ' +
                                            ISNULL
                                            (
                                                qp.parameter_compiled_value,
                                                N'NULL'
                                            )
                                        FROM #query_parameters AS qp
                                        WHERE qp.plan_id = qsp.plan_id
                                        ORDER BY
                                            qp.parameter_name
                                        FOR XML
                                            PATH(N''),
                                            TYPE
                                    ).value(N'./text()[1]', N'nvarchar(max)'),
                                    1,
                                    2,
                                    N''
                                ) +
                                N';' +
                                NCHAR(10)
                            ELSE
                                N'N'''';' +
                                NCHAR(10)
                        END
                    ELSE
                        qsqt.query_sql_text +
                        NCHAR(10)
                END
        )
FROM #query_store_plan AS qsp
JOIN #query_store_query AS qsq
  ON qsp.query_id = qsq.query_id
JOIN #query_store_query_text AS qsqt
  ON qsq.query_text_id = qsqt.query_text_id
CROSS APPLY
(
    SELECT
        query_text_cleaned =
            CASE
                WHEN qsqt.query_sql_text LIKE N'(@%'
                THEN
                    LTRIM
                    (
                        SUBSTRING
                        (
                            qsqt.query_sql_text,
                            PATINDEX(N'%)[^,]%', qsqt.query_sql_text) +
                                CASE
                                    WHEN SUBSTRING
                                         (
                                             qsqt.query_sql_text,
                                             PATINDEX(N'%)[^,]%', qsqt.query_sql_text),
                                             2
                                         ) = N'))'
                                    THEN 2
                                    ELSE 1
                                END,
                            LEN(qsqt.query_sql_text)
                        )
                    )
                ELSE qsqt.query_sql_text
            END
) AS clean_query
JOIN #query_store_runtime_stats AS qsrs
  ON qsp.plan_id = qsrs.plan_id
JOIN #query_context_settings AS qcs
  ON qsq.context_settings_id = qcs.context_settings_id
LEFT JOIN sys.syslanguages AS lang
  ON qcs.language_id = lang.langid
WHERE NOT EXISTS
      (
          SELECT
              1/0
          FROM #query_store_query_variant AS qsqv
          WHERE qsqv.parent_query_id = qsq.query_id
      )
OPTION(RECOMPILE);


SELECT
    table_name =
        N'results',
    database_name =
        DB_NAME(qsrs.database_id),
    executable_query =
        (
            SELECT
                [processing-instruction(_)] =
                    rq.executable_query
            FOR XML
                PATH(N''),
                TYPE
        ),
    parameter_values =
        ISNULL
        (
            STUFF
            (
                (
                    SELECT
                        N', ' +
                        qp.parameter_compiled_value
                    FROM #query_parameters AS qp
                    WHERE qp.plan_id = rq.plan_id
                    ORDER BY
                        qp.parameter_compiled_value
                    FOR XML
                        PATH(N''),
                        TYPE
                ).value(N'./text()[1]', N'nvarchar(max)'),
                1,
                2,
                N''
            ),
        N'N/A'
    ),
    embedded_constants =
        ISNULL
        (
            STUFF
            (
                (
                    SELECT
                        N', ' +
                        ec.constant_value
                    FROM #embedded_constants AS ec
                    WHERE ec.plan_id = rq.plan_id
                    ORDER BY
                        ec.constant_value
                    FOR XML
                        PATH(N''),
                        TYPE
                ).value(N'./text()[1]', N'nvarchar(max)'),
                1,
                2,
                N''
            ),
        N'N/A'
    ),
    rq.query_id,
    rq.plan_id,
    qsp.all_plan_ids,
    qsp.compatibility_level,
    qsq.object_name,
    qsqt.query_sql_text_clickable,
    query_plan =
         CASE
             WHEN TRY_CAST(qsp.query_plan AS xml) IS NOT NULL
             THEN TRY_CAST(qsp.query_plan AS xml)
             WHEN TRY_CAST(qsp.query_plan AS xml) IS NULL
             THEN
                 (
                     SELECT
                         [processing-instruction(query_plan)] =
                             N'-- ' + NCHAR(13) + NCHAR(10) +
                             N'-- This is a huge query plan.' + NCHAR(13) + NCHAR(10) +
                             N'-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR(13) + NCHAR(10) +
                             NCHAR(13) + NCHAR(10) +
                             REPLACE(qsp.query_plan, N'<RelOp', NCHAR(13) + NCHAR(10) + N'<RelOp') +
                             NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
                     FOR XML
                         PATH(N''),
                         TYPE
                 )
         END,
    qsrs.execution_type_desc,
    qsrs.first_execution_time,
    qsrs.last_execution_time,
    qsws.waits,
    qsrs.count_executions,
    qsrs.executions_per_second,
    qsrs.avg_duration_ms,
    qsrs.min_duration_ms,
    qsrs.max_duration_ms,
    qsrs.total_duration_ms,
    qsrs.last_duration_ms,
    qsrs.avg_cpu_time_ms,
    qsrs.min_cpu_time_ms,
    qsrs.max_cpu_time_ms,
    qsrs.total_cpu_time_ms,
    qsrs.last_cpu_time_ms,
    qsrs.last_dop,
    qsrs.min_dop,
    qsrs.max_dop,
    qsrs.avg_query_max_used_memory_mb,
    qsrs.last_query_max_used_memory_mb,
    qsrs.min_query_max_used_memory_mb,
    qsrs.max_query_max_used_memory_mb,
    qsrs.total_query_max_used_memory_mb,
    qsrs.avg_rowcount,
    qsrs.min_rowcount,
    qsrs.max_rowcount,
    qsrs.total_rowcount,
    qsrs.last_rowcount,
    qsrs.avg_num_physical_io_reads_mb,
    qsrs.min_num_physical_io_reads_mb,
    qsrs.max_num_physical_io_reads_mb,
    qsrs.total_num_physical_io_reads_mb,
    qsrs.last_num_physical_io_reads_mb
FROM
(
   SELECT DISTINCT
       rq.plan_id,
       rq.query_id,
       rq.executable_query
   FROM #repro_queries AS rq
) AS rq
CROSS APPLY
(
  SELECT TOP (1)
      qsrs.*
  FROM #query_store_runtime_stats AS qsrs
  WHERE qsrs.plan_id = rq.plan_id
  ORDER BY
      qsrs.plan_id DESC
) AS qsrs
CROSS APPLY
(
  SELECT TOP (1)
      qsp.*
  FROM #query_store_plan AS qsp
  WHERE qsp.plan_id = rq.plan_id
  AND   qsp.query_id = rq.query_id
  ORDER BY
      qsp.plan_id DESC,
      qsp.query_id DESC
) AS qsp
CROSS APPLY
(
  SELECT TOP (1)
      qsq.*
  FROM #query_store_query AS qsq
  WHERE qsq.query_id = rq.query_id
  ORDER BY
      qsq.query_id DESC
) AS qsq
CROSS APPLY
(
  SELECT TOP (1)
      qsqt.*
  FROM  #query_store_query_text AS qsqt
  WHERE qsqt.query_text_id = qsq.query_text_id
  ORDER BY
      qsqt.query_text_id DESC
) AS qsqt
OUTER APPLY
(
    SELECT
        qsws.plan_id,
        qsws.wait_category_desc,
        TotalWaitTime = SUM(qsws.total_query_wait_time_ms),
        AverageWaitTime = AVG(qsws.avg_query_wait_time_ms),
        MinimumWaitTime = MIN(qsws.min_query_wait_time_ms),
        MaximumWaitTime = MAX(qsws.max_query_wait_time_ms)
    FROM #query_store_wait_stats AS qsws
    WHERE qsws.plan_id = rq.plan_id
    GROUP BY
        qsws.plan_id,
        qsws.wait_category_desc
    ORDER BY
        TotalWaitTime DESC
    FOR
        XML
        PATH('waits'),
        TYPE
) AS qsws(waits)
ORDER BY
    qsrs.last_execution_time DESC
OPTION(RECOMPILE);

END TRY

BEGIN CATCH
    IF @current_table IS NOT NULL
    BEGIN
        RAISERROR('error while %s', 10, 1, @current_table) WITH NOWAIT;
    END;

    IF @sql IS NOT NULL
    BEGIN
        RAISERROR('current dynamic sql:', 10, 1) WITH NOWAIT;
        RAISERROR('%s', 10, 1, @sql) WITH NOWAIT;
    END;

    IF @@TRANCOUNT > 0
    BEGIN
        ROLLBACK;
    END;

    THROW;
END CATCH;

IF @debug = 1
BEGIN
    /*
    Debug result sets for temp tables
    */
    SELECT
        table_name =
            N'#query_store_runtime_stats',
        qsrs.*
    FROM #query_store_runtime_stats AS qsrs
    ORDER BY
        qsrs.plan_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_plan',
        qsp.*
    FROM #query_store_plan AS qsp
    ORDER BY
        qsp.plan_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_query_text',
        qsqt.*
    FROM #query_store_query_text AS qsqt
    ORDER BY
        qsqt.query_text_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_query',
        qsq.*
    FROM #query_store_query AS qsq
    ORDER BY
        qsq.query_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_context_settings',
        qcs.*
    FROM #query_context_settings AS qcs
    ORDER BY
        qcs.context_settings_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_wait_stats',
        qsws.*
    FROM #query_store_wait_stats AS qsws
    ORDER BY
        qsws.plan_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_plan_feedback',
        qspf.*
    FROM #query_store_plan_feedback AS qspf
    ORDER BY
        qspf.plan_feedback_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_query_variant',
        qsqv.*
    FROM #query_store_query_variant AS qsqv
    ORDER BY
        qsqv.query_variant_query_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_store_query_hints',
        qsqh.*
    FROM #query_store_query_hints AS qsqh
    ORDER BY
        qsqh.query_hint_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#query_parameters',
        qp.*
    FROM #query_parameters AS qp
    ORDER BY
        qp.plan_id,
        qp.parameter_name
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#reproduction_warnings',
        rw.*
    FROM #reproduction_warnings AS rw
    ORDER BY
        rw.plan_id,
        rw.warning_type
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#repro_queries',
        rq.*
    FROM #repro_queries AS rq
    ORDER BY
        rq.plan_id
    OPTION(RECOMPILE);

    SELECT
        table_name =
            N'#embedded_constants',
        ec.*
    FROM #embedded_constants AS ec
    ORDER BY
        ec.plan_id,
        ec.constant_value
    OPTION(RECOMPILE);
END;

END; /*Final end*/
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
SET ARITHABORT ON;
SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET STATISTICS TIME, IO OFF;
GO

/*

 ██████╗ ██╗   ██╗██╗ ██████╗██╗  ██╗██╗███████╗
██╔═══██╗██║   ██║██║██╔════╝██║ ██╔╝██║██╔════╝
██║   ██║██║   ██║██║██║     █████╔╝ ██║█████╗
██║▄▄ ██║██║   ██║██║██║     ██╔═██╗ ██║██╔══╝
╚██████╔╝╚██████╔╝██║╚██████╗██║  ██╗██║███████╗
 ╚══▀▀═╝  ╚═════╝ ╚═╝ ╚═════╝╚═╝  ╚═╝╚═╝╚══════╝

███████╗████████╗ ██████╗ ██████╗ ███████╗██╗
██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝██║
███████╗   ██║   ██║   ██║██████╔╝█████╗  ██║
╚════██║   ██║   ██║   ██║██╔══██╗██╔══╝  ╚═╝
███████║   ██║   ╚██████╔╝██║  ██║███████╗██╗
╚══════╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝

Copyright 2026 Darling Data, LLC
https://www.erikdarling.com/

For usage and licensing details, run:
EXECUTE sp_QuickieStore
    @help = 1;

For working through errors:
EXECUTE sp_QuickieStore
    @debug = 1;

For performance issues:
EXECUTE sp_QuickieStore
    @troubleshoot_performance = 1;

For support, head over to GitHub:
https://code.erikdarling.com

*/

IF OBJECT_ID(N'dbo.sp_QuickieStore', N'P') IS NULL
   BEGIN
       EXECUTE (N'CREATE PROCEDURE dbo.sp_QuickieStore AS RETURN 138;');
   END;
GO

ALTER PROCEDURE
    dbo.sp_QuickieStore
(
    @database_name sysname = NULL, /*the name of the database you want to look at query store in*/
    @sort_order varchar(20) = 'cpu', /*the runtime metric you want to prioritize results by*/
    @top bigint = 10, /*the number of queries you want to pull back*/
    @start_date datetimeoffset(7) = NULL, /*the begin date of your search, will be converted to UTC internally*/
    @end_date datetimeoffset(7) = NULL, /*the end date of your search, will be converted to UTC internally*/
    @timezone sysname = NULL, /*user specified time zone to override dates displayed in results*/
    @execution_count bigint = NULL, /*the minimum number of executions a query must have*/
    @duration_ms bigint = NULL, /*the minimum duration a query must have to show up in results*/
    @execution_type_desc nvarchar(60) = NULL, /*the type of execution you want to filter by (regular, aborted, exception)*/
    @procedure_schema sysname = NULL, /*the schema of the procedure you're searching for*/
    @procedure_name sysname = NULL, /*the name of the programmable object you're searching for*/
    @include_plan_ids nvarchar(4000) = NULL, /*a list of plan ids to search for*/
    @include_query_ids nvarchar(4000) = NULL, /*a list of query ids to search for*/
    @include_query_hashes nvarchar(4000) = NULL, /*a list of query hashes to search for*/
    @include_plan_hashes nvarchar(4000) = NULL, /*a list of query plan hashes to search for*/
    @include_sql_handles nvarchar(4000) = NULL, /*a list of sql handles to search for*/
    @ignore_plan_ids nvarchar(4000) = NULL, /*a list of plan ids to ignore*/
    @ignore_query_ids nvarchar(4000) = NULL, /*a list of query ids to ignore*/
    @ignore_query_hashes nvarchar(4000) = NULL, /*a list of query hashes to ignore*/
    @ignore_plan_hashes nvarchar(4000) = NULL, /*a list of query plan hashes to ignore*/
    @ignore_sql_handles nvarchar(4000) = NULL, /*a list of sql handles to ignore*/
    @query_text_search nvarchar(4000) = NULL, /*query text to search for*/
    @query_text_search_not nvarchar(4000) = NULL, /*query text to exclude*/
    @escape_brackets bit = 0, /*Set this bit to 1 to search for query text containing square brackets (common in .NET Entity Framework and other ORM queries)*/
    @escape_character nchar(1) = N'\', /*Sets the ESCAPE character for special character searches, defaults to the SQL standard backslash (\) character*/
    @only_queries_with_hints bit = 0, /*Set this bit to 1 to retrieve only queries with query hints*/
    @only_queries_with_feedback bit = 0, /*Set this bit to 1 to retrieve only queries with query feedback*/
    @only_queries_with_variants bit = 0, /*Set this bit to 1 to retrieve only queries with query variants*/
    @only_queries_with_forced_plans bit = 0, /*Set this bit to 1 to retrieve only queries with forced plans*/
    @only_queries_with_forced_plan_failures bit = 0, /*Set this bit to 1 to retrieve only queries with forced plan failures*/
    @wait_filter varchar(20) = NULL, /*wait category to search for; category details are below*/
    @query_type varchar(11) = NULL, /*filter for only ad hoc queries or only from queries from modules*/
    @expert_mode bit = 0, /*returns additional columns and results*/
    @hide_help_table bit = 0, /*hides the "bottom table" that shows help and support information*/
    @format_output bit = 1, /*returns numbers formatted with commas and most decimals rounded away*/
    @get_all_databases bit = 0, /*looks for query store enabled user databases and returns combined results from all of them*/
    @include_databases nvarchar(MAX) = NULL, /*comma-separated list of databases to include (only when @get_all_databases = 1)*/
    @exclude_databases nvarchar(MAX) = NULL, /*comma-separated list of databases to exclude (only when @get_all_databases = 1)*/
    @workdays bit = 0, /*Use this to filter out weekends and after-hours queries*/
    @work_start time(0) = '9am', /*Use this to set a specific start of your work days*/
    @work_end time(0) = '5pm', /*Use this to set a specific end of your work days*/
    @regression_baseline_start_date datetimeoffset(7) = NULL, /*the begin date of the baseline that you are checking for regressions against (if any), will be converted to UTC internally*/
    @regression_baseline_end_date datetimeoffset(7) = NULL, /*the end date of the baseline that you are checking for regressions against (if any), will be converted to UTC internally*/
    @regression_comparator varchar(20) = NULL, /*what difference to use ('relative' or 'absolute') when comparing @sort_order's metric for the normal time period with the regression time period.*/
    @regression_direction varchar(20) = NULL, /*when comparing against the regression baseline, what do you want the results sorted by ('magnitude', 'improved', or 'regressed')?*/
    @include_query_hash_totals bit = 0, /*will add an additional column to final output with total resource usage by query hash, may be skewed by query_hash and query_plan_hash bugs with forced plans/plan guides*/
    @include_maintenance bit = 0, /*Set this bit to 1 to add maintenance operations such as index creation to the result set*/
    @help bit = 0, /*return available parameter details, etc.*/
    @debug bit = 0, /*prints dynamic sql, statement length, parameter and variable values, and raw temp table contents*/
    @troubleshoot_performance bit = 0, /*set statistics xml on for queries against views*/
    @version varchar(30) = NULL OUTPUT, /*OUTPUT; for support*/
    @version_date datetime = NULL OUTPUT /*OUTPUT; for support*/
)
WITH RECOMPILE
AS
BEGIN
SET STATISTICS XML OFF;
SET NOCOUNT ON;
SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

BEGIN TRY
/*
These are for your outputs.
*/
SELECT
    @version = '6.3',
    @version_date = '20260301';

/*
Helpful section! For help.
*/
IF @help = 1
BEGIN
    /*
    Introduction
    */
    SELECT
        introduction =
           'hi, i''m sp_QuickieStore!' UNION ALL
    SELECT 'you got me from https://code.erikdarling.com' UNION ALL
    SELECT 'i can be used to quickly grab misbehaving queries from query store' UNION ALL
    SELECT 'the plan analysis is up to you; there will not be any XML shredding here' UNION ALL
    SELECT 'so what can you do, and how do you do it? read below!' UNION ALL
    SELECT  'from your loving sql server consultant, erik darling: https://erikdarling.com';

    /*
    Parameters
    */
    SELECT
        parameter_name =
            ap.name,
        data_type = t.name,
        description =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'the name of the database you want to look at query store in'
                WHEN N'@sort_order' THEN 'the runtime metric you want to prioritize results by'
                WHEN N'@top' THEN 'the number of queries you want to pull back'
                WHEN N'@start_date' THEN 'the begin date of your search, will be converted to UTC internally'
                WHEN N'@end_date' THEN 'the end date of your search, will be converted to UTC internally'
                WHEN N'@timezone' THEN 'user specified time zone to override dates displayed in results'
                WHEN N'@execution_count' THEN 'the minimum number of executions a query must have'
                WHEN N'@duration_ms' THEN 'the minimum duration a query must have to show up in results'
                WHEN N'@execution_type_desc' THEN 'the type of execution you want to filter by (regular, aborted, exception)'
                WHEN N'@procedure_schema' THEN 'the schema of the procedure you''re searching for'
                WHEN N'@procedure_name' THEN 'the name of the programmable object you''re searching for'
                WHEN N'@include_plan_ids' THEN 'a list of plan ids to search for'
                WHEN N'@include_query_ids' THEN 'a list of query ids to search for'
                WHEN N'@include_query_hashes' THEN 'a list of query hashes to search for'
                WHEN N'@include_plan_hashes' THEN 'a list of query plan hashes to search for'
                WHEN N'@include_sql_handles' THEN 'a list of sql handles to search for'
                WHEN N'@ignore_plan_ids' THEN 'a list of plan ids to ignore'
                WHEN N'@ignore_query_ids' THEN 'a list of query ids to ignore'
                WHEN N'@ignore_query_hashes' THEN 'a list of query hashes to ignore'
                WHEN N'@ignore_plan_hashes' THEN 'a list of query plan hashes to ignore'
                WHEN N'@ignore_sql_handles' THEN 'a list of sql handles to ignore'
                WHEN N'@query_text_search' THEN 'query text to search for'
                WHEN N'@query_text_search_not' THEN 'query text to exclude'
                WHEN N'@escape_brackets' THEN 'Set this bit to 1 to search for query text containing square brackets (common in .NET Entity Framework and other ORM queries)'
                WHEN N'@escape_character' THEN 'Sets the ESCAPE character for special character searches, defaults to the SQL standard backslash (\) character'
                WHEN N'@only_queries_with_hints' THEN 'only return queries with query hints'
                WHEN N'@only_queries_with_feedback' THEN 'only return queries with query feedback'
                WHEN N'@only_queries_with_variants' THEN 'only return queries with query variants'
                WHEN N'@only_queries_with_forced_plans' THEN 'only return queries with forced plans'
                WHEN N'@only_queries_with_forced_plan_failures' THEN 'only return queries with forced plan failures'
                WHEN N'@wait_filter' THEN 'wait category to search for; category details are below'
                WHEN N'@query_type' THEN 'filter for only ad hoc queries or only from queries from modules'
                WHEN N'@expert_mode' THEN 'returns additional columns and results'
                WHEN N'@hide_help_table' THEN 'hides the "bottom table" that shows help and support information'
                WHEN N'@format_output' THEN 'returns numbers formatted with commas and most decimals rounded away'
                WHEN N'@get_all_databases' THEN 'looks for query store enabled user databases and returns combined results from all of them'
                WHEN N'@include_databases' THEN 'comma-separated list of databases to include (only when @get_all_databases = 1)'
                WHEN N'@exclude_databases' THEN 'comma-separated list of databases to exclude (only when @get_all_databases = 1)'
                WHEN N'@workdays' THEN 'use this to filter out weekends and after-hours queries'
                WHEN N'@work_start' THEN 'use this to set a specific start of your work days'
                WHEN N'@work_end' THEN 'use this to set a specific end of your work days'
                WHEN N'@regression_baseline_start_date' THEN 'the begin date of the baseline that you are checking for regressions against (if any), will be converted to UTC internally'
                WHEN N'@regression_baseline_end_date' THEN 'the end date of the baseline that you are checking for regressions against (if any), will be converted to UTC internally'
                WHEN N'@regression_comparator' THEN 'what difference to use (''relative'' or ''absolute'') when comparing @sort_order''s metric for the normal time period with any regression time period.'
                WHEN N'@regression_direction' THEN 'when comparing against any regression baseline, what do you want the results sorted by (''magnitude'', ''improved'', or ''regressed'')?'
                WHEN N'@include_query_hash_totals' THEN N'will add an additional column to final output with total resource usage by query hash, may be skewed by query_hash and query_plan_hash bugs with forced plans/plan guides'
                WHEN N'@include_maintenance' THEN N'Set this bit to 1 to add maintenance operations such as index creation to the result set'
                WHEN N'@help' THEN 'how you got here'
                WHEN N'@debug' THEN 'prints dynamic sql, statement length, parameter and variable values, and raw temp table contents'
                WHEN N'@troubleshoot_performance' THEN 'set statistics xml on for queries against views'
                WHEN N'@version' THEN 'OUTPUT; for support'
                WHEN N'@version_date' THEN 'OUTPUT; for support'
            END,
        valid_inputs =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'a database name with query store enabled'
                WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes, cpu waits, lock waits, locks waits, latch waits, latches waits, buffer latch waits, buffer latches waits, buffer io waits, log waits, log io waits, network waits, network io waits, parallel waits, parallelism waits, memory waits, total waits, rows, total cpu, total logical reads, total physical reads, total writes, total duration, total memory, total tempdb, total rows (avg/average prefix also accepted, e.g. avg cpu, average duration)'
                WHEN N'@top' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807'
                WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@timezone' THEN 'SELECT tzi.* FROM sys.time_zone_info AS tzi;'
                WHEN N'@execution_count' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807'
                WHEN N'@duration_ms' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807'
                WHEN N'@execution_type_desc' THEN 'regular, aborted, exception'
                WHEN N'@procedure_schema' THEN 'a valid schema in your database'
                WHEN N'@procedure_name' THEN 'a valid programmable object in your database, can use wildcards'
                WHEN N'@include_plan_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@include_query_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@include_query_hashes' THEN 'a string; comma separated for multiple hashes'
                WHEN N'@include_plan_hashes' THEN 'a string; comma separated for multiple hashes'
                WHEN N'@include_sql_handles' THEN 'a string; comma separated for multiple handles'
                WHEN N'@ignore_plan_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@ignore_query_ids' THEN 'a string; comma separated for multiple ids'
                WHEN N'@ignore_query_hashes' THEN 'a string; comma separated for multiple hashes'
                WHEN N'@ignore_plan_hashes' THEN 'a string; comma separated for multiple hashes'
                WHEN N'@ignore_sql_handles' THEN 'a string; comma separated for multiple handles'
                WHEN N'@query_text_search' THEN 'a string; leading and trailing wildcards will be added if missing'
                WHEN N'@query_text_search_not' THEN 'a string; leading and trailing wildcards will be added if missing'
                WHEN N'@escape_brackets' THEN '0 or 1'
                WHEN N'@escape_character' THEN 'some escape character, SQL standard is backslash (\)'
                WHEN N'@only_queries_with_hints' THEN '0 or 1'
                WHEN N'@only_queries_with_feedback' THEN '0 or 1'
                WHEN N'@only_queries_with_variants' THEN '0 or 1'
                WHEN N'@only_queries_with_forced_plans' THEN '0 or 1'
                WHEN N'@only_queries_with_forced_plan_failures' THEN '0 or 1'
                WHEN N'@wait_filter' THEN 'cpu, lock, latch, buffer latch, buffer io, log io, network io, parallelism, memory'
                WHEN N'@query_type' THEN 'ad hoc, adhoc, proc, procedure, whatever.'
                WHEN N'@expert_mode' THEN '0 or 1'
                WHEN N'@hide_help_table' THEN '0 or 1'
                WHEN N'@format_output' THEN '0 or 1'
                WHEN N'@get_all_databases' THEN '0 or 1'
                WHEN N'@include_databases' THEN 'a string; comma separated database names'
                WHEN N'@exclude_databases' THEN 'a string; comma separated database names'
                WHEN N'@workdays' THEN '0 or 1'
                WHEN N'@work_start' THEN 'a time like 8am, 9am or something'
                WHEN N'@work_end' THEN 'a time like 5pm, 6pm or something'
                WHEN N'@regression_baseline_start_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@regression_baseline_end_date' THEN 'January 1, 1753, through December 31, 9999'
                WHEN N'@regression_comparator' THEN 'relative, absolute'
                WHEN N'@regression_direction' THEN 'regressed, worse, improved, better, magnitude, absolute, whatever'
                WHEN N'@include_query_hash_totals' THEN N'0 or 1'
                WHEN N'@include_maintenance' THEN N'0 or 1'
                WHEN N'@help' THEN '0 or 1'
                WHEN N'@debug' THEN '0 or 1'
                WHEN N'@troubleshoot_performance' THEN '0 or 1'
                WHEN N'@version' THEN 'none; OUTPUT'
                WHEN N'@version_date' THEN 'none; OUTPUT'
            END,
        defaults =
            CASE
                ap.name
                WHEN N'@database_name' THEN 'NULL; current database name if NULL'
                WHEN N'@sort_order' THEN 'cpu'
                WHEN N'@top' THEN '10'
                WHEN N'@start_date' THEN 'the last seven days'
                WHEN N'@end_date' THEN 'NULL'
                WHEN N'@timezone' THEN 'NULL'
                WHEN N'@execution_count' THEN 'NULL'
                WHEN N'@duration_ms' THEN 'NULL'
                WHEN N'@execution_type_desc' THEN 'NULL'
                WHEN N'@procedure_schema' THEN 'NULL; dbo if NULL and procedure name is not NULL'
                WHEN N'@procedure_name' THEN 'NULL'
                WHEN N'@include_plan_ids' THEN 'NULL'
                WHEN N'@include_query_ids' THEN 'NULL'
                WHEN N'@include_query_hashes' THEN 'NULL'
                WHEN N'@include_plan_hashes' THEN 'NULL'
                WHEN N'@include_sql_handles' THEN 'NULL'
                WHEN N'@ignore_plan_ids' THEN 'NULL'
                WHEN N'@ignore_query_ids' THEN 'NULL'
                WHEN N'@ignore_query_hashes' THEN 'NULL'
                WHEN N'@ignore_plan_hashes' THEN 'NULL'
                WHEN N'@ignore_sql_handles' THEN 'NULL'
                WHEN N'@query_text_search' THEN 'NULL'
                WHEN N'@query_text_search_not' THEN 'NULL'
                WHEN N'@escape_brackets' THEN '0'
                WHEN N'@escape_character' THEN '\'
                WHEN N'@only_queries_with_hints' THEN '0'
                WHEN N'@only_queries_with_feedback' THEN '0'
                WHEN N'@only_queries_with_variants' THEN '0'
                WHEN N'@only_queries_with_forced_plans' THEN '0'
                WHEN N'@only_queries_with_forced_plan_failures' THEN '0'
                WHEN N'@wait_filter' THEN 'NULL'
                WHEN N'@query_type' THEN 'NULL'
                WHEN N'@expert_mode' THEN '0'
                WHEN N'@hide_help_table' THEN '0'
                WHEN N'@format_output' THEN '1'
                WHEN N'@get_all_databases' THEN '0'
                WHEN N'@include_databases' THEN 'NULL'
                WHEN N'@exclude_databases' THEN 'NULL'
                WHEN N'@workdays' THEN '0'
                WHEN N'@work_start' THEN '9am'
                WHEN N'@work_end' THEN '5pm'
                WHEN N'@regression_baseline_start_date' THEN 'NULL'
                WHEN N'@regression_baseline_end_date' THEN 'NULL; One week after @regression_baseline_start_date if that is specified'
                WHEN N'@regression_comparator' THEN 'NULL; absolute if @regression_baseline_start_date is specified'
                WHEN N'@regression_direction' THEN 'NULL; regressed if @regression_baseline_start_date is specified'
                WHEN N'@include_query_hash_totals' THEN N'0'
                WHEN N'@include_maintenance' THEN N'0'
                WHEN N'@help' THEN '0'
                WHEN N'@debug' THEN '0'
                WHEN N'@troubleshoot_performance' THEN '0'
                WHEN N'@version' THEN 'none; OUTPUT'
                WHEN N'@version_date' THEN 'none; OUTPUT'
            END
    FROM sys.all_parameters AS ap
    JOIN sys.all_objects AS o
      ON ap.object_id = o.object_id
    JOIN sys.types AS t
      ON  ap.system_type_id = t.system_type_id
      AND ap.user_type_id = t.user_type_id
    WHERE o.name = N'sp_QuickieStore'
    OPTION(RECOMPILE);

    /*
    Wait categories: Only 2017+
    */
    IF EXISTS
    (
        SELECT
            1/0
        FROM sys.all_objects AS ao
        WHERE ao.name = N'query_store_wait_stats'
    )
    BEGIN
        SELECT
            wait_categories =
               'cpu (1): SOS_SCHEDULER_YIELD' UNION ALL
        SELECT 'lock (3): LCK_M_%' UNION ALL
        SELECT 'latch (4): LATCH_%' UNION ALL
        SELECT 'buffer latch (5): PAGELATCH_%' UNION ALL
        SELECT 'buffer io (6): PAGEIOLATCH_%' UNION ALL
        SELECT 'log io (14): LOGMGR, LOGBUFFER, LOGMGR_RESERVE_APPEND, LOGMGR_FLUSH, LOGMGR_PMM_LOG, CHKPT, WRITELOG' UNION ALL
        SELECT 'network io (15): ASYNC_NETWORK_IO, NET_WAITFOR_PACKET, PROXY_NETWORK_IO, EXTERNAL_SCRIPT_NETWORK_IOF' UNION ALL
        SELECT 'parallelism (16): CXPACKET, EXCHANGE, HT%, BMP%, BP%' UNION ALL
        SELECT 'memory (17): RESOURCE_SEMAPHORE, CMEMTHREAD, CMEMPARTITIONED, EE_PMOLOCK, MEMORY_ALLOCATION_EXT, RESERVED_MEMORY_ALLOCATION_EXT, MEMORY_GRANT_UPDATE';
    END;

    /*
    Results
    */
    SELECT
        results =
           'results returned at the end of the procedure:' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Runtime Stats: data from query_store_runtime_stats, along with query plan, query text, wait stats (2017+, when enabled), and parent object' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Compilation Stats (expert mode only): data from query_store_query about compilation metrics' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Resource Stats (expert mode only): data from dm_exec_query_stats, when available' UNION ALL
    SELECT 'query store does not currently track some details about memory grants and thread usage' UNION ALL
    SELECT 'so i go back to a plan cache view to try to track it down' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Store Plan Feedback (2022+, expert mode, or when using only_queries_with_feedback): Lists queries that have been adjusted based on automated feedback mechanisms' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Store Hints (2022+, expert mode or when using @only_queries_with_hints): lists hints applied to queries from automated feedback mechanisms' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Variants (2022+, expert mode or when using @only_queries_with_variants): lists plan variants from the Parameter Sensitive Plan feedback mechanism' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Store Waits By Query (2017+, expert mode only): information about query duration and logged wait stats' UNION ALL
    SELECT 'it can sometimes be useful to compare query duration to query wait times' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Store Waits Total (2017+, expert mode only): total wait stats for the chosen date range only' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Replicas (2022+, expert mode only): lists plans forced on AG replicas' UNION ALL
    SELECT REPLICATE('-', 100) UNION ALL
    SELECT 'Query Store Options (expert mode only): details about current query store configuration';

    /*
    Limitations
    */
    SELECT
        limitations =
           'frigid shortcomings:'  UNION ALL
    SELECT 'you need to be on at least SQL Server 2016 SP2, 2017 CU3, or any higher version to run this' UNION ALL
    SELECT 'if you''re on azure sql db then you''ll need to be in compat level 130' UNION ALL
    SELECT 'i do not currently support synapse or edge or other memes, and azure sql db support is not guaranteed';

    /*
    License to F5
    */
    SELECT
        mit_license_yo =
           'i am MIT licensed, so like, do whatever'
    UNION ALL

    SELECT
        mit_license_yo =
            'see printed messages for full license';

    RAISERROR('
MIT License

Copyright 2026 Darling Data, LLC

https://www.erikdarling.com/

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
', 0, 1) WITH NOWAIT;
    RETURN;
END; /*End @help section*/


/*
Normalize Sort Order.
Allow avg/average prefix for backwards compatibility,
e.g. 'avg cpu' or 'average cpu' maps to 'cpu'.
*/
IF LOWER(@sort_order) LIKE 'average %'
BEGIN
    SELECT
        @sort_order = LTRIM(SUBSTRING(@sort_order, 9, LEN(@sort_order)));
END;

IF LOWER(@sort_order) LIKE 'avg %'
BEGIN
    SELECT
        @sort_order = LTRIM(SUBSTRING(@sort_order, 5, LEN(@sort_order)));
END;

/*
Validate Sort Order.
We do this super early on, because we care about it even
when populating the tables that we declare very soon.
*/
IF @sort_order NOT IN
   (
       'cpu',
       'logical reads',
       'physical reads',
       'writes',
       'duration',
       'memory',
       'tempdb',
       'executions',
       'recent',
       'plan count by hashes',
       'cpu waits',
       'lock waits',
       'locks waits',
       'latch waits',
       'latches waits',
       'buffer latch waits',
       'buffer latches waits',
       'buffer io waits',
       'log waits',
       'log io waits',
       'network waits',
       'network io waits',
       'parallel waits',
       'parallelism waits',
       'memory waits',
       'total waits',
       'rows',
       'total cpu',
       'total logical reads',
       'total physical reads',
       'total writes',
       'total duration',
       'total memory',
       'total tempdb',
       'total rows'
   )
BEGIN
   RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT;

   SELECT
       @sort_order = 'cpu';
END;

DECLARE
    @sort_order_is_a_wait bit;

/*
Checks if the sort order is for a wait.
Cuts out a lot of repetition.
*/
IF LOWER(@sort_order) IN
   (
       'cpu waits',
       'lock waits',
       'locks waits',
       'latch waits',
       'latches waits',
       'buffer latch waits',
       'buffer latches waits',
       'buffer io waits',
       'log waits',
       'log io waits',
       'network waits',
       'network io waits',
       'parallel waits',
       'parallelism waits',
       'memory waits',
       'total waits'
   )
BEGIN
   SELECT
       @sort_order_is_a_wait = 1;
END;

/*
We also validate regression mode super early.
We need to do this here so we can build @ColumnDefinitions correctly.
It also lets us fail fast, if needed.
*/
DECLARE
    @regression_mode bit;

/*
Set @regression_mode if the given arguments indicate that
we are checking for regressed queries.
Also set any default parameters for regression mode while we're at it.
*/
IF @regression_baseline_start_date IS NOT NULL
BEGIN
    SELECT
        @regression_mode = 1,
        @regression_comparator =
            ISNULL(@regression_comparator, 'absolute'),
        @regression_direction =
            ISNULL(@regression_direction, 'regressed');
END;

/*
Error out if the @regression parameters do not make sense.
*/
IF
(
  @regression_baseline_start_date IS NULL
  AND
  (
      @regression_baseline_end_date IS NOT NULL
   OR @regression_comparator IS NOT NULL
   OR @regression_direction IS NOT NULL
  )
)
BEGIN
    RAISERROR('@regression_baseline_start_date is mandatory if you have specified any other @regression_ parameter.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/*
Error out if the @regression_baseline_start_date and
@regression_baseline_end_date are incompatible.
We could try and guess a sensible resolution, but
I do not think that we can know what people want.
*/
IF
(
    @regression_baseline_start_date IS NOT NULL
AND @regression_baseline_end_date IS NOT NULL
AND @regression_baseline_start_date >= @regression_baseline_end_date
)
BEGIN
    RAISERROR('@regression_baseline_start_date has been set greater than or equal to @regression_baseline_end_date.
This does not make sense. Check that the values of both parameters are as you intended them to be.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/*
Validate @regression_comparator.
*/
IF
(
    @regression_comparator IS NOT NULL
AND @regression_comparator NOT IN ('relative', 'absolute')
)
BEGIN
   RAISERROR('The regression_comparator (%s) you chose is so out of this world that I''m using ''absolute'' instead', 10, 1, @regression_comparator) WITH NOWAIT;

   SELECT
       @regression_comparator = 'absolute';
END;

/*
Validate @regression_direction.
*/
IF
(
    @regression_direction IS NOT NULL
AND @regression_direction NOT IN ('regressed', 'worse', 'improved', 'better', 'magnitude', 'absolute')
)
BEGIN
   RAISERROR('The regression_direction (%s) you chose is so out of this world that I''m using ''regressed'' instead', 10, 1, @regression_direction) WITH NOWAIT;

   SELECT
       @regression_direction = 'regressed';
END;

/*
Error out if we're trying to do regression mode with 'recent'
as our @sort_order. How could that ever make sense?
*/
IF
(
    @regression_mode = 1
AND @sort_order = 'recent'
)
BEGIN
    RAISERROR('Your @sort_order is ''recent'', but you are trying to compare metrics for two time periods.
If you can imagine a useful way to do that, then make a feature request.
Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/*
Error out if we're trying to do regression mode with 'plan count by hashes'
as our @sort_order. How could that ever make sense?
*/
IF
(
    @regression_mode = 1
AND @sort_order = 'plan count by hashes'
)
BEGIN
    RAISERROR('Your @sort_order is ''plan count by hashes'', but you are trying to compare metrics for two time periods.
This is probably not useful, since our method of comparing two time period relies on only checking query hashes that are in both time periods.
If you can imagine a useful way to do that, then make a feature request.
Otherwise, either stop specifying any @regression_ parameters or specify a different @sort_order.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/*
Error out if @regression_comparator tells us to use division,
but @regression_direction tells us to take the modulus.
It doesn't make sense to specifically ask us to remove the sign
of something that doesn't care about it.
*/
IF
(
    @regression_comparator = 'relative'
AND @regression_direction IN ('absolute', 'magnitude')
)
BEGIN
    RAISERROR('Your @regression_comparator is ''relative'', but you have asked for an ''absolute'' or ''magnitude'' @regression_direction. This is probably a mistake.
Your @regression_direction tells us to take the absolute value of our result of comparing the metrics in the current time period to the baseline time period,
but your @regression_comparator is telling us to use division to compare the two time periods. This is unlikely to produce useful results.
If you can imagine a useful way to do that, then make a feature request. Otherwise, either change @regression_direction to another value
(e.g. ''better'' or ''worse'') or change @regression_comparator to ''absolute''.', 11, 1) WITH NOWAIT;
    RETURN;
END;

/*
These are the tables that we'll use to grab data from query store
It will be fun
You'll love it
*/

/*
Plans we'll be working on
*/
CREATE TABLE
    #distinct_plans
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for procedures we're searching
*/
CREATE TABLE
    #procedure_plans
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for procedures we're searching
*/
CREATE TABLE
    #procedure_object_ids
(
    [object_id] bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for ad hoc or procedures we're searching for
*/
CREATE TABLE
    #query_types
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for plans we want
*/
CREATE TABLE
    #include_plan_ids
(
    plan_id bigint PRIMARY KEY CLUSTERED
                   WITH (IGNORE_DUP_KEY = ON)
);

/*
Hold query_ids for plans we want
*/
CREATE TABLE
    #include_query_ids
(
    query_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for ignored plans
*/
CREATE TABLE
    #ignore_plan_ids
(
    plan_id bigint PRIMARY KEY CLUSTERED
                   WITH (IGNORE_DUP_KEY = ON)
);

/*
Hold query_ids for ignored plans
*/
CREATE TABLE
    #ignore_query_ids
(
    query_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold query hashes for plans we want
*/
CREATE TABLE
    #include_query_hashes
(
    query_hash_s varchar(131),
    query_hash AS
        CONVERT
        (
            binary(8),
            query_hash_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
For filtering by @execution_count.
This is only used for filtering, so it only needs one column.
*/
CREATE TABLE
    #plan_ids_having_enough_executions
(
    plan_id bigint PRIMARY KEY CLUSTERED,
);

/*
The following two tables are for adding extra columns
on to our output. We need these for sorting by anything
that isn't in #query_store_runtime_stats.

We still have to declare these tables even when they're
not used because the debug output breaks if we don't.

They are database dependent but not truncated at
the end of each loop, so we need a database_id
column.

We do not truncate these because we need them to still
be in scope and fully populated when we return our
final results from #query_store_runtime_stats, which
is done after the point where we would truncate.
*/

/*
Holds plan_id with the count of the number of query hashes they have.
Only used when we're sorting by how many plan hashes each
query hash has.
*/
CREATE TABLE
    #plan_ids_with_query_hashes
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    query_hash binary(8) NOT NULL,
    plan_hash_count_for_query_hash integer NOT NULL,
    PRIMARY KEY CLUSTERED (database_id, plan_id, query_hash)
);

/*
Largely just exists because total_query_wait_time_ms
isn't in our normal output.

Unfortunately needs an extra column for regression
mode's benefit. The alternative was either a
horrible UNPIVOT with an extra temp table
or changing @parameters everywhere (and
therefore every sp_executesql).
*/
CREATE TABLE
    #plan_ids_with_total_waits
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    from_regression_baseline varchar(3) NOT NULL,
    total_query_wait_time_ms bigint NOT NULL,
    PRIMARY KEY CLUSTERED(database_id, plan_id, from_regression_baseline)
);

/*
Used in regression mode to hold the
statistics for each query hash in our
baseline time period.
*/
CREATE TABLE
    #regression_baseline_runtime_stats
(
    query_hash binary(8) NOT NULL PRIMARY KEY CLUSTERED,
    /* Nullable to protect from division by 0. */
    regression_metric_average float NULL
);

/*
Used in regression mode to hold the
statistics for each query hash in our
normal time period.
*/
CREATE TABLE
    #regression_current_runtime_stats
(
    query_hash binary(8) NOT NULL PRIMARY KEY CLUSTERED,
    /* Nullable to protect from division by 0. */
    current_metric_average float NULL
);

/*
Used in regression mode to hold the
results of comparing our two time
periods.

This is also used just like a
sort-helping table. For example,
it is used to bolt columns
on to our final output.
*/
CREATE TABLE
    #regression_changes
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    query_hash binary(8) NOT NULL,
    change_since_regression_time_period float NULL,
    PRIMARY KEY CLUSTERED (database_id, plan_id, query_hash)
);

/*
Hold plan hashes for plans we want
*/
CREATE TABLE
    #include_plan_hashes
(
    plan_hash_s varchar(131),
    plan_hash AS
        CONVERT
        (
            binary(8),
            plan_hash_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
Hold query hashes for ignored plans
*/
CREATE TABLE
    #ignore_query_hashes
(
    query_hash_s varchar(131),
    query_hash AS
        CONVERT
        (
            binary(8),
            query_hash_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
Hold plan hashes for ignored plans
*/
CREATE TABLE
    #ignore_plan_hashes
(
    plan_hash_s varchar(131),
    plan_hash AS
        CONVERT
        (
            binary(8),
            plan_hash_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
Hold sql handles for plans we want
*/
CREATE TABLE
    #include_sql_handles
(
    sql_handle_s varchar(131),
    sql_handle AS
        CONVERT
        (
            varbinary(64),
            sql_handle_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
Hold sql handles for ignored plans
*/
CREATE TABLE
    #ignore_sql_handles
(
    sql_handle_s varchar(131),
    sql_handle AS
        CONVERT
        (
            varbinary(64),
            sql_handle_s,
            1
        ) PERSISTED NOT NULL
          PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for only query with hints
*/
CREATE TABLE
    #only_queries_with_hints
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for only query with feedback
*/
CREATE TABLE
    #only_queries_with_feedback
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for only query with variants
*/
CREATE TABLE
    #only_queries_with_variants
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for forced plans and/or forced plan failures
I'm overloading this a bit for simplicity, since searching for
failures is just an extension of searching for forced plans
*/

CREATE TABLE
    #forced_plans_failures
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for matching query text
*/
CREATE TABLE
    #query_text_search
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for matching query text (not)
*/
CREATE TABLE
    #query_text_search_not
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Hold plan_ids for matching wait filter
*/
CREATE TABLE
    #wait_filter
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Index and statistics entries to avoid
*/
CREATE TABLE
    #maintenance_plans
(
    plan_id bigint PRIMARY KEY CLUSTERED
);

/*
Query Store Setup
*/
CREATE TABLE
    #database_query_store_options
(
    database_id integer NOT NULL,
    desired_state_desc nvarchar(60) NULL,
    actual_state_desc nvarchar(60) NULL,
    readonly_reason nvarchar(100) NULL,
    current_storage_size_mb bigint NULL,
    flush_interval_seconds bigint NULL,
    interval_length_minutes bigint NULL,
    max_storage_size_mb bigint NULL,
    stale_query_threshold_days bigint NULL,
    max_plans_per_query bigint NULL,
    query_capture_mode_desc nvarchar(60) NULL,
    capture_policy_execution_count integer NULL,
    capture_policy_total_compile_cpu_time_ms bigint NULL,
    capture_policy_total_execution_cpu_time_ms bigint NULL,
    capture_policy_stale_threshold_hours integer NULL,
    size_based_cleanup_mode_desc nvarchar(60) NULL,
    wait_stats_capture_mode_desc nvarchar(60) NULL
);

/*
Query Store Trouble
*/
CREATE TABLE
    #query_store_trouble
(
    database_id integer NOT NULL,
    desired_state_desc nvarchar(60) NULL,
    actual_state_desc nvarchar(60) NULL,
    readonly_reason nvarchar(100) NULL,
    current_storage_size_mb bigint NULL,
    flush_interval_seconds bigint NULL,
    interval_length_minutes bigint NULL,
    max_storage_size_mb bigint NULL,
    stale_query_threshold_days bigint NULL,
    max_plans_per_query bigint NULL,
    query_capture_mode_desc nvarchar(60) NULL,
    size_based_cleanup_mode_desc nvarchar(60) NULL
);

/*
Plans and Plan information
*/
CREATE TABLE
    #query_store_plan
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    query_id bigint NOT NULL,
    all_plan_ids varchar(max),
    plan_group_id bigint NULL,
    engine_version nvarchar(32) NULL,
    compatibility_level smallint NOT NULL,
    query_plan_hash binary(8) NOT NULL,
    query_plan nvarchar(max) NULL,
    is_online_index_plan bit NOT NULL,
    is_trivial_plan bit NOT NULL,
    is_parallel_plan bit NOT NULL,
    is_forced_plan bit NOT NULL,
    toggle_forcing nvarchar(300) NOT NULL,
    is_natively_compiled bit NOT NULL,
    force_failure_count bigint NOT NULL,
    last_force_failure_reason_desc nvarchar(128) NULL,
    count_compiles bigint NULL,
    initial_compile_start_time datetimeoffset(7) NOT NULL,
    last_compile_start_time datetimeoffset(7) NULL,
    last_execution_time datetimeoffset(7) NULL,
    avg_compile_duration_ms float NULL,
    last_compile_duration_ms bigint NULL,
    plan_forcing_type_desc nvarchar(60) NULL,
    has_compile_replay_script bit NULL,
    is_optimized_plan_forcing_disabled bit NULL,
    plan_type_desc nvarchar(120) NULL
);

/*
Queries and Compile Information
*/
CREATE TABLE
    #query_store_query
(
    database_id integer NOT NULL,
    query_id bigint NOT NULL,
    query_text_id bigint NOT NULL,
    context_settings_id bigint NOT NULL,
    object_id bigint NULL,
    object_name AS
        ISNULL
        (
            QUOTENAME
            (
                OBJECT_SCHEMA_NAME
                (
                    object_id,
                    database_id
                )
            ) +
            N'.' +
            QUOTENAME
            (
                OBJECT_NAME
                (
                    object_id,
                    database_id
                )
            ),
            CASE
                WHEN object_id > 0
                THEN N'Unknown object_id: ' +
                     RTRIM(object_id)
                ELSE N'Adhoc'
            END
        ),
    batch_sql_handle varbinary(64) NULL,
    query_hash binary(8) NOT NULL,
    is_internal_query bit NOT NULL,
    query_parameterization_type_desc nvarchar(60) NULL,
    initial_compile_start_time datetimeoffset(7) NOT NULL,
    last_compile_start_time datetimeoffset(7) NULL,
    last_execution_time datetimeoffset(7) NULL,
    last_compile_batch_sql_handle varbinary(64) NULL,
    last_compile_batch_offset_start bigint NULL,
    last_compile_batch_offset_end bigint NULL,
    count_compiles bigint NULL,
    avg_compile_duration_ms float NULL,
    total_compile_duration_ms AS
        (count_compiles * avg_compile_duration_ms),
    last_compile_duration_ms bigint NULL,
    avg_bind_duration_ms float NULL,
    total_bind_duration_ms AS
        (count_compiles * avg_bind_duration_ms),
    last_bind_duration_ms bigint NULL,
    avg_bind_cpu_time_ms float NULL,
    total_bind_cpu_time_ms AS
        (count_compiles * avg_bind_cpu_time_ms),
    last_bind_cpu_time_ms bigint NULL,
    avg_optimize_duration_ms float NULL,
    total_optimize_duration_ms AS
        (count_compiles * avg_optimize_duration_ms),
    last_optimize_duration_ms bigint NULL,
    avg_optimize_cpu_time_ms float NULL,
    total_optimize_cpu_time_ms AS
        (count_compiles * avg_optimize_cpu_time_ms),
    last_optimize_cpu_time_ms bigint NULL,
    avg_compile_memory_mb float NULL,
    total_compile_memory_mb AS
        (count_compiles * avg_compile_memory_mb),
    last_compile_memory_mb bigint NULL,
    max_compile_memory_mb bigint NULL,
    is_clouddb_internal_query bit NULL
);

/*
Query Text And Columns From sys.dm_exec_query_stats
*/
CREATE TABLE
    #query_store_query_text
(
    database_id integer NOT NULL,
    query_text_id bigint NOT NULL,
    query_sql_text xml NULL,
    statement_sql_handle varbinary(64) NULL,
    is_part_of_encrypted_module bit NOT NULL,
    has_restricted_text bit NOT NULL,
    total_grant_mb bigint NULL,
    last_grant_mb bigint NULL,
    min_grant_mb bigint NULL,
    max_grant_mb bigint NULL,
    total_used_grant_mb bigint NULL,
    last_used_grant_mb bigint NULL,
    min_used_grant_mb bigint NULL,
    max_used_grant_mb bigint NULL,
    total_ideal_grant_mb bigint NULL,
    last_ideal_grant_mb bigint NULL,
    min_ideal_grant_mb bigint NULL,
    max_ideal_grant_mb bigint NULL,
    total_reserved_threads bigint NULL,
    last_reserved_threads bigint NULL,
    min_reserved_threads bigint NULL,
    max_reserved_threads bigint NULL,
    total_used_threads bigint NULL,
    last_used_threads bigint NULL,
    min_used_threads bigint NULL,
    max_used_threads bigint NULL
);

/*
Figure it out.
*/
CREATE TABLE
    #dm_exec_query_stats
(
    statement_sql_handle varbinary(64) NOT NULL,
    total_grant_mb bigint NULL,
    last_grant_mb bigint NULL,
    min_grant_mb bigint NULL,
    max_grant_mb bigint NULL,
    total_used_grant_mb bigint NULL,
    last_used_grant_mb bigint NULL,
    min_used_grant_mb bigint NULL,
    max_used_grant_mb bigint NULL,
    total_ideal_grant_mb bigint NULL,
    last_ideal_grant_mb bigint NULL,
    min_ideal_grant_mb bigint NULL,
    max_ideal_grant_mb bigint NULL,
    total_reserved_threads bigint NULL,
    last_reserved_threads bigint NULL,
    min_reserved_threads bigint NULL,
    max_reserved_threads bigint NULL,
    total_used_threads bigint NULL,
    last_used_threads bigint NULL,
    min_used_threads bigint NULL,
    max_used_threads bigint NULL
);

/*
Runtime stats information
*/
CREATE TABLE
    #query_store_runtime_stats
(
    database_id integer NOT NULL,
    runtime_stats_id bigint NOT NULL,
    plan_id bigint NOT NULL,
    runtime_stats_interval_id bigint NOT NULL,
    execution_type_desc nvarchar(60) NULL,
    first_execution_time datetimeoffset(7) NOT NULL,
    last_execution_time datetimeoffset(7) NOT NULL,
    count_executions bigint NOT NULL,
    executions_per_second AS
        ISNULL
        (
            count_executions /
                NULLIF
                (
                    DATEDIFF
                    (
                        SECOND,
                        first_execution_time,
                        last_execution_time
                    ),
                    0
                ),
            0
        ),
    avg_duration_ms float NULL,
    last_duration_ms bigint NOT NULL,
    min_duration_ms bigint NOT NULL,
    max_duration_ms bigint NOT NULL,
    total_duration_ms AS
        (avg_duration_ms * count_executions),
    avg_cpu_time_ms float NULL,
    last_cpu_time_ms bigint NOT NULL,
    min_cpu_time_ms bigint NOT NULL,
    max_cpu_time_ms bigint NOT NULL,
    total_cpu_time_ms AS
        (avg_cpu_time_ms * count_executions),
    avg_logical_io_reads_mb float NULL,
    last_logical_io_reads_mb bigint NOT NULL,
    min_logical_io_reads_mb bigint NOT NULL,
    max_logical_io_reads_mb bigint NOT NULL,
    total_logical_io_reads_mb AS
        (avg_logical_io_reads_mb * count_executions),
    avg_logical_io_writes_mb float NULL,
    last_logical_io_writes_mb bigint NOT NULL,
    min_logical_io_writes_mb bigint NOT NULL,
    max_logical_io_writes_mb bigint NOT NULL,
    total_logical_io_writes_mb AS
        (avg_logical_io_writes_mb * count_executions),
    avg_physical_io_reads_mb float NULL,
    last_physical_io_reads_mb bigint NOT NULL,
    min_physical_io_reads_mb bigint NOT NULL,
    max_physical_io_reads_mb bigint NOT NULL,
    total_physical_io_reads_mb AS
        (avg_physical_io_reads_mb * count_executions),
    avg_clr_time_ms float NULL,
    last_clr_time_ms bigint NOT NULL,
    min_clr_time_ms bigint NOT NULL,
    max_clr_time_ms bigint NOT NULL,
    total_clr_time_ms AS
        (avg_clr_time_ms * count_executions),
    last_dop bigint NOT NULL,
    min_dop bigint NOT NULL,
    max_dop bigint NOT NULL,
    avg_query_max_used_memory_mb float NULL,
    last_query_max_used_memory_mb bigint NOT NULL,
    min_query_max_used_memory_mb bigint NOT NULL,
    max_query_max_used_memory_mb bigint NOT NULL,
    total_query_max_used_memory_mb AS
        (avg_query_max_used_memory_mb * count_executions),
    avg_rowcount float NULL,
    last_rowcount bigint NOT NULL,
    min_rowcount bigint NOT NULL,
    max_rowcount bigint NOT NULL,
    total_rowcount AS
        (avg_rowcount * count_executions),
    avg_num_physical_io_reads_mb float NULL,
    last_num_physical_io_reads_mb bigint NULL,
    min_num_physical_io_reads_mb bigint NULL,
    max_num_physical_io_reads_mb bigint NULL,
    total_num_physical_io_reads_mb AS
        (avg_num_physical_io_reads_mb * count_executions),
    avg_log_bytes_used_mb float NULL,
    last_log_bytes_used_mb bigint NULL,
    min_log_bytes_used_mb bigint NULL,
    max_log_bytes_used_mb bigint NULL,
    total_log_bytes_used_mb AS
        (avg_log_bytes_used_mb * count_executions),
    avg_tempdb_space_used_mb float NULL,
    last_tempdb_space_used_mb bigint NULL,
    min_tempdb_space_used_mb bigint NULL,
    max_tempdb_space_used_mb bigint NULL,
    total_tempdb_space_used_mb AS
        (avg_tempdb_space_used_mb * count_executions),
    from_regression_baseline varchar(3) NULL,
    context_settings nvarchar(256) NULL
);

/*
Wait Stats, When Available (2017+)
*/
CREATE TABLE
    #query_store_wait_stats
(
    database_id integer NOT NULL,
    plan_id bigint NOT NULL,
    wait_category_desc nvarchar(60) NOT NULL,
    total_query_wait_time_ms bigint NOT NULL,
    avg_query_wait_time_ms float NULL,
    last_query_wait_time_ms bigint NOT NULL,
    min_query_wait_time_ms bigint NOT NULL,
    max_query_wait_time_ms bigint NOT NULL
);

/*
Context is everything
*/
CREATE TABLE
    #query_context_settings
(
    database_id integer NOT NULL,
    context_settings_id bigint NOT NULL,
    set_options varbinary(8) NULL,
    language_id smallint NOT NULL,
    date_format smallint NOT NULL,
    date_first tinyint NOT NULL,
    status varbinary(2) NULL,
    required_cursor_options integer NOT NULL,
    acceptable_cursor_options integer NOT NULL,
    merge_action_type smallint NOT NULL,
    default_schema_id integer NOT NULL,
    is_replication_specific bit NOT NULL,
    is_contained varbinary(1) NULL
);

/*
Feed me Seymour
*/
CREATE TABLE
    #query_store_plan_feedback
(
    database_id integer NOT NULL,
    plan_feedback_id bigint NOT NULL,
    plan_id bigint NULL,
    feature_desc nvarchar(120) NULL,
    feedback_data nvarchar(max) NULL,
    state_desc nvarchar(120) NULL,
    create_time datetimeoffset(7) NOT NULL,
    last_updated_time datetimeoffset(7) NULL
);

/*
America's Most Hinted
*/
CREATE TABLE
    #query_store_query_hints
(
    database_id integer NOT NULL,
    query_hint_id bigint NOT NULL,
    query_id bigint NOT NULL,
    query_hint_text nvarchar(max) NULL,
    last_query_hint_failure_reason_desc nvarchar(256) NULL,
    query_hint_failure_count bigint NOT NULL,
    source_desc nvarchar(256) NULL
);

/*
Variant? Deviant? You decide!
*/
CREATE TABLE
    #query_store_query_variant
(
    database_id integer NOT NULL,
    query_variant_query_id bigint NOT NULL,
    parent_query_id bigint NOT NULL,
    dispatcher_plan_id bigint NOT NULL
);

/*
Replicants
*/
CREATE TABLE
    #query_store_replicas
(
    database_id integer NOT NULL,
    replica_group_id bigint NOT NULL,
    role_type smallint NOT NULL,
    replica_name nvarchar(1288) NULL
);

/*Gonna try gathering this based on*/
CREATE TABLE
    #query_hash_totals
(
    database_id integer NOT NULL,
    query_hash binary(8) NOT NULL,
    total_executions bigint NOT NULL,
    total_duration_ms decimal(19,2) NOT NULL,
    total_cpu_time_ms decimal(19,2) NOT NULL,
    total_logical_reads_mb decimal(19,2) NOT NULL,
    total_physical_reads_mb decimal(19,2) NOT NULL,
    total_logical_writes_mb decimal(19,2) NOT NULL,
    total_clr_time_ms decimal(19,2) NOT NULL,
    total_memory_mb decimal(19,2) NOT NULL,
    total_rowcount decimal(19,2) NOT NULL,
    total_num_physical_io_reads decimal(19,2) NULL,
    total_log_bytes_used_mb decimal(19,2) NULL,
    total_tempdb_space_used_mb decimal(19,2) NULL,
    PRIMARY KEY CLUSTERED(query_hash, database_id)
);

/*
Location, location, location
*/
CREATE TABLE
    #query_store_plan_forcing_locations
(
    database_id integer NOT NULL,
    plan_forcing_location_id bigint NOT NULL,
    query_id bigint NOT NULL,
    plan_id bigint NOT NULL,
    replica_group_id bigint NOT NULL
);

/*
Tuning In, Tuning Out
*/
CREATE TABLE
    #database_automatic_tuning_configurations
(
    database_id integer NOT NULL,
    [option] nvarchar(120) NULL,
    option_value nvarchar(120) NULL,
    [type] nvarchar(120) NULL,
    type_value nvarchar(120) NULL,
    details nvarchar(4000) NULL,
    [state] integer NULL
);

/*
Trouble Loves Me
*/
CREATE TABLE
    #troubleshoot_performance
(
    id bigint IDENTITY PRIMARY KEY CLUSTERED,
    current_table nvarchar(100) NOT NULL,
    start_time datetime NOT NULL,
    end_time datetime NULL,
    runtime_ms AS
        FORMAT
        (
            DATEDIFF
            (
                MILLISECOND,
                start_time,
                end_time
            ),
            'N0'
        )
);

/*GET ALL THOSE DATABASES*/
CREATE TABLE
    #databases
(
    database_name sysname PRIMARY KEY CLUSTERED
);

/* Create tables for database filtering */
CREATE TABLE
    #include_databases
(
    database_name sysname PRIMARY KEY CLUSTERED
);

CREATE TABLE
    #exclude_databases
(
    database_name sysname PRIMARY KEY CLUSTERED
);

CREATE TABLE
    #requested_but_skipped_databases
(
    database_name sysname PRIMARY KEY CLUSTERED,
    reason varchar(100) NOT NULL
);

/* Create a table variable to store ALL column definitions with logical ordering */
DECLARE
    @ColumnDefinitions table
(
    column_id integer
        PRIMARY KEY CLUSTERED, /* Controls the ordering of columns in output */
    metric_group nvarchar(50) NOT NULL, /* Grouping (duration, cpu, etc.) */
    metric_type nvarchar(20) NOT NULL, /* Type within group (avg, total, last, min, max) */
    column_name nvarchar(100) NOT NULL, /* Column name as it appears in output */
    column_source nvarchar(max) NOT NULL, /* Source expression or formula */
    is_conditional bit NOT NULL, /* Is this a conditional column (depends on a parameter) */
    condition_param nvarchar(50) NULL, /* Parameter name this column depends on */
    condition_value sql_variant NULL, /* Value the parameter must have */
    expert_only bit NOT NULL, /* Only include in expert mode */
    format_pattern nvarchar(20) NULL /* Format pattern (e.g., 'N0', 'P2', NULL for no formatting) */
);

/* Fill the table with ALL columns, including SQL 2022 views and regression columns */

/* Basic metadata columns (still part of prefix, but in the table) */
INSERT INTO
    @ColumnDefinitions
(
    column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern
)
VALUES
    (10, 'emergency_troubleshooting', 'toggle_forcing', 'toggle_forcing', 'qsp.toggle_forcing', 0, NULL, NULL, 1, NULL),
    (20, 'metadata', 'force_count', 'force_failure_count', 'qsp.force_failure_count', 0, NULL, NULL, 0, NULL),
    (30, 'metadata', 'force_reason', 'last_force_failure_reason_desc', 'qsp.last_force_failure_reason_desc', 0, NULL, NULL, 0, NULL),
    /* SQL 2022 specific columns */
    (40, 'sql_2022', 'feedback', 'has_query_feedback', 'CASE WHEN EXISTS (SELECT 1/0 FROM #query_store_plan_feedback AS qspf WHERE qspf.plan_id = qsp.plan_id) THEN ''Yes'' ELSE ''No'' END', 1, 'sql_2022_views', 1, 0, NULL),
    (50, 'sql_2022', 'hints', 'has_query_store_hints', 'CASE WHEN EXISTS (SELECT 1/0 FROM #query_store_query_hints AS qsqh WHERE qsqh.query_id = qsp.query_id) THEN ''Yes'' ELSE ''No'' END', 1, 'sql_2022_views', 1, 0, NULL),
    (55, 'sql_2022', 'hints', 'set_query_store_hints', '''EXECUTE ''+ QUOTENAME(DB_NAME(qsp.database_id)) + ''.sys.sp_query_store_set_hints @query_id = '' + CONVERT(nvarchar(20), qsq.query_id) + '', @query_hints = N''''OPTION(older_hints_go_here, USE HINT(''''''''newer_hints_go_here''''''''))'''';''', 1, 'sql_2022_views', 1, 1, NULL),
    (60, 'sql_2022', 'variants', 'has_plan_variants', 'CASE WHEN EXISTS (SELECT 1/0 FROM #query_store_query_variant AS qsqv WHERE qsqv.query_variant_query_id = qsp.query_id) THEN ''Yes'' ELSE ''No'' END', 1, 'sql_2022_views', 1, 0, NULL),
    (70, 'sql_2022', 'replay', 'has_compile_replay_script', 'qsp.has_compile_replay_script', 1, 'sql_2022_views', 1, 0, NULL),
    (80, 'sql_2022', 'opt_forcing', 'is_optimized_plan_forcing_disabled', 'qsp.is_optimized_plan_forcing_disabled', 1, 'sql_2022_views', 1, 0, NULL),
    (90, 'sql_2022', 'plan_type', 'plan_type_desc', 'qsp.plan_type_desc', 1, 'sql_2022_views', 1, 0, NULL),
    /* New version features */
    (95, 'new_features', 'forcing_type', 'plan_forcing_type_desc', 'qsp.plan_forcing_type_desc', 1, 'new', 1, 0, NULL),
    (97, 'new_features', 'top_waits', 'top_waits', 'w.top_waits', 1, 'new', 1, 0, NULL),
    /* Date/time columns (not conditional, always included) */
    (100, 'execution_time', 'first', 'first_execution_time', 'CASE WHEN @timezone IS NULL THEN SWITCHOFFSET(qsrs.first_execution_time, @utc_offset_string) WHEN @timezone IS NOT NULL THEN qsrs.first_execution_time AT TIME ZONE @timezone END', 0, NULL, NULL, 0, NULL),
    (110, 'execution_time', 'first_utc', 'first_execution_time_utc', 'qsrs.first_execution_time', 0, NULL, NULL, 0, NULL),
    (120, 'execution_time', 'last', 'last_execution_time', 'CASE WHEN @timezone IS NULL THEN SWITCHOFFSET(qsrs.last_execution_time, @utc_offset_string) WHEN @timezone IS NOT NULL THEN qsrs.last_execution_time AT TIME ZONE @timezone END', 0, NULL, NULL, 0, NULL),
    (130, 'execution_time', 'last_utc', 'last_execution_time_utc', 'qsrs.last_execution_time', 0, NULL, NULL, 0, NULL),
    /* Regression mode columns */
    (140, 'regression', 'baseline', 'from_regression_baseline_time_period', 'qsrs.from_regression_baseline', 1, 'regression_mode', 1, 0, NULL),
    (150, 'regression', 'hash', 'query_hash_from_regression_checking', 'regression.query_hash', 1, 'regression_mode', 1, 0, NULL),
    /* Execution columns */
    (200, 'executions', 'count', 'count_executions', 'qsrs.count_executions', 0, NULL, NULL, 0, 'N0'),
    (210, 'executions', 'per_second', 'executions_per_second', 'qsrs.executions_per_second', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals - conditionally added */
    (215, 'executions', 'count_hash', 'count_executions_by_query_hash', 'qht.total_executions', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* Duration metrics (group together avg, total, last, min, max) */
    (300, 'duration', 'avg', 'avg_duration_ms', 'qsrs.avg_duration_ms', 0, NULL, NULL, 0, 'N0'),
    (310, 'duration', 'last', 'last_duration_ms', 'qsrs.last_duration_ms', 0, NULL, NULL, 1, 'N0'),
    (320, 'duration', 'min', 'min_duration_ms', 'qsrs.min_duration_ms', 0, NULL, NULL, 0, 'N0'),
    (330, 'duration', 'max', 'max_duration_ms', 'qsrs.max_duration_ms', 0, NULL, NULL, 0, 'N0'),
    (340, 'duration', 'total', 'total_duration_ms', 'qsrs.total_duration_ms', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for duration */
    (315, 'duration', 'total_hash', 'total_duration_ms_by_query_hash', 'qht.total_duration_ms', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* CPU metrics */
    (400, 'cpu', 'avg', 'avg_cpu_time_ms', 'qsrs.avg_cpu_time_ms', 0, NULL, NULL, 0, 'N0'),
    (410, 'cpu', 'last', 'last_cpu_time_ms', 'qsrs.last_cpu_time_ms', 0, NULL, NULL, 1, 'N0'),
    (420, 'cpu', 'min', 'min_cpu_time_ms', 'qsrs.min_cpu_time_ms', 0, NULL, NULL, 0, 'N0'),
    (430, 'cpu', 'max', 'max_cpu_time_ms', 'qsrs.max_cpu_time_ms', 0, NULL, NULL, 0, 'N0'),
    (440, 'cpu', 'total', 'total_cpu_time_ms', 'qsrs.total_cpu_time_ms', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for CPU */
    (415, 'cpu', 'total_hash', 'total_cpu_time_ms_by_query_hash', 'qht.total_cpu_time_ms', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* Logical IO Reads */
    (500, 'logical_io_reads', 'avg', 'avg_logical_io_reads_mb', 'qsrs.avg_logical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    (510, 'logical_io_reads', 'last', 'last_logical_io_reads_mb', 'qsrs.last_logical_io_reads_mb', 0, NULL, NULL, 1, 'N0'),
    (520, 'logical_io_reads', 'min', 'min_logical_io_reads_mb', 'qsrs.min_logical_io_reads_mb', 0, NULL, NULL, 1, 'N0'),
    (530, 'logical_io_reads', 'max', 'max_logical_io_reads_mb', 'qsrs.max_logical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    (540, 'logical_io_reads', 'total', 'total_logical_io_reads_mb', 'qsrs.total_logical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for logical reads */
    (515, 'logical_io_reads', 'total_hash', 'total_logical_io_reads_mb_by_query_hash', 'qht.total_logical_reads_mb', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* Logical IO Writes */
    (600, 'logical_io_writes', 'avg', 'avg_logical_io_writes_mb', 'qsrs.avg_logical_io_writes_mb', 0, NULL, NULL, 0, 'N0'),
    (610, 'logical_io_writes', 'last', 'last_logical_io_writes_mb', 'qsrs.last_logical_io_writes_mb', 0, NULL, NULL, 1, 'N0'),
    (620, 'logical_io_writes', 'min', 'min_logical_io_writes_mb', 'qsrs.min_logical_io_writes_mb', 0, NULL, NULL, 1, 'N0'),
    (630, 'logical_io_writes', 'max', 'max_logical_io_writes_mb', 'qsrs.max_logical_io_writes_mb', 0, NULL, NULL, 0, 'N0'),
    (640, 'logical_io_writes', 'total', 'total_logical_io_writes_mb', 'qsrs.total_logical_io_writes_mb', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for logical writes */
    (615, 'logical_io_writes', 'total_hash', 'total_logical_io_writes_mb_by_query_hash', 'qht.total_logical_writes_mb', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* Physical IO Reads */
    (700, 'physical_io_reads', 'avg', 'avg_physical_io_reads_mb', 'qsrs.avg_physical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    (710, 'physical_io_reads', 'last', 'last_physical_io_reads_mb', 'qsrs.last_physical_io_reads_mb', 0, NULL, NULL, 1, 'N0'),
    (720, 'physical_io_reads', 'min', 'min_physical_io_reads_mb', 'qsrs.min_physical_io_reads_mb', 0, NULL, NULL, 1, 'N0'),
    (730, 'physical_io_reads', 'max', 'max_physical_io_reads_mb', 'qsrs.max_physical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    (740, 'physical_io_reads', 'total', 'total_physical_io_reads_mb', 'qsrs.total_physical_io_reads_mb', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for physical reads */
    (715, 'physical_io_reads', 'total_hash', 'total_physical_io_reads_mb_by_query_hash', 'qht.total_physical_reads_mb', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* CLR Time */
    (800, 'clr_time', 'avg', 'avg_clr_time_ms', 'qsrs.avg_clr_time_ms', 0, NULL, NULL, 0, 'N0'),
    (810, 'clr_time', 'last', 'last_clr_time_ms', 'qsrs.last_clr_time_ms', 0, NULL, NULL, 1, 'N0'),
    (820, 'clr_time', 'min', 'min_clr_time_ms', 'qsrs.min_clr_time_ms', 0, NULL, NULL, 1, 'N0'),
    (830, 'clr_time', 'max', 'max_clr_time_ms', 'qsrs.max_clr_time_ms', 0, NULL, NULL, 0, 'N0'),
    (840, 'clr_time', 'total', 'total_clr_time_ms', 'qsrs.total_clr_time_ms', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for CLR time */
    (815, 'clr_time', 'total_hash', 'total_clr_time_ms_by_query_hash', 'qht.total_clr_time_ms', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* DOP (Degree of Parallelism) */
    (900, 'dop', 'last', 'last_dop', 'qsrs.last_dop', 0, NULL, NULL, 1, NULL),
    (910, 'dop', 'min', 'min_dop', 'qsrs.min_dop', 0, NULL, NULL, 0, NULL),
    (920, 'dop', 'max', 'max_dop', 'qsrs.max_dop', 0, NULL, NULL, 0, NULL),
    /* Memory metrics */
    (1000, 'memory', 'avg', 'avg_query_max_used_memory_mb', 'qsrs.avg_query_max_used_memory_mb', 0, NULL, NULL, 0, 'N0'),
    (1010, 'memory', 'last', 'last_query_max_used_memory_mb', 'qsrs.last_query_max_used_memory_mb', 0, NULL, NULL, 1, 'N0'),
    (1020, 'memory', 'min', 'min_query_max_used_memory_mb', 'qsrs.min_query_max_used_memory_mb', 0, NULL, NULL, 1, 'N0'),
    (1030, 'memory', 'max', 'max_query_max_used_memory_mb', 'qsrs.max_query_max_used_memory_mb', 0, NULL, NULL, 0, 'N0'),
    (1040, 'memory', 'total', 'total_query_max_used_memory_mb', 'qsrs.total_query_max_used_memory_mb', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for memory */
    (1015, 'memory', 'total_hash', 'total_query_max_used_memory_mb_by_query_hash', 'qht.total_memory_mb', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* Row counts */
    (1100, 'rowcount', 'avg', 'avg_rowcount', 'qsrs.avg_rowcount', 0, NULL, NULL, 0, 'N0'),
    (1110, 'rowcount', 'last', 'last_rowcount', 'qsrs.last_rowcount', 0, NULL, NULL, 1, 'N0'),
    (1120, 'rowcount', 'min', 'min_rowcount', 'qsrs.min_rowcount', 0, NULL, NULL, 1, 'N0'),
    (1130, 'rowcount', 'max', 'max_rowcount', 'qsrs.max_rowcount', 0, NULL, NULL, 0, 'N0'),
    (1140, 'rowcount', 'total', 'total_rowcount', 'qsrs.total_rowcount', 0, NULL, NULL, 0, 'N0'),
    /* Hash totals for row counts */
    (1115, 'rowcount', 'total_hash', 'total_rowcount_by_query_hash', 'qht.total_rowcount', 1, 'include_query_hash_totals', 1, 0, 'N0'),
    /* New metrics for newer versions */
    /* Physical IO Reads (for newer versions) */
    (1200, 'num_physical_io_reads', 'avg', 'avg_num_physical_io_reads_mb', 'qsrs.avg_num_physical_io_reads_mb', 1, 'new', 1, 0, 'N0'),
    (1210, 'num_physical_io_reads', 'last', 'last_num_physical_io_reads_mb', 'qsrs.last_num_physical_io_reads_mb', 1, 'new', 1, 1, 'N0'),
    (1220, 'num_physical_io_reads', 'min', 'min_num_physical_io_reads_mb', 'qsrs.min_num_physical_io_reads_mb', 1, 'new', 1, 1, 'N0'),
    (1230, 'num_physical_io_reads', 'max', 'max_num_physical_io_reads_mb', 'qsrs.max_num_physical_io_reads_mb', 1, 'new', 1, 0, 'N0'),
    (1240, 'num_physical_io_reads', 'total', 'total_num_physical_io_reads_mb', 'qsrs.total_num_physical_io_reads_mb', 1, 'new', 1, 0, 'N0'),
    /* Hash totals for new physical IO reads */
    (1215, 'num_physical_io_reads', 'total_hash', 'total_num_physical_io_reads_mb_by_query_hash', 'qht.total_num_physical_io_reads', 1, 'new_with_hash_totals', 1, 0, 'N0'),
    /* Finish adding the remaining columns (log bytes and tempdb usage) */
    /* Log bytes used */
    (1300, 'log_bytes', 'avg', 'avg_log_bytes_used_mb', 'qsrs.avg_log_bytes_used_mb', 1, 'new', 1, 0, 'N0'),
    (1310, 'log_bytes', 'last', 'last_log_bytes_used_mb', 'qsrs.last_log_bytes_used_mb', 1, 'new', 1, 1, 'N0'),
    (1320, 'log_bytes', 'min', 'min_log_bytes_used_mb', 'qsrs.min_log_bytes_used_mb', 1, 'new', 1, 1, 'N0'),
    (1330, 'log_bytes', 'max', 'max_log_bytes_used_mb', 'qsrs.max_log_bytes_used_mb', 1, 'new', 1, 0, 'N0'),
    (1340, 'log_bytes', 'total', 'total_log_bytes_used_mb', 'qsrs.total_log_bytes_used_mb', 1, 'new', 1, 0, 'N0'),
    /* Hash totals for log bytes */
    (1315, 'log_bytes', 'total_hash', 'total_log_bytes_used_mb_by_query_hash', 'qht.total_log_bytes_used_mb', 1, 'new_with_hash_totals', 1, 0, 'N0'),
    /* TempDB usage  */
    (1400, 'tempdb', 'avg', 'avg_tempdb_space_used_mb', 'qsrs.avg_tempdb_space_used_mb', 1, 'new', 1, 0, 'N0'),
    (1410, 'tempdb', 'last', 'last_tempdb_space_used_mb', 'qsrs.last_tempdb_space_used_mb', 1, 'new', 1, 1, 'N0'),
    (1420, 'tempdb', 'min', 'min_tempdb_space_used_mb', 'qsrs.min_tempdb_space_used_mb', 1, 'new', 1, 1, 'N0'),
    (1430, 'tempdb', 'max', 'max_tempdb_space_used_mb', 'qsrs.max_tempdb_space_used_mb', 1, 'new', 1, 0, 'N0'),
    (1440, 'tempdb', 'total', 'total_tempdb_space_used_mb', 'qsrs.total_tempdb_space_used_mb', 1, 'new', 1, 0, 'N0'),
    /* Hash totals for tempdb */
    (1415, 'tempdb', 'total_hash', 'total_tempdb_space_used_mb_by_query_hash', 'qht.total_tempdb_space_used_mb', 1, 'new_with_hash_totals', 1, 0, 'N0'),
    /* Context settings and sorting columns  */
    (1500, 'metadata', 'context', 'context_settings', 'qsrs.context_settings', 0, NULL, NULL, 0, NULL);

/* Add special sorting columns based on @sort_order */
/* Plan hash count for 'plan count by hashes' sort */
IF @sort_order = 'plan count by hashes'
BEGIN
    INSERT INTO
        @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
    VALUES
        /* 230, so just before avg_query_duration_ms. */
        (230, 'sort_order', 'query_hash', 'query_hash_from_hash_counting', 'hashes.query_hash', 0, NULL, NULL, 0, NULL),
        (231, 'sort_order', 'plan_hash_count', 'plan_hash_count_for_query_hash', 'hashes.plan_hash_count_for_query_hash', 0, NULL, NULL, 0, 'N0');
END;

/* Dynamic regression change column based on formatting and comparator */
IF @regression_mode = 1 AND @regression_comparator = 'relative' AND @format_output = 1
BEGIN
    INSERT INTO
        @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
    VALUES (160, 'regression', 'change', 'change_in_average_for_query_hash_since_regression_time_period', 'regression.change_since_regression_time_period', 1, 'regression_mode', 1, 0, 'P2');
END;
ELSE IF @regression_mode = 1 AND @format_output = 1
BEGIN
    INSERT INTO
        @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
    VALUES (160, 'regression', 'change', 'change_in_average_for_query_hash_since_regression_time_period', 'regression.change_since_regression_time_period', 1, 'regression_mode', 1, 0, 'N2');
END;
ELSE IF @regression_mode = 1
BEGIN
    INSERT INTO
        @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
    VALUES (160, 'regression', 'change', 'change_in_average_for_query_hash_since_regression_time_period', 'regression.change_since_regression_time_period', 1, 'regression_mode', 1, 0, NULL);
END;

/* Wait time for wait-based sorting */
IF @sort_order_is_a_wait = 1
BEGIN
    INSERT INTO
        @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
    VALUES
        /* 240, so just before avg_query_duration_ms. */
        (240, 'sort_order', 'wait_time', 'total_wait_time_from_sort_order_ms', 'waits.total_query_wait_time_ms', 0, NULL, NULL, 0, 'N0');
END;

/* ROW_NUMBER window function for sorting */
INSERT INTO
    @ColumnDefinitions (column_id, metric_group, metric_type, column_name, column_source, is_conditional, condition_param, condition_value, expert_only, format_pattern)
VALUES
    (
        2000,
        'metadata',
        'n',
        'n',
        'ROW_NUMBER() OVER (PARTITION BY qsrs.plan_id ORDER BY ' +
        CASE WHEN @regression_mode = 1 THEN
             /* As seen when populating #regression_changes */
             CASE @regression_direction
                  WHEN 'regressed' THEN 'regression.change_since_regression_time_period'
                  WHEN 'worse' THEN 'regression.change_since_regression_time_period'
                  WHEN 'improved' THEN 'regression.change_since_regression_time_period * -1.0'
                  WHEN 'better' THEN 'regression.change_since_regression_time_period * -1.0'
                  WHEN 'magnitude' THEN 'ABS(regression.change_since_regression_time_period)'
                  WHEN 'absolute' THEN 'ABS(regression.change_since_regression_time_period)'
             END
        ELSE
            CASE @sort_order
                 WHEN 'cpu' THEN 'qsrs.avg_cpu_time_ms'
                 WHEN 'logical reads' THEN 'qsrs.avg_logical_io_reads_mb'
                 WHEN 'physical reads' THEN 'qsrs.avg_physical_io_reads_mb'
                 WHEN 'writes' THEN 'qsrs.avg_logical_io_writes_mb'
                 WHEN 'duration' THEN 'qsrs.avg_duration_ms'
                 WHEN 'memory' THEN 'qsrs.avg_query_max_used_memory_mb'
                 WHEN 'tempdb' THEN 'qsrs.avg_tempdb_space_used_mb' /*This gets validated later*/
                 WHEN 'executions' THEN 'qsrs.count_executions'
                 WHEN 'recent' THEN 'qsrs.last_execution_time'
                 WHEN 'rows' THEN 'qsrs.avg_rowcount'
                 WHEN 'total cpu' THEN 'qsrs.total_cpu_time_ms'
                 WHEN 'total logical reads' THEN 'qsrs.total_logical_io_reads_mb'
                 WHEN 'total physical reads' THEN 'qsrs.total_physical_io_reads_mb'
                 WHEN 'total writes' THEN 'qsrs.total_logical_io_writes_mb'
                 WHEN 'total duration' THEN 'qsrs.total_duration_ms'
                 WHEN 'total memory' THEN 'qsrs.total_query_max_used_memory_mb'
                 WHEN 'total tempdb' THEN 'qsrs.total_tempdb_space_used_mb' /*This gets validated later*/
                 WHEN 'total rows' THEN 'qsrs.total_rowcount'
                 WHEN 'plan count by hashes' THEN 'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash'
                 ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN 'waits.total_query_wait_time_ms'
                 ELSE 'qsrs.avg_cpu_time_ms' END
            END
        END + ' DESC)',
        0,
        NULL,
        NULL,
        0,
        NULL
    );

/* Create a table variable to define parameter processing */
DECLARE
    @FilterParameters table
(
    parameter_name nvarchar(100) NOT NULL,
    parameter_value nvarchar(4000) NOT NULL,
    temp_table_name sysname NOT NULL,
    column_name sysname NOT NULL,
    data_type sysname NOT NULL,
    is_include bit NOT NULL,
    requires_secondary_processing bit NOT NULL
);

/* Populate with parameter definitions*/
INSERT INTO
    @FilterParameters
(
    parameter_name,
    parameter_value,
    temp_table_name,
    column_name,
    data_type,
    is_include,
    requires_secondary_processing
)
SELECT
    v.parameter_name,
    v.parameter_value,
    v.temp_table_name,
    v.column_name,
    v.data_type,
    v.is_include,
    v.requires_secondary_processing
FROM
(
    VALUES
        /* Include parameters */
        ('include_plan_ids', @include_plan_ids, '#include_plan_ids', 'plan_id', 'bigint', 1, 0),
        ('include_query_ids', @include_query_ids, '#include_query_ids', 'query_id', 'bigint', 1, 1),
        ('include_query_hashes', @include_query_hashes, '#include_query_hashes', 'query_hash_s', 'varchar', 1, 1),
        ('include_plan_hashes', @include_plan_hashes, '#include_plan_hashes', 'plan_hash_s', 'varchar', 1, 1),
        ('include_sql_handles', @include_sql_handles, '#include_sql_handles', 'sql_handle_s', 'varchar', 1, 1),
        /* Ignore parameters */
        ('ignore_plan_ids', @ignore_plan_ids, '#ignore_plan_ids', 'plan_id', 'bigint', 0, 0),
        ('ignore_query_ids', @ignore_query_ids, '#ignore_query_ids', 'query_id', 'bigint', 0, 1),
        ('ignore_query_hashes', @ignore_query_hashes, '#ignore_query_hashes', 'query_hash_s', 'varchar', 0, 1),
        ('ignore_plan_hashes', @ignore_plan_hashes, '#ignore_plan_hashes', 'plan_hash_s', 'varchar', 0, 1),
        ('ignore_sql_handles', @ignore_sql_handles, '#ignore_sql_handles', 'sql_handle_s', 'varchar', 0, 1)
    ) AS v
    (
        parameter_name,
        parameter_value,
        temp_table_name,
        column_name,
        data_type,
        is_include,
        requires_secondary_processing
    )
WHERE v.parameter_value IS NOT NULL;

/*
Try to be helpful by subbing in a database name if null
*/
IF
  (
      @database_name IS NULL
      AND LOWER(DB_NAME())
          NOT IN
          (
              N'master',
              N'model',
              N'msdb',
              N'tempdb',
              N'dbatools',
              N'dbadmin',
              N'dbmaintenance',
              N'rdsadmin',
              N'other_memes'
          )
      AND @get_all_databases = 0
  )
BEGIN
    SELECT
        @database_name =
            DB_NAME();
END;

/*
Attempt at overloading procedure name so it can
accept a [schema].[procedure] pasted from results
from other executions of sp_QuickieStore
*/
IF
(
      @procedure_name LIKE N'[[]%].[[]%]'
  AND @procedure_schema IS NULL
)
BEGIN
    SELECT
        @procedure_schema = PARSENAME(@procedure_name, 2),
        @procedure_name   = PARSENAME(@procedure_name, 1);
END;

/*
Variables for the variable gods
*/
DECLARE
    @azure bit,
    @engine integer,
    @product_version integer,
    @database_id integer,
    @database_name_quoted sysname,
    @procedure_name_quoted nvarchar(1024),
    @collation sysname,
    @new bit,
    @sql nvarchar(max),
    @isolation_level nvarchar(max),
    @parameters nvarchar(4000),
    @plans_top bigint,
    @queries_top bigint,
    @nc10 nvarchar(2),
    @where_clause nvarchar(max),
    @query_text_search_original_value nvarchar(4000),
    @query_text_search_not_original_value nvarchar(4000),
    @procedure_exists bit,
    @query_store_exists bit,
    @query_store_trouble bit,
    @query_store_waits_enabled bit,
    @sql_2022_views bit,
    @ags_present bit,
    @string_split_ints nvarchar(1500),
    @string_split_strings nvarchar(1500),
    @current_table nvarchar(100),
    @troubleshoot_insert nvarchar(max),
    @troubleshoot_update nvarchar(max),
    @troubleshoot_info nvarchar(max),
    @rc bigint,
    @em tinyint,
    @fo tinyint,
    @start_date_original datetimeoffset(7),
    @end_date_original datetimeoffset(7),
    @utc_minutes_difference bigint,
    @utc_offset_string nvarchar(6),
    @df integer,
    @work_start_utc time(0),
    @work_end_utc time(0),
    @regression_baseline_start_date_original datetimeoffset(7),
    @regression_baseline_end_date_original datetimeoffset(7),
    @regression_where_clause nvarchar(max),
    @column_sql nvarchar(max),
    @param_name nvarchar(100),
    @param_value nvarchar(4000),
    @temp_table sysname,
    @column_name sysname,
    @data_type sysname,
    @is_include bit,
    @requires_secondary_processing bit,
    @split_sql nvarchar(max),
    @error_msg nvarchar(2000),
    @conflict_list nvarchar(max) = N'',
    @database_cursor CURSOR,
    @filter_cursor CURSOR,
    @dynamic_sql nvarchar(max) = N'',
    @secondary_sql nvarchar(max) = N'',
    @temp_target_table nvarchar(100),
    @exist_or_not_exist nvarchar(20);

/*
In cases where we are escaping @query_text_search and
looping over multiple databases, we need to make sure
to not escape the string more than once.

The solution is to reset to the original value each loop.
This therefore needs to be done before the cursor.
*/
IF
(
    @get_all_databases = 1
AND @escape_brackets = 1
)
BEGIN
    SELECT
         @query_text_search_original_value = @query_text_search,
         @query_text_search_not_original_value = @query_text_search_not;
END;

/*
We also need to capture original values here.
Doing it inside a loop over multiple databases
would break the UTC conversion.
*/
SELECT
    @start_date_original =
        ISNULL
        (
            @start_date,
            DATEADD
            (
                DAY,
                -7,
                DATEDIFF
                (
                    DAY,
                    '19000101',
                    SYSUTCDATETIME()
                )
            )
        ),
    @end_date_original =
        ISNULL
        (
            @end_date,
            DATEADD
            (
                DAY,
                1,
                DATEADD
                (
                    MINUTE,
                    0,
                    DATEDIFF
                    (
                        DAY,
                        '19000101',
                        SYSUTCDATETIME()
                    )
                )
            )
        );

/*
Set the _original variables, as we have
for other values that would break inside
the loop without them.

This is also where we enforce the default
that leaving @regression_baseline_end_date
unspecified will set it to be a week
after @regression_baseline_start_date.

We do not need to account for the possibility
of @regression_baseline_start_date being NULL.
Due to the above error-throwing, it cannot be
NULL if we are doing anything that would care
about it.
*/
SELECT
    @regression_baseline_start_date_original =
        @regression_baseline_start_date,
    @regression_baseline_end_date_original =
        ISNULL
        (
            @regression_baseline_end_date,
            DATEADD
            (
                DAY,
                7,
                @regression_baseline_start_date
            )
        );

/*
Error out if the @execution_type_desc value is invalid.
*/
IF
(
    @execution_type_desc IS NOT NULL
AND @execution_type_desc NOT IN ('regular', 'aborted', 'exception')
)
BEGIN
    RAISERROR('@execution_type_desc can only take one of these three non-NULL values:
    1) ''regular'' (meaning a successful execution),
    2) ''aborted'' (meaning that the client cancelled the query),
    3) ''exception'' (meaning that an exception cancelled the query).

You supplied ''%s''.

If you leave @execution_type_desc NULL, then we grab every type of execution.

See the official documentation for sys.query_store_runtime_stats for more details on the execution types.', 11, 1, @execution_type_desc) WITH NOWAIT;
    RETURN;
END;


/*
This section is in a cursor whether we
hit one database, or multiple

I do all the variable assignment in the
cursor block because some of them
are assigned for the specific database
that is currently being looked at
*/

/*
Look at databases to include or exclude
*/
IF @get_all_databases = 1
BEGIN
    /* Check for contradictory parameters */
    IF @database_name IS NOT NULL
    BEGIN
        IF @debug = 1
        BEGIN
            RAISERROR(N'@database name being ignored since @get_all_databases is set to 1', 0, 0) WITH NOWAIT;
        END;
        SET @database_name = NULL;
    END;

    /* Parse @include_databases if specified using XML for compatibility */
    IF @include_databases IS NOT NULL
    BEGIN
        INSERT
            #include_databases
        WITH
            (TABLOCK)
        (
            database_name
        )
        SELECT DISTINCT
            database_name =
                LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname')))
        FROM
        (
            SELECT
                x = CONVERT
                    (
                        xml,
                        N'<i>' +
                        REPLACE
                        (
                            @include_databases,
                            N',',
                            N'</i><i>'
                        ) +
                        N'</i>'
                    )
        ) AS a
        CROSS APPLY x.nodes(N'//i') AS t(c)
        WHERE LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname'))) <> N''
        OPTION(RECOMPILE);
    END;

    /* Parse @exclude_databases if specified using XML for compatibility */
    IF @exclude_databases IS NOT NULL
    BEGIN
        INSERT
            #exclude_databases
        WITH
            (TABLOCK)
        (
            database_name
        )
        SELECT DISTINCT
            database_name =
                LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname')))
        FROM
        (
            SELECT
                x = CONVERT
                    (
                        xml,
                        N'<i>' +
                        REPLACE
                        (
                            @exclude_databases,
                            N',',
                            N'</i><i>'
                        ) +
                        N'</i>'
                    )
        ) AS a
        CROSS APPLY x.nodes(N'//i') AS t(c)
        WHERE LTRIM(RTRIM(c.value(N'(./text())[1]', N'sysname'))) <> N''
        OPTION(RECOMPILE);

        /* Check for databases in both include and exclude lists */
        IF @include_databases IS NOT NULL
        BEGIN
            /* Build list of conflicting databases */
            SELECT
                @conflict_list =
                    @conflict_list +
                    ed.database_name + N', '
            FROM #exclude_databases AS ed
            WHERE EXISTS
                (
                    SELECT
                        1/0
                    FROM #include_databases AS id
                    WHERE id.database_name = ed.database_name
                )
            OPTION(RECOMPILE);

            /* If we found any conflicts, raise an error */
            IF LEN(@conflict_list) > 0
            BEGIN
                /* Remove trailing comma and space */
                SET @conflict_list = LEFT(@conflict_list, LEN(@conflict_list) - 2);

                SET @error_msg =
                    N'The following databases appear in both @include_databases and @exclude_databases, which creates ambiguity: ' +
                    @conflict_list + N'. Please remove these databases from one of the lists.';

                RAISERROR(@error_msg, 16, 1);
                RETURN;
            END;
        END;
    END;
END;

/*
Build up the databases to process
*/
IF
(
SELECT
    CONVERT
    (
        sysname,
        SERVERPROPERTY('EngineEdition')
    )
) IN (5, 8)
BEGIN
    INSERT INTO
        #databases
    WITH
        (TABLOCK)
    (
        database_name
    )
    SELECT
        database_name =
            ISNULL(@database_name, DB_NAME())
    WHERE @get_all_databases = 0

    UNION ALL

    SELECT
        database_name =
            d.name
    FROM sys.databases AS d
    WHERE @get_all_databases = 1
    AND   d.is_query_store_on = 1
    AND   d.database_id > 4
    AND   d.state = 0
    AND   d.is_in_standby = 0
    AND   d.is_read_only = 0
    AND   (
            @include_databases IS NULL
            OR EXISTS (SELECT 1/0 FROM #include_databases AS id WHERE id.database_name = d.name)
          )
    AND   (
            @exclude_databases IS NULL
            OR NOT EXISTS (SELECT 1/0 FROM #exclude_databases AS ed WHERE ed.database_name = d.name)
          )
    OPTION(RECOMPILE);

    /* Track which requested databases were skipped */
    IF  @include_databases IS NOT NULL
    AND @get_all_databases = 1
    BEGIN
        INSERT
            #requested_but_skipped_databases
        WITH
            (TABLOCK)
        (
            database_name,
            reason
        )
        SELECT
            id.database_name,
            reason =
                CASE
                    WHEN d.name IS NULL
                    THEN 'Database does not exist'
                    WHEN d.state <> 0
                    THEN 'Database not online'
                    WHEN d.is_query_store_on = 0
                    THEN 'Query Store not enabled'
                    WHEN d.is_in_standby = 1
                    THEN 'Database is in standby'
                    WHEN d.is_read_only = 1
                    THEN 'Database is read-only'
                    WHEN d.database_id <= 4
                    THEN 'System database'
                    ELSE 'Other issue'
                END
        FROM #include_databases AS id
        LEFT JOIN sys.databases AS d
          ON id.database_name = d.name
        WHERE NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #databases AS db
                  WHERE db.database_name = id.database_name
              )
        OPTION(RECOMPILE);
    END;
END;
ELSE
BEGIN
    INSERT
        #databases
    WITH
        (TABLOCK)
    (
        database_name
    )
    SELECT
        database_name =
            ISNULL(@database_name, DB_NAME())
    WHERE @get_all_databases = 0

    UNION ALL

    SELECT
        database_name =
            d.name
    FROM sys.databases AS d
    WHERE @get_all_databases = 1
    AND   d.is_query_store_on = 1
    AND   d.name NOT IN (N'master', N'model', N'msdb', N'tempdb', N'rdsadmin')
    AND   d.state = 0
    AND   d.is_in_standby = 0
    AND   d.is_read_only = 0
    AND   NOT EXISTS
    (
        SELECT
            1/0
        FROM sys.dm_hadr_availability_replica_states AS s
        JOIN sys.availability_databases_cluster AS c
          ON  s.group_id = c.group_id
          AND d.name = c.database_name
        WHERE s.is_local <> 1
        AND   s.role_desc <> N'PRIMARY'
        AND   DATABASEPROPERTYEX(c.database_name, N'Updateability') <> N'READ_WRITE'
    )
    AND   (
            @include_databases IS NULL
            OR EXISTS (SELECT 1/0 FROM #include_databases AS id WHERE id.database_name = d.name)
          )
    AND   (
            @exclude_databases IS NULL
            OR NOT EXISTS (SELECT 1/0 FROM #exclude_databases AS ed WHERE ed.database_name = d.name)
          )
    OPTION(RECOMPILE);

    /* Track which requested databases were skipped */
    IF  @include_databases IS NOT NULL
    AND @get_all_databases = 1
    BEGIN
        INSERT
            #requested_but_skipped_databases
        WITH
            (TABLOCK)
        (
            database_name,
            reason
        )
        SELECT
            id.database_name,
            reason =
                CASE
                    WHEN d.name IS NULL THEN 'Database does not exist'
                    WHEN d.state <> 0 THEN 'Database not online'
                    WHEN d.is_query_store_on = 0 THEN 'Query Store not enabled'
                    WHEN d.is_in_standby = 1 THEN 'Database is in standby'
                    WHEN d.is_read_only = 1 THEN 'Database is read-only'
                    WHEN d.database_id <= 4 THEN 'System database'
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM sys.dm_hadr_availability_replica_states AS s
                             JOIN sys.availability_databases_cluster AS c
                               ON  s.group_id = c.group_id
                               AND d.name = c.database_name
                             WHERE s.is_local <> 1
                             AND   s.role_desc <> N'PRIMARY'
                             AND   DATABASEPROPERTYEX(c.database_name, N'Updateability') <> N'READ_WRITE'
                         ) THEN 'AG replica issues'
                    ELSE 'Other issue'
                END
        FROM #include_databases AS id
        LEFT JOIN sys.databases AS d
          ON id.database_name = d.name
        WHERE NOT EXISTS
              (
                  SELECT
                      1/0
                  FROM #databases AS db
                  WHERE db.database_name = id.database_name
              )
        OPTION(RECOMPILE);
    END;
END;

SET
    @database_cursor =
        CURSOR
        LOCAL
        SCROLL
        DYNAMIC
        READ_ONLY
FOR
SELECT
    d.database_name
FROM #databases AS d;

OPEN @database_cursor;

FETCH FIRST
FROM @database_cursor
INTO @database_name;

WHILE @@FETCH_STATUS = 0
BEGIN
/*
These tables need to get cleared out
to avoid result pollution and
primary key violations
*/
IF @debug = 1
BEGIN
    RAISERROR('Truncating per-database temp tables for the next iteration', 0, 0) WITH NOWAIT;
END;

TRUNCATE TABLE
    #regression_baseline_runtime_stats;

TRUNCATE TABLE
    #regression_current_runtime_stats;

TRUNCATE TABLE
    #distinct_plans;

TRUNCATE TABLE
    #procedure_plans;

TRUNCATE TABLE
    #procedure_object_ids;

TRUNCATE TABLE
    #maintenance_plans;

TRUNCATE TABLE
    #query_text_search;

TRUNCATE TABLE
    #query_text_search_not;

TRUNCATE TABLE
    #dm_exec_query_stats;

TRUNCATE TABLE
    #query_types;

TRUNCATE TABLE
    #wait_filter;

TRUNCATE TABLE
    #only_queries_with_hints;

TRUNCATE TABLE
    #only_queries_with_feedback;

TRUNCATE TABLE
    #only_queries_with_variants;

TRUNCATE TABLE
    #forced_plans_failures;

TRUNCATE TABLE
    #plan_ids_having_enough_executions;

TRUNCATE TABLE
    #include_plan_ids;

TRUNCATE TABLE
    #include_query_ids;

TRUNCATE TABLE
    #include_query_hashes;

TRUNCATE TABLE
    #include_plan_hashes;

TRUNCATE TABLE
    #include_sql_handles;

TRUNCATE TABLE
    #ignore_plan_ids;

TRUNCATE TABLE
    #ignore_query_ids;

TRUNCATE TABLE
    #ignore_query_hashes;

TRUNCATE TABLE
    #ignore_plan_hashes;

TRUNCATE TABLE
    #ignore_sql_handles;

TRUNCATE TABLE
    #query_hash_totals;


/*
Some variable assignment, because why not?
*/
IF @debug = 1
BEGIN
    RAISERROR('Starting analysis for database %s', 0, 0, @database_name) WITH NOWAIT;
END;

SELECT
    @azure =
        CASE
            WHEN
                CONVERT
                (
                    sysname,
                    SERVERPROPERTY('EDITION')
                ) = N'SQL Azure'
            THEN 1
            ELSE 0
        END,
    @engine =
        CONVERT
        (
            integer,
            SERVERPROPERTY('ENGINEEDITION')
        ),
    @product_version =
        CONVERT
        (
            integer,
            PARSENAME
            (
                CONVERT
                (
                    sysname,
                    SERVERPROPERTY('PRODUCTVERSION')
                ),
                4
            )
        ),
    @database_id =
        DB_ID(@database_name),
    @database_name_quoted =
        QUOTENAME(@database_name),
    @procedure_name_quoted =
        QUOTENAME(@database_name) +
        N'.' +
        QUOTENAME
        (
            ISNULL
            (
                @procedure_schema,
                N'dbo'
            )
        ) +
        N'.' +
        QUOTENAME(@procedure_name),
    @collation =
        CONVERT
        (
            sysname,
            DATABASEPROPERTYEX
            (
                @database_name,
                'Collation'
            )
        ),
    @new = 0,
    @sql = N'',
    @isolation_level =
        N'
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;',
    @parameters =
        N'@top bigint,
          @start_date datetimeoffset(7),
          @end_date datetimeoffset(7),
          @execution_count bigint,
          @duration_ms bigint,
          @execution_type_desc nvarchar(60),
          @database_id integer,
          @queries_top bigint,
          @work_start_utc time(0),
          @work_end_utc time(0),
          @regression_baseline_start_date datetimeoffset(7),
          @regression_baseline_end_date datetimeoffset(7)',
    @plans_top =
        9223372036854775807,
    @queries_top =
        9223372036854775807,
    @nc10 = NCHAR(10),
    @where_clause = N'',
    @query_text_search =
        CASE
            WHEN @get_all_databases = 1
            AND  @escape_brackets = 1
            THEN @query_text_search_original_value
            ELSE @query_text_search
         END,
    @query_text_search_not =
        CASE
            WHEN @get_all_databases = 1
            AND  @escape_brackets = 1
            THEN @query_text_search_not_original_value
            ELSE @query_text_search_not
         END,
    @procedure_exists = 0,
    @query_store_exists = 0,
    @query_store_trouble = 0,
    @query_store_waits_enabled = 0,
    @sql_2022_views = 0,
    @ags_present = 0,
    @current_table = N'',
    @string_split_ints = N'
        SELECT DISTINCT
            ids =
                LTRIM
                (
                    RTRIM
                    (
                        ids.ids
                    )
                )
        FROM
        (
            SELECT
                ids =
                    x.x.value
                        (
                            ''(./text())[1]'',
                            ''bigint''
                        )
            FROM
            (
                SELECT
                    ids =
                        CONVERT
                        (
                            xml,
                            ''<x>'' +
                            REPLACE
                            (
                                REPLACE
                                (
                                    @ids,
                                    '','',
                                    ''</x><x>''
                                ),
                                '' '',
                                ''''
                            ) +
                            ''</x>''
                        ).query(''.'')
            ) AS ids
                CROSS APPLY ids.nodes(''x'') AS x (x)
        ) AS ids
        OPTION(RECOMPILE);
        ',
    @string_split_strings = N'
        SELECT DISTINCT
            ids =
                LTRIM
                (
                    RTRIM
                    (
                        ids.ids
                    )
                )
        FROM
        (
            SELECT
                ids =
                    x.x.value
                        (
                            ''(./text())[1]'',
                            ''varchar(131)''
                        )
            FROM
            (
                SELECT
                    ids =
                        CONVERT
                        (
                            xml,
                            ''<x>'' +
                            REPLACE
                            (
                                REPLACE
                                (
                                    @ids,
                                    '','',
                                    ''</x><x>''
                                ),
                                '' '',
                                ''''
                            ) +
                            ''</x>''
                        ).query(''.'')
            ) AS ids
                CROSS APPLY ids.nodes(''x'') AS x (x)
        ) AS ids
        OPTION(RECOMPILE);
        ',
    @troubleshoot_insert = N'
        INSERT
            #troubleshoot_performance
        WITH
            (TABLOCK)
        (
            current_table,
            start_time
        )
        VALUES
        (
            @current_table,
            GETDATE()
        )
        OPTION(RECOMPILE);
        ',
    @troubleshoot_update = N'
        UPDATE
            tp
        SET
            tp.end_time = GETDATE()
        FROM #troubleshoot_performance AS tp
        WHERE tp.current_table = @current_table
        OPTION(RECOMPILE);
        ',
    @troubleshoot_info = N'
        SELECT
            (
                SELECT
                    runtime_ms =
                        tp.runtime_ms,
                    current_table =
                        tp.current_table,
                    query_length =
                        FORMAT(LEN(@sql), ''N0''),
                    ''processing-instruction(statement_text)'' =
                        @sql
                FROM #troubleshoot_performance AS tp
                WHERE tp.current_table = @current_table
                FOR XML
                    PATH(N''''),
                    TYPE
            ).query(''.[1]'') AS current_query
        OPTION(RECOMPILE);
        ',
    @rc = 0,
    @em = @expert_mode,
    @fo = @format_output,
    @utc_minutes_difference =
        DATEDIFF
        (
            MINUTE,
            SYSDATETIME(),
            SYSUTCDATETIME()
        ),
    /*
    There is no direct way to get the user's timezone in a
    format compatible with sys.time_zone_info.

    We also cannot directly get their UTC offset,
    so we need this hack to get it instead.

    This is to make our datetimeoffsets have the
    correct offset in cases where the user didn't
    give us their timezone.
    */
    @utc_offset_string = RIGHT(SYSDATETIMEOFFSET(), 6),
    @df = @@DATEFIRST,
    @work_start_utc = @work_start,
    @work_end_utc = @work_end;

/*
Some parameters can't be NULL,
and some shouldn't be empty strings
*/
SELECT
    @sort_order =
        ISNULL(@sort_order, 'cpu'),
    @top =
        ISNULL(@top, 10),
    @expert_mode =
        ISNULL(@expert_mode, 0),
    @hide_help_table =
        ISNULL(@hide_help_table, 0),
    @procedure_schema =
        NULLIF(@procedure_schema, ''),
    @procedure_name =
        NULLIF(@procedure_name, ''),
    @include_plan_ids =
        NULLIF(@include_plan_ids, ''),
    @include_query_ids =
        NULLIF(@include_query_ids, ''),
    @ignore_plan_ids =
        NULLIF(@ignore_plan_ids, ''),
    @ignore_query_ids =
        NULLIF(@ignore_query_ids, ''),
    @include_query_hashes =
        NULLIF(@include_query_hashes, ''),
    @include_plan_hashes =
        NULLIF(@include_plan_hashes, ''),
    @include_sql_handles =
        NULLIF(@include_sql_handles, ''),
    @ignore_query_hashes =
        NULLIF(@ignore_query_hashes, ''),
    @ignore_plan_hashes =
        NULLIF(@ignore_plan_hashes, ''),
    @ignore_sql_handles =
        NULLIF(@ignore_sql_handles, ''),
    @only_queries_with_hints =
        ISNULL(@only_queries_with_hints, 0),
    @only_queries_with_feedback =
        ISNULL(@only_queries_with_feedback, 0),
    @only_queries_with_variants =
        ISNULL(@only_queries_with_variants, 0),
    @only_queries_with_forced_plans =
        ISNULL(@only_queries_with_forced_plans, 0),
    @only_queries_with_forced_plan_failures =
        ISNULL(@only_queries_with_forced_plan_failures, 0),
    @escape_brackets =
        ISNULL(@escape_brackets, 0),
    @wait_filter =
        NULLIF(@wait_filter, ''),
    @query_type =
        NULLIF(@query_type, ''),
    @execution_type_desc =
        NULLIF(@execution_type_desc, ''),
    @format_output =
        ISNULL(@format_output, 1),
    @help =
        ISNULL(@help, 0),
    @debug =
        ISNULL(@debug, 0),
    @troubleshoot_performance =
        ISNULL(@troubleshoot_performance, 0),
    @get_all_databases =
        ISNULL(@get_all_databases, 0),
    @workdays =
        ISNULL(@workdays, 0),
    @include_query_hash_totals =
        ISNULL(@include_query_hash_totals, 0),
    @include_maintenance =
        ISNULL(@include_maintenance, 0),
    /*
        doing start and end date last because they're more complicated
        if start or end date is null,
    */
    @start_date =
        CASE
            WHEN @start_date IS NULL
            THEN
                DATEADD
                (
                    DAY,
                    -7,
                    DATEDIFF
                    (
                        DAY,
                        '19000101',
                        SYSUTCDATETIME()
                    )
                )
            WHEN @start_date IS NOT NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    @utc_minutes_difference,
                    @start_date_original
                )
        END,
    @end_date =
        CASE
            WHEN @end_date IS NULL
            THEN
                DATEADD
                (
                    DAY,
                    1,
                    DATEADD
                    (
                        MINUTE,
                        0,
                        DATEDIFF
                        (
                            DAY,
                            '19000101',
                            SYSUTCDATETIME()
                        )
                    )
                )
            WHEN @end_date IS NOT NULL
            THEN
                DATEADD
                (
                    MINUTE,
                    @utc_minutes_difference,
                    @end_date_original
                )
        END;

/*
I need to tweak this so the WHERE clause on the last execution column
works correctly as >= @start_date and < @end_date, otherwise there are no results
*/
IF @start_date >= @end_date
BEGIN
    SELECT
        @end_date =
            DATEADD
            (
                DAY,
                7,
                @start_date
            ),
        @end_date_original =
            DATEADD
            (
                DAY,
                1,
                @start_date_original
            );
END;

/*
As above, but for @regression_baseline_start_date and @regression_baseline_end_date.
*/
IF @regression_mode = 1
BEGIN
/*
We set both _date_original variables earlier.
*/
    SELECT
        @regression_baseline_start_date =
            DATEADD
            (
                MINUTE,
                @utc_minutes_difference,
                @regression_baseline_start_date_original
            ),
        @regression_baseline_end_date =
            DATEADD
            (
                MINUTE,
                @utc_minutes_difference,
                @regression_baseline_end_date_original
            );
END;

/*
Let's make sure things will work
*/

/*
Database are you there?
*/
IF
(
   @database_id IS NULL
OR @collation IS NULL
)
BEGIN
    RAISERROR('Database %s does not exist', 10, 1, @database_name) WITH NOWAIT;

    IF @get_all_databases = 0
    BEGIN
        IF @debug = 1
        BEGIN
            GOTO DEBUG;
        END;
        ELSE
        BEGIN
            RETURN;
        END;
    END;

    IF @get_all_databases = 1
    BEGIN
        FETCH NEXT
        FROM @database_cursor
        INTO @database_name;

        CONTINUE;
    END;
END;

/*
Database what are you?
*/
IF
(
    @azure = 1
AND @engine NOT IN (5, 8)
)
BEGIN
    RAISERROR('Not all Azure offerings are supported, please try avoiding memes', 11, 1) WITH NOWAIT;
    IF @debug = 1
    BEGIN
        GOTO DEBUG;
    END;
    ELSE
    BEGIN
        RETURN;
    END;
END;

/*
Sometimes sys.databases will report Query Store being on, but it's really not
*/
SELECT
    @current_table = 'checking query store existence',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN

    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @query_store_exists =
        CASE
            WHEN EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
                     WHERE
                     (
                          dqso.actual_state = 0
                       OR dqso.actual_state IS NULL
                     )
                 )
            OR   NOT EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
                 )
            THEN 0
            ELSE 1
        END
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

EXECUTE sys.sp_executesql
    @sql,
  N'@query_store_exists bit OUTPUT',
    @query_store_exists OUTPUT;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END;

IF @query_store_exists = 0
BEGIN
    RAISERROR('Query Store doesn''t seem to be enabled for database: %s', 10, 1, @database_name) WITH NOWAIT;

    IF @get_all_databases = 0
    BEGIN
        IF @debug = 1
        BEGIN
            GOTO DEBUG;
        END;
        ELSE
        BEGIN
            RETURN;
        END;
    END;

    IF @get_all_databases = 1
    BEGIN
        FETCH NEXT
        FROM @database_cursor
        INTO @database_name;

        CONTINUE;
    END;
END;

/*
If Query Store is enabled, but in read only mode for some reason, return some information about why
*/
SELECT
    @current_table = 'checking for query store trouble',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN

    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    database_id =
        @database_id,
    desired_state_desc,
    actual_state_desc,
    readonly_reason =
        CASE dqso.readonly_reason
             WHEN 0
             THEN ''None''
             WHEN 2
             THEN ''Database in single user mode''
             WHEN 4
             THEN ''Database is in emergency mode''
             WHEN 8
             THEN ''Database is AG secondary''
             WHEN 65536
             THEN ''Reached max size: '' +
                  FORMAT(dqso.current_storage_size_mb, ''N0'') +
                  '' of '' +
                  FORMAT(dqso.max_storage_size_mb, ''N0'') +
                  ''.''
             WHEN 131072
             THEN ''The number of different statements in Query Store has reached the internal memory limit''
             WHEN 262144
             THEN ''Size of in-memory items waiting to be persisted on disk has reached the internal memory limit''
             WHEN 524288
             THEN ''Database has reached disk size limit''
             ELSE ''WOAH''
        END,
    current_storage_size_mb,
    flush_interval_seconds,
    interval_length_minutes,
    max_storage_size_mb,
    stale_query_threshold_days,
    max_plans_per_query,
    query_capture_mode_desc,
    size_based_cleanup_mode_desc
FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
WHERE dqso.desired_state <> 4
AND   dqso.readonly_reason <> 8
AND   dqso.desired_state <> dqso.actual_state
AND   dqso.actual_state IN (0, 3)
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_trouble
WITH
    (TABLOCK)
(
    database_id,
    desired_state_desc,
    actual_state_desc,
    readonly_reason,
    current_storage_size_mb,
    flush_interval_seconds,
    interval_length_minutes,
    max_storage_size_mb,
    stale_query_threshold_days,
    max_plans_per_query,
    query_capture_mode_desc,
    size_based_cleanup_mode_desc
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

IF ROWCOUNT_BIG() > 0
BEGIN
    SELECT
        @query_store_trouble = 1;
END;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END;


/*
If you specified a procedure name, we need to figure out if there are any plans for it available
*/
IF @procedure_name IS NOT NULL
BEGIN
    IF @procedure_schema IS NULL
    BEGIN
        SELECT
            @procedure_schema = N'dbo';
    END;
    SELECT
        @current_table = 'checking procedure existence',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    IF CHARINDEX(N'%', @procedure_name) > 0
    BEGIN
        SELECT
            @current_table = 'getting procedure object ids for wildcard',
            @sql = @isolation_level;

        SELECT @sql += N'
SELECT
    p.object_id
FROM ' + @database_name_quoted + N'.sys.procedures AS p
JOIN ' + @database_name_quoted + N'.sys.schemas AS s
  ON p.schema_id = s.schema_id
WHERE s.name = @procedure_schema
AND   p.name LIKE @procedure_name
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #procedure_object_ids
        WITH
            (TABLOCK)
        (
            [object_id]
        )
        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_schema sysname,
            @procedure_name sysname',
            @procedure_schema,
            @procedure_name;

        IF ROWCOUNT_BIG() = 0
        BEGIN
            RAISERROR('No object_ids were found for %s in schema %s', 11, 1, @procedure_name, @procedure_schema) WITH NOWAIT;
            RETURN;
        END;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;

        SELECT
            @current_table = 'checking wildcard procedure existence',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
SELECT
    @procedure_exists =
        MAX(x.procedure_exists)
    FROM
    (
        SELECT
            procedure_exists =
                CASE
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                             WHERE EXISTS
                             (
                                 SELECT
                                     1/0
                                 FROM #procedure_object_ids AS p
                                 WHERE qsq.[object_id] = p.[object_id]
                             )
                         )
                    THEN 1
                    ELSE 0
                END
    ) AS x
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_exists bit OUTPUT,
            @procedure_name_quoted sysname',
            @procedure_exists OUTPUT,
            @procedure_name_quoted;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;
    END; /*End procedure object id check*/

    IF CHARINDEX(N'%', @procedure_name) = 0
    BEGIN
        SELECT
            @current_table = 'checking single procedure existence',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
SELECT
    @procedure_exists =
        CASE
            WHEN EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                     WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
                 )
            THEN 1
            ELSE 0
        END
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql,
          N'@procedure_exists bit OUTPUT,
            @procedure_name_quoted sysname',
            @procedure_exists OUTPUT,
            @procedure_name_quoted;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;
    END;

    IF
    (
        @procedure_exists = 0
    AND @get_all_databases = 1
    )
    BEGIN
        RAISERROR('The stored procedure %s does not appear to have any entries in Query Store for database %s
Check that you spelled everything correctly and you''re in the right database
We will skip this database and continue',
                       10, 1, @procedure_name, @database_name) WITH NOWAIT;
        FETCH NEXT
        FROM @database_cursor
        INTO @database_name;

        CONTINUE;
    END;

    IF
    (
        @procedure_exists = 0
    AND @get_all_databases = 0
    )
        BEGIN
            RAISERROR('The stored procedure %s does not appear to have any entries in Query Store for database %s
Check that you spelled everything correctly and you''re in the right database',
                       10, 1, @procedure_name, @database_name) WITH NOWAIT;

        IF @get_all_databases = 0
        BEGIN
            IF @debug = 1
            BEGIN
                GOTO DEBUG;
            END;
            ELSE
            BEGIN
                RETURN;
            END;
        END;
    END;
END; /*End procedure existence checking*/

/*
Some things are version dependent.
Normally, I'd check for object existence, but the documentation
leads me to believe that certain things won't be back-ported,
like the wait stats DMV, and tempdb spills columns
*/
IF
(
   @product_version > 13
OR @engine IN (5, 8)
)
BEGIN
   SELECT
       @new = 1;
END;

/*
See if our cool new 2022 views exist.
May have to tweak this if views aren't present in some cloudy situations.
*/
SELECT
    @sql_2022_views =
        CASE
            WHEN COUNT_BIG(*) = 5
            THEN 1
            ELSE 0
        END
FROM sys.all_objects AS ao
WHERE ao.name IN
      (
          N'query_store_plan_feedback',
          N'query_store_query_hints',
          N'query_store_query_variant',
          N'query_store_replicas',
          N'query_store_plan_forcing_locations'
      )
OPTION(RECOMPILE);

/*
Hints aren't in Query Store until 2022, so we can't do that on television
*/
IF
(
  (
       @only_queries_with_hints    = 1
    OR @only_queries_with_feedback = 1
    OR @only_queries_with_variants = 1
  )
  AND  @sql_2022_views = 0
)
BEGIN
    RAISERROR('Query Store hints, feedback, and variants are not available prior to SQL Server 2022', 10, 1) WITH NOWAIT;

    IF @get_all_databases = 0
    BEGIN
        IF @debug = 1
        BEGIN
            GOTO DEBUG;
        END;
        ELSE
        BEGIN
            RETURN;
        END;
    END;
END;

/*
Wait stats aren't in Query Store until 2017, so we can't do that on television
*/
IF
(
    @wait_filter IS NOT NULL
AND @new = 0
)
BEGIN
    RAISERROR('Query Store wait stats are not available prior to SQL Server 2017', 10, 1) WITH NOWAIT;

    IF @get_all_databases = 0
    BEGIN
        IF @debug = 1
        BEGIN
            GOTO DEBUG;
        END;
        ELSE
        BEGIN
            RETURN;
        END;
    END;
END;

/*
Make sure the wait filter is valid
*/
IF
(
    @new = 1
AND @wait_filter NOT IN
    (
        'cpu',
        'lock',
        'locks',
        'latch',
        'latches',
        'buffer latch',
        'buffer latches',
        'buffer io',
        'log',
        'log io',
        'network',
        'network io',
        'parallel',
        'parallelism',
        'memory'
    )
)
BEGIN
    RAISERROR('The wait category (%s) you chose is invalid', 10, 1, @wait_filter) WITH NOWAIT;

    IF @get_all_databases = 0
    BEGIN
        IF @debug = 1
        BEGIN
            GOTO DEBUG;
        END;
        ELSE
        BEGIN
            RETURN;
        END;
    END;
END;

/*
These columns are only available in 2017+.
This is an instance-level check.
We do it before the database-level checks because the relevant DMVs may not exist on old versions.
@wait_filter has already been checked.
*/
IF
(
  (
      @sort_order = 'tempdb'
   OR @sort_order_is_a_wait = 1
  )
  AND
  (
      @new = 0
  )
)
BEGIN
   RAISERROR('The sort order (%s) you chose is invalid in product version %i, reverting to sorting by cpu.', 10, 1, @sort_order, @product_version) WITH NOWAIT;

   SELECT
       @sort_order = N'cpu',
       @sort_order_is_a_wait = 0;

   DELETE
   FROM @ColumnDefinitions
   WHERE metric_type IN (N'wait_time', N'top waits');

   UPDATE
       @ColumnDefinitions
   SET
       column_source = N'ROW_NUMBER() OVER (PARTITION BY qsrs.plan_id ORDER BY qsrs.avg_cpu_time_ms DESC)'
   WHERE metric_type = N'n';
END;

/*
Wait stat capture can be enabled or disabled in settings.
This is a database-level check.
*/
IF
(
  @new = 1
)
BEGIN
    SELECT
        @current_table = 'checking query store waits are enabled',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN

        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @query_store_waits_enabled =
        CASE
            WHEN EXISTS
                 (
                     SELECT
                         1/0
                     FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
                     WHERE dqso.wait_stats_capture_mode = 1
                 )
            THEN 1
            ELSE 0
        END
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    EXECUTE sys.sp_executesql
        @sql,
      N'@query_store_waits_enabled bit OUTPUT',
        @query_store_waits_enabled OUTPUT;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END;

/*
To avoid mixing sort orders in the @get_all_databases = 1 case, we skip the
database if something wait related is requested on a database that does not capture waits.

There is an edge case.
If you have capturing wait stats disabled, your database can still hold wait stats.
This happens if you turned capturing off after having it on.
We make no attempt to handle this.
Instead, we assume that anyone with capturing wait stats turned off does not want to see them.
*/
IF
(
  (
      @wait_filter IS NOT NULL
   OR @sort_order_is_a_wait = 1
  )
  AND
  (
      @query_store_waits_enabled = 0
  )
)
BEGIN
    IF @get_all_databases = 1
    BEGIN
        RAISERROR('Query Store wait stats are not enabled for database %s, but you have requested them. We are skipping this database and continuing with any that remain.', 10, 1, @database_name_quoted) WITH NOWAIT;
        FETCH NEXT
        FROM @database_cursor
        INTO @database_name;

        CONTINUE;
    END;
    ELSE
    BEGIN
        RAISERROR('Query Store wait stats are not enabled for database %s, but you have requested them. We are reverting to sorting by cpu without respect for any wait filters.', 10, 1, @database_name_quoted) WITH NOWAIT;

        SELECT
            @sort_order = N'cpu',
            @sort_order_is_a_wait = 0,
            @wait_filter = NULL;

        DELETE
        FROM @ColumnDefinitions
        WHERE metric_type IN (N'wait_time');

        UPDATE
            @ColumnDefinitions
        SET
            column_source = N'ROW_NUMBER() OVER (PARTITION BY qsrs.plan_id ORDER BY qsrs.avg_cpu_time_ms DESC)'
        WHERE metric_type = N'n';
    END;
END;

/* There is no reason to show the top_waits column if we know it is NULL. */
IF
(
        @query_store_waits_enabled = 0
    AND @get_all_databases = 0
)
BEGIN
    DELETE
    FROM @ColumnDefinitions
    WHERE metric_type IN (N'top_waits');
END;

/*Check that the selected @timezone is valid*/
IF @timezone IS NOT NULL
BEGIN
    IF NOT EXISTS
       (
           SELECT
               1/0
           FROM sys.time_zone_info AS tzi
           WHERE tzi.name = @timezone
       )
       BEGIN
           RAISERROR('The time zone you chose (%s) is not valid. Please check sys.time_zone_info for a valid list.', 10, 1, @timezone) WITH NOWAIT;
           IF @debug = 1
           BEGIN
               GOTO DEBUG;
           END;
           ELSE
           BEGIN
               RETURN;
           END;
       END;
END;

/*
See if AGs are a thing so we can skip the checks for replica stuff
*/
IF @azure = 1
BEGIN
    SELECT
        @ags_present = 0;
END;
ELSE
BEGIN
    IF
    (
        SELECT
            CONVERT
            (
                sysname,
                SERVERPROPERTY('EngineEdition')
            )
    ) NOT IN (5, 8)
    BEGIN
        SELECT
            @ags_present =
                CASE
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM sys.availability_groups AS ag
                         )
                    THEN 1
                    ELSE 0
                END
        OPTION(RECOMPILE);
    END;
END;

/*
Get filters ready, or whatever
We're only going to pull some stuff from runtime stats and plans
*/
IF @start_date <= @end_date
BEGIN
    SELECT
        @where_clause += N'AND   qsrs.last_execution_time >= @start_date
    AND   qsrs.last_execution_time <  @end_date' + @nc10;
END;

/*Other filters*/
IF @duration_ms IS NOT NULL
BEGIN
    SELECT
        @where_clause += N'    AND   qsrs.avg_duration >= (@duration_ms * 1000.)' + @nc10;
END;

IF @execution_type_desc IS NOT NULL
BEGIN
    SELECT
        @where_clause += N'    AND   qsrs.execution_type_desc = @execution_type_desc' + @nc10;
END;

IF @workdays = 1
BEGIN
    IF  @work_start_utc IS NULL
    AND @work_end_utc   IS NULL
    BEGIN
         SELECT
             @work_start_utc = '09:00',
             @work_end_utc = '17:00';
    END;

    IF  @work_start_utc IS NOT NULL
    AND @work_end_utc   IS NULL
    BEGIN
        SELECT
            @work_end_utc =
                DATEADD
                (
                    HOUR,
                    8,
                    @work_start_utc
                );
    END;

    IF  @work_start_utc IS NULL
    AND @work_end_utc   IS NOT NULL
    BEGIN
        SELECT
            @work_start_utc =
                DATEADD
                (
                    HOUR,
                    -8,
                    @work_end_utc
                );
    END;

    SELECT
        @work_start_utc =
            DATEADD
            (
                MINUTE,
                @utc_minutes_difference,
                @work_start_utc
            ),
        @work_end_utc =
            DATEADD
            (
                MINUTE,
                @utc_minutes_difference,
                @work_end_utc
            );

    IF @df = 1
    BEGIN
       SELECT
           @where_clause += N'AND   DATEPART(WEEKDAY, qsrs.last_execution_time) BETWEEN 1 AND 5' + @nc10;
    END;/*df 1*/
    ELSE IF @df = 7
    BEGIN
       SELECT
           @where_clause += N'AND   DATEPART(WEEKDAY, qsrs.last_execution_time) BETWEEN 2 AND 6' + @nc10;
    END;/*df 7*/
    ELSE
    BEGIN
       RAISERROR('Warning: @workdays filter does not support @@DATEFIRST = %i, weekday filter skipped', 10, 1, @df) WITH NOWAIT;
    END;

    IF  @work_start_utc IS NOT NULL
    AND @work_end_utc IS NOT NULL
    BEGIN
        /*
          depending on local TZ, work time might span midnight UTC;
          account for that by splitting the interval into before/after midnight.
          for example:
              [09:00 - 17:00] PST
           =  [17:00 - 01:00] UTC
           =  [17:00 - 00:00) + [00:00 - 01:00] UTC

          NB: because we don't have the benefit of the context of what day midnight
          is occurring on, we have to rely on the behavior from the documentation of
          the time DT of higher to lower precision resulting in truncation to split
          the interval. i.e. 23:59:59.9999999 -> 23:59:59. which should make that
          value safe to use as the endpoint for our "before midnight" interval.
        */
        IF @work_start_utc < @work_end_utc
        SELECT
            @where_clause += N'AND   CONVERT(time(0), qsrs.last_execution_time) BETWEEN @work_start_utc AND @work_end_utc' + @nc10;
        ELSE
        SELECT
            @where_clause += N'AND
(' + @nc10 +
N'      CONVERT(time(0), qsrs.last_execution_time) BETWEEN @work_start_utc AND ''23:59:59'' ' + @nc10 +
N'   OR CONVERT(time(0), qsrs.last_execution_time) BETWEEN ''00:00:00'' AND @work_end_utc' + @nc10 +
N')' + @nc10;
    END; /*Work hours*/
END; /*Final end*/

/*
In this section we set up the filter if someone's searching for
a single stored procedure in Query Store.
*/
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
)
BEGIN
    SELECT
        @current_table = 'inserting #procedure_plans',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT DISTINCT
    qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
   ON qsq.query_id = qsp.query_id
WHERE ';

IF CHARINDEX(N'%', @procedure_name) = 0
BEGIN
    SELECT
        @sql += N'qsq.object_id = OBJECT_ID(@procedure_name_quoted)';
END;

IF CHARINDEX(N'%', @procedure_name) > 0
BEGIN
    SELECT
        @sql += N'EXISTS
(
     SELECT
         1/0
    FROM #procedure_object_ids AS poi
    WHERE poi.[object_id] = qsq.[object_id]
)';
END;

    SELECT
        @sql += N'
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #procedure_plans
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@procedure_name_quoted sysname',
        @procedure_name_quoted;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'    AND   EXISTS
        (
            SELECT
                1/0
            FROM #procedure_plans AS pp
            WHERE pp.plan_id = qsrs.plan_id
        )'  + @nc10;
END; /*End procedure filter table population*/


/*
In this section we set up the filter if someone's searching for
either ad hoc queries or queries from modules.
*/
IF LEN(@query_type) > 0
BEGIN
    SELECT
        @current_table = 'inserting #query_types',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT DISTINCT
    qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
  ON qsq.query_id = qsp.query_id
WHERE qsq.object_id ' +
CASE
    WHEN LOWER(@query_type) LIKE 'a%'
    THEN N'= 0'
    ELSE N'<> 0'
END
+ N'
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_types
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'    AND   EXISTS
        (
            SELECT
                1/0
            FROM #query_types AS qt
            WHERE qt.plan_id = qsrs.plan_id
        )'  + @nc10;
END; /*End query type filter table population*/


/*
This section filters query or plan ids, both inclusive and exclusive
*/
IF
(
   @include_plan_ids  IS NOT NULL
OR @include_query_ids IS NOT NULL
OR @ignore_plan_ids   IS NOT NULL
OR @ignore_query_ids  IS NOT NULL
OR @include_query_hashes IS NOT NULL
OR @include_plan_hashes  IS NOT NULL
OR @include_sql_handles  IS NOT NULL
OR @ignore_query_hashes  IS NOT NULL
OR @ignore_plan_hashes   IS NOT NULL
OR @ignore_sql_handles   IS NOT NULL
)
BEGIN
    SET @filter_cursor =
        CURSOR
        LOCAL
        FORWARD_ONLY
        STATIC
        READ_ONLY
    FOR
    SELECT
        parameter_name,
        parameter_value,
        temp_table_name,
        column_name,
        data_type,
        is_include,
        requires_secondary_processing
    FROM @FilterParameters AS fp;

    OPEN @filter_cursor;

    FETCH NEXT
    FROM @filter_cursor
    INTO
        @param_name,
        @param_value,
        @temp_table,
        @column_name,
        @data_type,
        @is_include,
        @requires_secondary_processing;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        /* Clean parameter value */
        SELECT
            @param_value =
                REPLACE(REPLACE(REPLACE(REPLACE(
                LTRIM(RTRIM(@param_value)),
                CHAR(10), N''), CHAR(13), N''),
                NCHAR(10), N''), NCHAR(13), N'');

        /* Log current operation if debugging */
        IF @debug = 1
        BEGIN
            RAISERROR('Processing %s with value %s', 0, 0, @param_name, @param_value) WITH NOWAIT;
        END;

        /* Set current table name for troubleshooting */
        SELECT
            @current_table = 'inserting ' + @temp_table;

        /* Choose appropriate string split function based on data type */
        IF @data_type = N'bigint'
        BEGIN
            SELECT
                @split_sql = @string_split_ints;
        END
        ELSE
        BEGIN
            SELECT
                @split_sql = @string_split_strings;
        END;

        /* Execute the initial insert with troubleshooting if enabled */
        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        /* Execute the dynamic SQL to populate the temporary table */
        SET @dynamic_sql = N'
        INSERT INTO
            ' + @temp_table + N'
        WITH
            (TABLOCK)
        (
            ' + @column_name +
      N')
        EXECUTE sys.sp_executesql
            @split_sql,
         N''@ids nvarchar(4000)'',
            @param_value;';

        IF @debug = 1
        BEGIN
            PRINT @dynamic_sql;
        END;

        EXECUTE sys.sp_executesql
            @dynamic_sql,
          N'@split_sql nvarchar(max),
            @param_value nvarchar(4000)',
            @split_sql,
            @param_value;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @split_sql,
                @current_table;
        END;

        /* Secondary processing (for parameters that need to populate plan IDs) */
        IF @requires_secondary_processing = 1
        BEGIN
            SELECT
                @current_table = 'inserting #include_plan_ids for ' + @param_name;

            /* Build appropriate SQL based on parameter type */
            IF @param_name = 'include_query_ids'
            OR @param_name = 'ignore_query_ids'
            BEGIN
                SET @secondary_sql = @isolation_level;

                SELECT @secondary_sql += N'
                SELECT DISTINCT
                    qsp.plan_id
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
                WHERE EXISTS
                      (
                          SELECT
                              1/0
                          FROM #' +
                              CASE
                                  WHEN @is_include = 1
                                  THEN N'include'
                                  ELSE N'ignore'
                              END +
                              N'_query_ids AS iqi
                          WHERE iqi.query_id = qsp.query_id
                      )
                OPTION(RECOMPILE);' + @nc10;
            END;
            ELSE

            IF @param_name = 'include_query_hashes'
            OR @param_name = 'ignore_query_hashes'
            BEGIN
                SET @secondary_sql = @isolation_level;

                SELECT @secondary_sql += N'
                SELECT DISTINCT
                    qsp.plan_id
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
                WHERE EXISTS
                      (
                          SELECT
                              1/0
                          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                          WHERE qsq.query_id = qsp.query_id
                          AND   EXISTS
                                (
                                    SELECT
                                        1/0
                                    FROM #' +
                                        CASE
                                            WHEN @is_include = 1
                                            THEN N'include'
                                            ELSE N'ignore'
                                         END +
                                         N'_query_hashes AS iqh
                                    WHERE iqh.query_hash = qsq.query_hash
                                )
                      )
                OPTION(RECOMPILE);' + @nc10;
            END;
            ELSE

            IF @param_name = 'include_plan_hashes'
            OR @param_name = 'ignore_plan_hashes'
            BEGIN
                SET @secondary_sql = @isolation_level;

                SELECT @secondary_sql += N'
                SELECT DISTINCT
                    qsp.plan_id
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
                WHERE EXISTS
                      (
                          SELECT
                              1/0
                          FROM #' +
                              CASE
                                  WHEN @is_include = 1
                                  THEN N'include'
                                  ELSE N'ignore'
                              END + N'_plan_hashes AS iph
                          WHERE iph.plan_hash = qsp.query_plan_hash
                      )
                OPTION(RECOMPILE);' + @nc10;
            END;
            ELSE

            IF @param_name = 'include_sql_handles'
            OR @param_name = 'ignore_sql_handles'
            BEGIN
                SET @secondary_sql = @isolation_level;

                SELECT @secondary_sql += N'
                SELECT DISTINCT
                    qsp.plan_id
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
                WHERE EXISTS
                      (
                          SELECT
                              1/0
                          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
                          WHERE qsp.query_id = qsq.query_id
                          AND EXISTS
                              (
                                  SELECT
                                      1/0
                                  FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
                                  WHERE qsqt.query_text_id = qsq.query_text_id
                                  AND   EXISTS
                                        (
                                            SELECT
                                                1/0
                                            FROM #' +
                                                CASE
                                                    WHEN @is_include = 1
                                                    THEN N'include'
                                                    ELSE N'ignore'
                                                END + N'_sql_handles AS ish
                                            WHERE ish.sql_handle = qsqt.statement_sql_handle
                                        )
                              )
                      )
                OPTION(RECOMPILE);' + @nc10;
            END;

            /* Process secondary sql if defined */
            IF @secondary_sql IS NOT NULL
            BEGIN
                IF @troubleshoot_performance = 1
                BEGIN
                    EXECUTE sys.sp_executesql
                        @troubleshoot_insert,
                      N'@current_table nvarchar(100)',
                        @current_table;

                    SET STATISTICS XML ON;
                END;

                IF @debug = 1
                BEGIN
                    PRINT @secondary_sql;
                END;

                /* Insert into the correct target table based on include/ignore */
                IF @is_include = 1
                BEGIN
                    INSERT INTO
                        #include_plan_ids
                    WITH
                        (TABLOCK)
                    (
                        plan_id
                    )
                    EXECUTE sys.sp_executesql
                        @secondary_sql;
                END
                ELSE
                BEGIN
                    INSERT INTO
                        #ignore_plan_ids
                    WITH
                        (TABLOCK)
                    (
                        plan_id
                    )
                    EXECUTE sys.sp_executesql
                        @secondary_sql;
                END;

                IF @troubleshoot_performance = 1
                BEGIN
                    SET STATISTICS XML OFF;

                    EXECUTE sys.sp_executesql
                        @troubleshoot_update,
                      N'@current_table nvarchar(100)',
                        @current_table;

                    EXECUTE sys.sp_executesql
                        @troubleshoot_info,
                      N'@sql nvarchar(max), @current_table nvarchar(100)',
                        @secondary_sql,
                        @current_table;
                END;
            END;
        END;

        /* Update where clause based on parameter type */
        IF @param_name = 'include_plan_ids'
        OR @param_name = 'ignore_plan_ids'
        OR @requires_secondary_processing = 1
        BEGIN
            /* Choose the correct table and exists/not exists operator */
            SELECT
                @temp_target_table =
                    CASE
                        WHEN @is_include = 1
                        THEN N'#include_plan_ids'
                        ELSE N'#ignore_plan_ids'
                    END,
                @exist_or_not_exist =
                    CASE
                        WHEN @is_include = 1
                        THEN N'EXISTS'
                        ELSE N'NOT EXISTS'
                    END;

            /* Add the filter condition to the where clause */
            SELECT
                @where_clause +=
                N'AND   ' +
                @exist_or_not_exist +
                N'
              (
                 SELECT
                    1/0
                 FROM ' + @temp_target_table + N' AS idi
                 WHERE idi.plan_id = qsrs.plan_id
              )' + @nc10;

              IF @debug = 1
              BEGIN
                  PRINT @where_clause;
              END;
        END;

        FETCH NEXT
        FROM @filter_cursor
        INTO
            @param_name,
            @param_value,
            @temp_table,
            @column_name,
            @data_type,
            @is_include,
            @requires_secondary_processing;
    END;
END; /*End hash and handle filtering*/

IF @sql_2022_views = 1
BEGIN
    IF @only_queries_with_hints = 1
    BEGIN
        SELECT
            @current_table = 'inserting #only_queries_with_hints',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_query_hints AS qsqh
              WHERE qsqh.query_id = qsp.query_id
          )';

        SELECT
            @sql += N'
    OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #only_queries_with_hints
        WITH
            (TABLOCK)
        (
            plan_id
        )
        EXECUTE sys.sp_executesql
            @sql;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;

        SELECT
            @where_clause += N'    AND   EXISTS
           (
               SELECT
                   1/0
               FROM #only_queries_with_hints AS qst
               WHERE qst.plan_id = qsrs.plan_id
           )' + @nc10;
    END;

    IF @only_queries_with_feedback = 1
    BEGIN
        SELECT
            @current_table = 'inserting #only_queries_with_feedback',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_plan_feedback AS qsqf
              WHERE qsqf.plan_id = qsp.plan_id
          )';

        SELECT
            @sql += N'
    OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #only_queries_with_feedback
        WITH
            (TABLOCK)
        (
            plan_id
        )
        EXECUTE sys.sp_executesql
            @sql;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;

        SELECT
            @where_clause += N'    AND   EXISTS
           (
               SELECT
                   1/0
               FROM #only_queries_with_feedback AS qst
               WHERE qst.plan_id = qsrs.plan_id
           )' + @nc10;
    END;

    IF @only_queries_with_variants = 1
    BEGIN
        SELECT
            @current_table = 'inserting #only_queries_with_variants',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
    SELECT DISTINCT
        qsp.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM ' + @database_name_quoted + N'.sys.query_store_query_variant AS qsqv
              WHERE qsqv.query_variant_query_id = qsp.query_id
          )';

        SELECT
            @sql += N'
    OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #only_queries_with_variants
        WITH
            (TABLOCK)
        (
            plan_id
        )
        EXECUTE sys.sp_executesql
            @sql;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;

        SELECT
            @where_clause += N'    AND   EXISTS
           (
               SELECT
                   1/0
               FROM #only_queries_with_variants AS qst
               WHERE qst.plan_id = qsrs.plan_id
           )' + @nc10;
    END;
END;

IF
(
     @only_queries_with_forced_plans = 1
  OR @only_queries_with_forced_plan_failures = 1
)
BEGIN
    SELECT
        @current_table = 'inserting #forced_plans_failures',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT DISTINCT
    qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
WHERE qsp.is_forced_plan = 1';

IF @only_queries_with_forced_plan_failures = 1
BEGIN
    SELECT
        @sql += N'
AND   qsp.last_force_failure_reason > 0';
END;

    SELECT
        @sql += N'
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #forced_plans_failures
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'    AND   EXISTS
       (
           SELECT
               1/0
           FROM #forced_plans_failures AS fpf
           WHERE fpf.plan_id = qsrs.plan_id
       )' + @nc10;
END;

IF @query_text_search IS NOT NULL
BEGIN
    IF
    (
        LEFT
        (
            @query_text_search,
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search =
                N'%' + @query_text_search;
    END;

    IF
    (
        LEFT
        (
            REVERSE
            (
                @query_text_search
            ),
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search =
                @query_text_search + N'%';
    END;

    /* If our query texts contains square brackets (common in Entity Framework queries), add a leading escape character to each bracket character */
    IF @escape_brackets = 1
    BEGIN
        SELECT
            @query_text_search =
                REPLACE(REPLACE(REPLACE(
                    @query_text_search,
                N'[', @escape_character + N'['),
                N']', @escape_character + N']'),
                N'_', @escape_character + N'_');
    END;

    SELECT
        @current_table = 'inserting #query_text_search',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT DISTINCT
    qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
WHERE EXISTS
      (
          SELECT
              1/0
          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
          WHERE qsp.query_id = qsq.query_id
          AND EXISTS
              (
                  SELECT
                      1/0
                  FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
                  WHERE qsqt.query_text_id = qsq.query_text_id
                  AND   qsqt.query_sql_text LIKE @query_text_search
              )
      )';

    /* If we are escaping bracket character in our query text search, add the ESCAPE clause and character to the LIKE subquery*/
    IF @escape_brackets = 1
    BEGIN
        SELECT
            @sql =
                REPLACE
                (
                    @sql,
                    N'@query_text_search',
                    N'@query_text_search ESCAPE ''' + @escape_character + N''''
                );
    END;

/*If we're searching by a procedure name, limit the text search to it */
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
)
BEGIN
    SELECT
        @sql += N'
    AND   EXISTS
          (
              SELECT
                  1/0
              FROM #procedure_plans AS pp
              WHERE pp.plan_id = qsp.plan_id
          )';
END;

    SELECT
        @sql += N'
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_text_search
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@query_text_search nvarchar(4000)',
        @query_text_search;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'    AND   EXISTS
       (
           SELECT
               1/0
           FROM #query_text_search AS qst
           WHERE qst.plan_id = qsrs.plan_id
       )' + @nc10;
END;

IF @query_text_search_not IS NOT NULL
BEGIN
    IF
    (
        LEFT
        (
            @query_text_search_not,
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search_not =
                N'%' + @query_text_search_not;
    END;

    IF
    (
        LEFT
        (
            REVERSE
            (
                @query_text_search_not
            ),
            1
        ) <> N'%'
    )
    BEGIN
        SELECT
            @query_text_search_not =
                @query_text_search_not + N'%';
    END;

    /* If our query texts contains square brackets (common in Entity Framework queries), add a leading escape character to each bracket character */
    IF @escape_brackets = 1
    BEGIN
        SELECT
            @query_text_search_not =
                REPLACE(REPLACE(REPLACE(
                    @query_text_search_not,
                N'[', @escape_character + N'['),
                N']', @escape_character + N']'),
                N'_', @escape_character + N'_');
    END;

    SELECT
        @current_table = 'inserting #query_text_search_not',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT DISTINCT
    qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
WHERE EXISTS
      (
          SELECT
              1/0
          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
          WHERE qsp.query_id = qsq.query_id
          AND EXISTS
              (
                  SELECT
                      1/0
                  FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
                  WHERE qsqt.query_text_id = qsq.query_text_id
                  AND   qsqt.query_sql_text LIKE @query_text_search_not
              )
      )';

    /* If we are escaping bracket character in our query text search, add the ESCAPE clause and character to the LIKE subquery*/
    IF @escape_brackets = 1
    BEGIN
        SELECT
            @sql =
                REPLACE
                (
                    @sql,
                    N'@query_text_search_not',
                    N'@query_text_search_not ESCAPE ''' + @escape_character + N''''
                );
    END;

/*If we're searching by a procedure name, limit the text search to it */
IF
(
    @procedure_name IS NOT NULL
AND @procedure_exists = 1
)
BEGIN
    SELECT
        @sql += N'
    AND   EXISTS
          (
              SELECT
                  1/0
              FROM #procedure_plans AS pp
              WHERE pp.plan_id = qsp.plan_id
          )';
END;

    SELECT
        @sql += N'
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_text_search_not
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@query_text_search_not nvarchar(4000)',
        @query_text_search_not;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'    AND   NOT EXISTS
       (
           SELECT
               1/0
           FROM #query_text_search_not AS qst
           WHERE qst.plan_id = qsrs.plan_id
       )' + @nc10;
END;

/*
Validate wait stats stuff
*/
IF @wait_filter IS NOT NULL
BEGIN
    BEGIN
        SELECT
            @current_table = 'inserting #wait_filter',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
SELECT TOP (@top)
    qsws.plan_id
FROM  ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws
WHERE 1 = 1
AND   qsws.wait_category = ' +
CASE @wait_filter
     WHEN 'cpu' THEN N'1'
     WHEN 'lock' THEN N'3'
     WHEN 'locks' THEN N'3'
     WHEN 'latch' THEN N'4'
     WHEN 'latches' THEN N'4'
     WHEN 'buffer latch' THEN N'5'
     WHEN 'buffer latches' THEN N'5'
     WHEN 'buffer io' THEN N'6'
     WHEN 'log' THEN N'14'
     WHEN 'log io' THEN N'14'
     WHEN 'network' THEN N'15'
     WHEN 'network io' THEN N'15'
     WHEN 'parallel' THEN N'16'
     WHEN 'parallelism' THEN N'16'
     WHEN 'memory' THEN N'17'
END
+ N'
GROUP BY
    qsws.plan_id
HAVING
    SUM(qsws.avg_query_wait_time_ms) > 0
ORDER BY
    SUM(qsws.avg_query_wait_time_ms) DESC
OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;
    END;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #wait_filter
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@top bigint',
        @top;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @where_clause += N'AND   EXISTS
       (
           SELECT
               1/0
           FROM #wait_filter AS wf
           WHERE wf.plan_id = qsrs.plan_id
       )' + @nc10;
END;

/*
This section screens out index create and alter statements because who cares
*/
IF @include_maintenance = 0
BEGIN
SELECT
    @current_table = 'inserting #maintenance_plans',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT DISTINCT
   qsp.plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
WHERE NOT EXISTS
      (
          SELECT
             1/0
          FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
          JOIN ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
            ON qsqt.query_text_id = qsq.query_text_id
          WHERE qsq.query_id = qsp.query_id
          AND   qsqt.query_sql_text NOT LIKE N''ALTER INDEX%''
          AND   qsqt.query_sql_text NOT LIKE N''ALTER TABLE%''
          AND   qsqt.query_sql_text NOT LIKE N''CREATE%INDEX%''
          AND   qsqt.query_sql_text NOT LIKE N''CREATE STATISTICS%''
          AND   qsqt.query_sql_text NOT LIKE N''UPDATE STATISTICS%''
          AND   qsqt.query_sql_text NOT LIKE N''%SELECT StatMan%''
          AND   qsqt.query_sql_text NOT LIKE N''DBCC%''
          AND   qsqt.query_sql_text NOT LIKE N''(@[_]msparam%''
          AND   qsqt.query_sql_text NOT LIKE N''WAITFOR%''
      )
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #maintenance_plans
WITH
    (TABLOCK)
(
    plan_id
)
EXECUTE sys.sp_executesql
    @sql;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END;

SELECT
    @where_clause += N'    AND   NOT EXISTS
      (
          SELECT
              1/0
          FROM #maintenance_plans AS mp
          WHERE mp.plan_id = qsrs.plan_id
      )' + @nc10;
END;

/*
Filtering by @execution_count is non-trivial.
In the Query Store DMVs, execution counts only exist in
sys.query_store_runtime_stats.
That DMV has no query_id column (or anything similar),
but we promised that @execution_count would filter by the
number of executions of the query.
The best column for us in the DMV is plan_id, so we need
to get from there to query_id.
Because we do most of our filtering work in #distinct_plans,
we must also make what we do here compatible with that.

In conclusion, we want produce a temp table holding the
plan_ids for the queries with @execution_count or more executions.

This is similar to the sort-helping tables that you are
about to see, but easier because we do not need to return or sort
by the execution count.
We just need to know that these plans have enough executions.
*/
IF @execution_count > 0
BEGIN
    SELECT
        @current_table = 'inserting #plan_ids_having_enough_executions',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT DISTINCT
        unfiltered_execution_counts.plan_id
    FROM
    (
       SELECT
           qsp.plan_id,
           total_executions_for_query_of_plan =
               SUM(qsrs.count_executions) OVER (PARTITION BY qsq.query_id)
       FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
       JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
         ON qsq.query_id = qsp.query_id
       JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
         ON qsp.plan_id = qsrs.plan_id
       WHERE 1 = 1
       ' + @where_clause
         + N'
    ) AS unfiltered_execution_counts
    WHERE
        unfiltered_execution_counts.total_executions_for_query_of_plan >= @execution_count
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #plan_ids_having_enough_executions
    WITH
        (TABLOCK)
    (
        plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

SELECT
    @where_clause += N'    AND EXISTS
    (
        SELECT
            1/0
        FROM #plan_ids_having_enough_executions AS enough_executions
        WHERE enough_executions.plan_id = qsrs.plan_id
    )' + @nc10;
END;

/*
Tidy up the where clause a bit
*/
SELECT
    @where_clause =
        SUBSTRING
        (
            @where_clause,
            1,
            LEN(@where_clause) - 1
        );

/*
Regression mode differs significantly from our defaults.
In this mode, we measure every query hash in the time period
specified by @regression_baseline_start_date and
@regression_baseline_end_date ("the baseline time period").
Our measurements are taken based on the metric given
by @sort_order.
For all of the hashes we have taken measurements for, we
make the same measurement for the time period specified
by @start_date and @end_date ("the current time period").
We then compare each hashes' measurement across the two
time periods, by the means specified by
@regression_comparator and take the @top results ordered by
@regression_direction.
We then get every plan_id in both time periods for those
query hashes and carry on as normal.

This gives us three immediate concerns. We:
   1) Need to adjust our @where_clause to refer to the
      baseline time period.
   2) Need all of the queries from the baseline time
      period (rather than just the @top whatever).
   3) Are interested in the query hashes rather than
      just plan_ids.

We address part of the first concern immediately.
Later, we will do some wicked and foul things to
modify our dynamic SQL's usages of @where_clause
to use @regression_where_clause.
*/
IF @regression_mode = 1
BEGIN

SELECT
    @regression_where_clause =
        REPLACE
        (
            REPLACE
            (
                @where_clause,
                '@start_date',
                '@regression_baseline_start_date'
            ),
           '@end_date',
           '@regression_baseline_end_date'
        );
END;

/*
Populate sort-helping tables, if needed.

In theory, these exist just to put in scope
columns that wouldn't normally be in scope.
However, they're also quite helpful for the next
temp table, #distinct_plans.

Note that this block must come after we are done with
anything that edits @where_clause because we want to use
that here.

Regression mode complicates this process considerably.
It forces us to use different dates.
We also have to adjust @top.

Luckily, the 'plan count by hashes' sort
order is not supported in regression mode.
Earlier on, we throw an error if somebody
tries (it just doesn't make sense).
*/
IF @sort_order = 'plan count by hashes'
BEGIN
    SELECT
        @current_table = 'inserting #plan_ids_with_query_hashes',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
    /*
    This sort order is useless if we don't show the
    ties, so only DENSE_RANK() makes sense to use.
    This is why this is not SELECT TOP.
    */
        @sql += N'
    SELECT
        @database_id,
        ranked_plans.plan_id,
        ranked_plans.query_hash,
        ranked_plans.plan_hash_count_for_query_hash
    FROM
    (
        SELECT
            QueryHashesWithIds.plan_id,
            QueryHashesWithCounts.query_hash,
            QueryHashesWithCounts.plan_hash_count_for_query_hash,
            ranking =
                DENSE_RANK() OVER
                (
                    ORDER BY
                        QueryHashesWithCounts.plan_hash_count_for_query_hash DESC,
                        QueryHashesWithCounts.query_hash DESC
                )
        FROM
        (
           SELECT
               qsq.query_hash,
               plan_hash_count_for_query_hash =
                   COUNT_BIG(DISTINCT qsp.query_plan_hash)
           FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
           JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
             ON qsq.query_id = qsp.query_id
           JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
             ON qsp.plan_id = qsrs.plan_id
           WHERE 1 = 1
           ' + @where_clause
             + N'
           GROUP
               BY qsq.query_hash
        ) AS QueryHashesWithCounts
        JOIN
        (
           SELECT DISTINCT
               qsq.query_hash,
               qsp.plan_id
           FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
           JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
              ON qsq.query_id = qsp.query_id
           JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
             ON qsp.plan_id = qsrs.plan_id
            WHERE 1 = 1
           ' + @where_clause
             + N'
        ) AS QueryHashesWithIds
          ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash
    ) AS ranked_plans
    WHERE ranked_plans.ranking <= @top
    OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #plan_ids_with_query_hashes
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        query_hash,
        plan_hash_count_for_query_hash
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END;
IF @sort_order = 'total waits'
BEGIN
    SELECT
        @current_table = 'inserting #plan_ids_with_total_waits',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT TOP (@top)
        @database_id,
        qsrs.plan_id,
        from_regression_baseline =
            CASE
                WHEN qsrs.last_execution_time >= @start_date
                AND  qsrs.last_execution_time < @end_date
                THEN ''No''
                ELSE ''Yes''
            END,
        total_query_wait_time_ms =
            SUM(qsws.total_query_wait_time_ms)
    FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
    JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws
      ON qsrs.plan_id = qsws.plan_id
    WHERE 1 = 1
    '
   + CASE WHEN @regression_mode = 1
      THEN N' AND ( 1 = 1
      ' + @regression_where_clause
      + N' )
OR
      ( 1 = 1
      '
      + @where_clause
      + N' ) '
      ELSE @where_clause
      END
      + N'
    GROUP
        BY qsrs.plan_id,
        CASE
            WHEN qsrs.last_execution_time >= @start_date
            AND  qsrs.last_execution_time < @end_date
            THEN ''No''
            ELSE ''Yes''
        END
    ORDER BY
        SUM(qsws.total_query_wait_time_ms) DESC
    OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;

    IF @regression_mode = 1
    BEGIN

        /* Very stupid way to stop us repeating the above code. */
        SELECT
           @sql = REPLACE
                   (
                       @sql,
                       'TOP (@top)',
                       'TOP (2147483647 + (0 * @top))'
                   );
    END;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #plan_ids_with_total_waits
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        from_regression_baseline,
        total_query_wait_time_ms
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END;
/*
'total waits' is special. It's a sum, not a max, so
we cover it above rather than here.
*/

IF  @sort_order_is_a_wait = 1
AND @sort_order <> 'total waits'
BEGIN
    SELECT
        @current_table = 'inserting #plan_ids_with_total_waits',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT TOP (@top)
        @database_id,
        qsrs.plan_id,
        from_regression_baseline =
            CASE
                WHEN qsrs.last_execution_time >= @start_date
                AND   qsrs.last_execution_time < @end_date
                THEN ''No''
                ELSE ''Yes''
            END,
        total_query_wait_time_ms =
            MAX(qsws.total_query_wait_time_ms)
    FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
    JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws
      ON qsrs.plan_id = qsws.plan_id
    WHERE 1 = 1
    AND qsws.wait_category = '  +
    CASE @sort_order
         WHEN 'cpu waits' THEN N'1'
         WHEN 'lock waits' THEN N'3'
         WHEN 'locks waits' THEN N'3'
         WHEN 'latch waits' THEN N'4'
         WHEN 'latches waits' THEN N'4'
         WHEN 'buffer latch waits' THEN N'5'
         WHEN 'buffer latches waits' THEN N'5'
         WHEN 'buffer io waits' THEN N'6'
         WHEN 'log waits' THEN N'14'
         WHEN 'log io waits' THEN N'14'
         WHEN 'network waits' THEN N'15'
         WHEN 'network io waits' THEN N'15'
         WHEN 'parallel waits' THEN N'16'
         WHEN 'parallelism waits' THEN N'16'
         WHEN 'memory waits' THEN N'17'
    END
      + N'
      '
      + CASE WHEN @regression_mode = 1
         THEN N' AND ( 1 = 1
         ' + @regression_where_clause
         + N' )
   OR
         ( 1 = 1
         '
         + @where_clause
         + N' ) '
         ELSE @where_clause
         END
      + N'
    GROUP
        BY qsrs.plan_id,
        CASE
            WHEN qsrs.last_execution_time >= @start_date
            AND  qsrs.last_execution_time < @end_date
            THEN ''No''
            ELSE ''Yes''
        END
    ORDER BY
        MAX(qsws.total_query_wait_time_ms) DESC
    OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;

    IF @regression_mode = 1
    BEGIN

        /* Very stupid way to stop us repeating the above code. */
        SELECT
           @sql = REPLACE
                   (
                       @sql,
                       'TOP (@top)',
                       'TOP (2147483647 + (0 * @top))'
                   );
    END;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #plan_ids_with_total_waits
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        from_regression_baseline,
        total_query_wait_time_ms
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END;
/*End populating sort-helping tables*/

/*
This is where the bulk of the regression mode
work is done. We grab the metrics for both time
periods for each query hash, compare them,
and get the @top.
*/
IF @regression_mode = 1
BEGIN
    /*
    We begin by getting the metrics per query hash
    in the time period.
    */
    SELECT
        @current_table = 'inserting #regression_baseline_runtime_stats',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT
        qsq.query_hash,
        /* All of these but count_executions are already floats. */
        regression_metric_average =
            CONVERT
            (
                float,
                ' +
                CASE @sort_order
                     WHEN 'cpu' THEN N'AVG(qsrs.avg_cpu_time)'
                     WHEN 'logical reads' THEN N'AVG(qsrs.avg_logical_io_reads)'
                     WHEN 'physical reads' THEN N'AVG(qsrs.avg_physical_io_reads)'
                     WHEN 'writes' THEN N'AVG(qsrs.avg_logical_io_writes)'
                     WHEN 'duration' THEN N'AVG(qsrs.avg_duration)'
                     WHEN 'memory' THEN N'AVG(qsrs.avg_query_max_used_memory)'
                     WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'AVG(qsrs.avg_tempdb_space_used)' ELSE N'AVG(qsrs.avg_cpu_time)' END
                     WHEN 'executions' THEN N'AVG(qsrs.count_executions)'
                     WHEN 'rows' THEN N'AVG(qsrs.avg_rowcount)'
                     WHEN 'total cpu' THEN N'SUM(qsrs.avg_cpu_time * qsrs.count_executions)'
                     WHEN 'total logical reads' THEN N'SUM(qsrs.avg_logical_io_reads * qsrs.count_executions)'
                     WHEN 'total physical reads' THEN N'SUM(qsrs.avg_physical_io_reads * qsrs.count_executions)'
                     WHEN 'total writes' THEN N'SUM(qsrs.avg_logical_io_writes * qsrs.count_executions)'
                     WHEN 'total duration' THEN N'SUM(qsrs.avg_duration * qsrs.count_executions)'
                     WHEN 'total memory' THEN N'SUM(qsrs.avg_query_max_used_memory * qsrs.count_executions)'
                     WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'SUM(qsrs.avg_tempdb_space_used * qsrs.count_executions)' ELSE N'SUM(qsrs.avg_cpu_time * qsrs.count_executions)' END
                     WHEN 'total rows' THEN N'SUM(qsrs.avg_rowcount * qsrs.count_executions)'
                     ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'AVG(waits.total_query_wait_time_ms)' ELSE N'AVG(qsrs.avg_cpu_time)' END
                END
                + N'
            )
    FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
    JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
      ON qsq.query_id = qsp.query_id
    JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
      ON qsp.plan_id = qsrs.plan_id
    LEFT JOIN #plan_ids_with_total_waits AS waits
      ON  qsp.plan_id = waits.plan_id
      AND waits.from_regression_baseline = ''Yes''
    WHERE 1 = 1
    ' + @regression_where_clause
      + N'
    GROUP
        BY qsq.query_hash
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #regression_baseline_runtime_stats
    WITH
        (TABLOCK)
    (
        query_hash,
        regression_metric_average
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    /*
    We now take the same measurement for all of the same query hashes,
    but in the @where_clause time period.
    */
    SELECT
        @current_table = 'inserting #regression_current_runtime_stats',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT
        qsq.query_hash,
        /* All of these but count_executions are already floats. */
        current_metric_average =
            CONVERT
            (
                float,
                ' +
                CASE @sort_order
                     WHEN 'cpu' THEN N'AVG(qsrs.avg_cpu_time)'
                     WHEN 'logical reads' THEN N'AVG(qsrs.avg_logical_io_reads)'
                     WHEN 'physical reads' THEN N'AVG(qsrs.avg_physical_io_reads)'
                     WHEN 'writes' THEN N'AVG(qsrs.avg_logical_io_writes)'
                     WHEN 'duration' THEN N'AVG(qsrs.avg_duration)'
                     WHEN 'memory' THEN N'AVG(qsrs.avg_query_max_used_memory)'
                     WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'AVG(qsrs.avg_tempdb_space_used)' ELSE N'AVG(qsrs.avg_cpu_time)' END
                     WHEN 'executions' THEN N'AVG(qsrs.count_executions)'
                     WHEN 'rows' THEN N'AVG(qsrs.avg_rowcount)'
                     WHEN 'total cpu' THEN N'SUM(qsrs.avg_cpu_time * qsrs.count_executions)'
                     WHEN 'total logical reads' THEN N'SUM(qsrs.avg_logical_io_reads * qsrs.count_executions)'
                     WHEN 'total physical reads' THEN N'SUM(qsrs.avg_physical_io_reads * qsrs.count_executions)'
                     WHEN 'total writes' THEN N'SUM(qsrs.avg_logical_io_writes * qsrs.count_executions)'
                     WHEN 'total duration' THEN N'SUM(qsrs.avg_duration * qsrs.count_executions)'
                     WHEN 'total memory' THEN N'SUM(qsrs.avg_query_max_used_memory * qsrs.count_executions)'
                     WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'SUM(qsrs.avg_tempdb_space_used * qsrs.count_executions)' ELSE N'SUM(qsrs.avg_cpu_time * qsrs.count_executions)' END
                     WHEN 'total rows' THEN N'SUM(qsrs.avg_rowcount * qsrs.count_executions)'
                     ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'AVG(waits.total_query_wait_time_ms)' ELSE N'AVG(qsrs.avg_cpu_time)' END
                END
                + N'
            )
    FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
    JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
      ON qsq.query_id = qsp.query_id
    JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
      ON qsp.plan_id = qsrs.plan_id
    LEFT JOIN #plan_ids_with_total_waits AS waits
      ON  qsp.plan_id = waits.plan_id
      AND waits.from_regression_baseline = ''No''
    WHERE 1 = 1
    AND EXISTS
    (
        SELECT
            1/0
        FROM #regression_baseline_runtime_stats AS base
        WHERE base.query_hash = qsq.query_hash
    )
    ' + @where_clause
      + N'
    GROUP
        BY qsq.query_hash
    OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #regression_current_runtime_stats
    WITH
        (TABLOCK)
    (
        query_hash,
        current_metric_average
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    SELECT
        @current_table = 'inserting #regression_changes',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    /*
    Now that we have the data from
    both time periods, we must
    compare them as @regression_comparator
    demands and order them as
    @regression_direction demands.

    However, we care about query_hashes
    here despite everything after this
    wanting plan_ids. This means we
    must repeat some of the tricks
    we used for #plan_ids_with_query_hashes.
    */
    SELECT
        @sql += N'
    SELECT
        @database_id,
        plans_for_hashes.plan_id,
        hashes_with_changes.query_hash,
        change_since_regression_time_period =
        ' +
        /*
        If we are returning differences that are not percentages,
        then we need the units we show for any given metric to be
        the same as anywhere else that gives the same metric.
        If we do not, then our final output will look wrong.
        For example, our CPU time will be 1,000 times bigger
        here than it is in any other column.
        To avoid this problem, we need to replicate the calculations
        later used to populate #query_store_runtime_stats.
        */
        CASE @regression_comparator
            WHEN 'absolute' THEN
                CASE @sort_order
                     WHEN 'cpu' THEN N'hashes_with_changes.change_since_regression_time_period / 1000.'
                     WHEN 'logical reads' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'physical reads' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'writes' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'duration' THEN N'hashes_with_changes.change_since_regression_time_period / 1000.'
                     WHEN 'memory' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.' ELSE N'hashes_with_changes.change_since_regression_time_period / 1000.' END
                     WHEN 'executions' THEN N'hashes_with_changes.change_since_regression_time_period'
                     WHEN 'rows' THEN N'hashes_with_changes.change_since_regression_time_period'
                     WHEN 'total cpu' THEN N'hashes_with_changes.change_since_regression_time_period / 1000.'
                     WHEN 'total logical reads' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'total physical reads' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'total writes' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'total duration' THEN N'hashes_with_changes.change_since_regression_time_period / 1000.'
                     WHEN 'total memory' THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.'
                     WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'(hashes_with_changes.change_since_regression_time_period * 8.) / 1024.' ELSE N'hashes_with_changes.change_since_regression_time_period / 1000.' END
                     WHEN 'total rows' THEN N'hashes_with_changes.change_since_regression_time_period'
                     ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'hashes_with_changes.change_since_regression_time_period / 1000.' ELSE N'hashes_with_changes.change_since_regression_time_period / 1000.' END
                END
            ELSE N'hashes_with_changes.change_since_regression_time_period' END
        + N'
    FROM
    (
        SELECT TOP (@top)
            compared_stats.query_hash,
            compared_stats.change_since_regression_time_period
        FROM
        (
            SELECT
                current_stats.query_hash,
                change_since_regression_time_period =
                '
                + CASE @regression_comparator
                      WHEN 'relative' THEN N'((current_stats.current_metric_average / NULLIF(baseline.regression_metric_average, 0.0)) - 1.0)'
                      WHEN 'absolute' THEN N'(current_stats.current_metric_average - baseline.regression_metric_average)'
                  END
                + N'
            FROM #regression_current_runtime_stats AS current_stats
            JOIN #regression_baseline_runtime_stats AS baseline
              ON current_stats.query_hash = baseline.query_hash
        ) AS compared_stats
        ORDER BY
            '
            /*
            Current metrics that are better than that of the baseline period,
            will give change_since_regression_time_period values that
            are smaller than metrics that are worse.
            In other words, ORDER BY change_since_regression_time_period DESC
            gives us the regressed queries first.
            This is true regardless of @regression_comparator.
            To make @regression_direction behave as intended, we
            need to account for this. We could use dynamic SQL,
            but mathematics has given us better tools.
            */
            + CASE @regression_direction
                   WHEN 'regressed' THEN N'change_since_regression_time_period'
                   WHEN 'worse' THEN N'change_since_regression_time_period'
                   WHEN 'improved' THEN N'change_since_regression_time_period * -1.0'
                   WHEN 'better' THEN N'change_since_regression_time_period * -1.0'
                   /*
                   The following two branches cannot be hit if
                   @regression_comparator is 'relative'.
                   We have made errors be thrown if somebody tries
                   to mix the two.
                   If you can figure out a way to make the two make
                   sense together, then feel free to add it in.
                   */
                   WHEN 'magnitude' THEN N'ABS(change_since_regression_time_period)'
                   WHEN 'absolute' THEN N'ABS(change_since_regression_time_period)'
              END
            + N' DESC
    ) AS hashes_with_changes
    JOIN
    (
       SELECT DISTINCT
           qsq.query_hash,
           qsp.plan_id
       FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
       JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
         ON qsq.query_id = qsp.query_id
       JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
         ON qsp.plan_id = qsrs.plan_id
        WHERE
            ( 1 = 1
            '
            /* We want each time period's plan_ids for these query hashes. */
            + @regression_where_clause
            + N'
            )
        OR
            ( 1 = 1
            '
            + @where_clause
         + N'
            )
    ) AS plans_for_hashes
      ON hashes_with_changes.query_hash = plans_for_hashes.query_hash
    OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #regression_changes
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        query_hash,
        change_since_regression_time_period
    )
    EXECUTE sys.sp_executesql
        @sql,
        @parameters,
        @top,
        @start_date,
        @end_date,
        @execution_count,
        @duration_ms,
        @execution_type_desc,
        @database_id,
        @queries_top,
        @work_start_utc,
        @work_end_utc,
        @regression_baseline_start_date,
        @regression_baseline_end_date;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

END;

/*
This gets the plan_ids we care about.

We unfortunately need an ELSE IF chain here
because the final branch contains defaults
that we only want to hit if we did not hit
any others.
*/
SELECT
    @current_table = 'inserting #distinct_plans',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

IF @regression_mode = 1
BEGIN
    SELECT
        @sql += N'
    SELECT DISTINCT
        plan_id
    FROM #regression_changes
    WHERE database_id = @database_id
    OPTION(RECOMPILE);' + @nc10;
END;
ELSE IF @sort_order = 'plan count by hashes'
BEGIN
    SELECT
        @sql += N'
    SELECT DISTINCT
        plan_id
    FROM #plan_ids_with_query_hashes
    WHERE database_id = @database_id
    OPTION(RECOMPILE);' + @nc10;
END;
ELSE IF @sort_order_is_a_wait = 1
BEGIN
    SELECT
        @sql += N'
    SELECT DISTINCT
        plan_id
    FROM #plan_ids_with_total_waits
    WHERE database_id = @database_id
    OPTION(RECOMPILE);' + @nc10;
END;
ELSE
BEGIN
    SELECT
        @sql += N'
    SELECT TOP (@top)
        qsrs.plan_id
    FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
    WHERE 1 = 1
    ' + @where_clause
      + N'
    GROUP BY
        qsrs.plan_id
    ORDER BY
        MAX(' +
    CASE @sort_order
         WHEN 'cpu' THEN N'qsrs.avg_cpu_time'
         WHEN 'logical reads' THEN N'qsrs.avg_logical_io_reads'
         WHEN 'physical reads' THEN N'qsrs.avg_physical_io_reads'
         WHEN 'writes' THEN N'qsrs.avg_logical_io_writes'
         WHEN 'duration' THEN N'qsrs.avg_duration'
         WHEN 'memory' THEN N'qsrs.avg_query_max_used_memory'
         WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END
         WHEN 'executions' THEN N'qsrs.count_executions'
         WHEN 'recent' THEN N'qsrs.last_execution_time'
         WHEN 'rows' THEN N'qsrs.avg_rowcount'
         WHEN 'total cpu' THEN N'qsrs.avg_cpu_time * qsrs.count_executions'
         WHEN 'total logical reads' THEN N'qsrs.avg_logical_io_reads * qsrs.count_executions'
         WHEN 'total physical reads' THEN N'qsrs.avg_physical_io_reads * qsrs.count_executions'
         WHEN 'total writes' THEN N'qsrs.avg_logical_io_writes * qsrs.count_executions'
         WHEN 'total duration' THEN N'qsrs.avg_duration * qsrs.count_executions'
         WHEN 'total memory' THEN N'qsrs.avg_query_max_used_memory * qsrs.count_executions'
         WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used * qsrs.count_executions' ELSE N'qsrs.avg_cpu_time * qsrs.count_executions' END
         WHEN 'total rows' THEN N'qsrs.avg_rowcount * qsrs.count_executions'
         ELSE N'qsrs.avg_cpu_time'
    END +
    N') DESC
    OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10;
END;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #distinct_plans
WITH
    (TABLOCK)
(
    plan_id
)
EXECUTE sys.sp_executesql
    @sql,
    @parameters,
    @top,
    @start_date,
    @end_date,
    @execution_count,
    @duration_ms,
    @execution_type_desc,
    @database_id,
    @queries_top,
    @work_start_utc,
    @work_end_utc,
    @regression_baseline_start_date,
    @regression_baseline_end_date;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End gathering plan ids*/

/*
This gets the runtime stats for the plans we care about.
It is notably the last usage of @where_clause.
*/
SELECT
    @current_table = 'inserting #query_store_runtime_stats',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @database_id,
    MAX(qsrs_with_lasts.runtime_stats_id),
    qsrs_with_lasts.plan_id,
    MAX(qsrs_with_lasts.runtime_stats_interval_id),
    MAX(qsrs_with_lasts.execution_type_desc),
    MIN(qsrs_with_lasts.first_execution_time),
    MAX(qsrs_with_lasts.partitioned_last_execution_time),
    SUM(qsrs_with_lasts.count_executions),
    AVG((qsrs_with_lasts.avg_duration / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_duration / 1000.)),
    MIN((qsrs_with_lasts.min_duration / 1000.)),
    MAX((qsrs_with_lasts.max_duration / 1000.)),
    AVG((qsrs_with_lasts.avg_cpu_time / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_cpu_time / 1000.)),
    MIN((qsrs_with_lasts.min_cpu_time / 1000.)),
    MAX((qsrs_with_lasts.max_cpu_time / 1000.)),
    AVG(((qsrs_with_lasts.avg_logical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_logical_io_reads * 8.) / 1024.)),
    MIN(((qsrs_with_lasts.min_logical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.max_logical_io_reads * 8.) / 1024.)),
    AVG(((qsrs_with_lasts.avg_logical_io_writes * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_logical_io_writes * 8.) / 1024.)),
    MIN(((qsrs_with_lasts.min_logical_io_writes * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.max_logical_io_writes * 8.) / 1024.)),
    AVG(((qsrs_with_lasts.avg_physical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_physical_io_reads * 8.) / 1024.)),
    MIN(((qsrs_with_lasts.min_physical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.max_physical_io_reads * 8.) / 1024.)),
    AVG((qsrs_with_lasts.avg_clr_time / 1000.)),
    MAX((qsrs_with_lasts.partitioned_last_clr_time / 1000.)),
    MIN((qsrs_with_lasts.min_clr_time / 1000.)),
    MAX((qsrs_with_lasts.max_clr_time / 1000.)),
    MAX(qsrs_with_lasts.partitioned_last_dop),
    MIN(qsrs_with_lasts.min_dop),
    MAX(qsrs_with_lasts.max_dop),
    AVG(((qsrs_with_lasts.avg_query_max_used_memory * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_query_max_used_memory * 8.) / 1024.)),
    MIN(((qsrs_with_lasts.min_query_max_used_memory * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.max_query_max_used_memory * 8.) / 1024.)),
    AVG(qsrs_with_lasts.avg_rowcount),
    MAX(qsrs_with_lasts.partitioned_last_rowcount),
    MIN(qsrs_with_lasts.min_rowcount),
    MAX(qsrs_with_lasts.max_rowcount),';

IF @new = 1
BEGIN
    SELECT
        @sql += N'
    AVG(((qsrs_with_lasts.avg_num_physical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_num_physical_io_reads * 8.) / 1024.)),
    MIN(((qsrs_with_lasts.min_num_physical_io_reads * 8.) / 1024.)),
    MAX(((qsrs_with_lasts.max_num_physical_io_reads * 8.) / 1024.)),
    AVG((qsrs_with_lasts.avg_log_bytes_used / 1048576.)),
    MAX((qsrs_with_lasts.partitioned_last_log_bytes_used / 1048576.)),
    MIN((qsrs_with_lasts.min_log_bytes_used / 1048576.)),
    MAX((qsrs_with_lasts.max_log_bytes_used / 1048576.)),
    AVG(((qsrs_with_lasts.avg_tempdb_space_used * 8) / 1024.)),
    MAX(((qsrs_with_lasts.partitioned_last_tempdb_space_used * 8) / 1024.)),
    MIN(((qsrs_with_lasts.min_tempdb_space_used * 8) / 1024.)),
    MAX(((qsrs_with_lasts.max_tempdb_space_used * 8) / 1024.)),';
END;

IF @new = 0
BEGIN
    SELECT
        @sql += N'
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,';
END;

/*
In regression mode, we do not mind seeing the
same plan_id twice. We need the below to make
the two time periods under consideration
distinct.
*/
IF @regression_mode = 1
BEGIN
   SELECT
       @sql +=  N'
   CASE
       WHEN qsrs_with_lasts.last_execution_time >= @start_date
       AND  qsrs_with_lasts.last_execution_time < @end_date
       THEN ''No''
       ELSE ''Yes''
   END,';
END;
ELSE
BEGIN
   SELECT
       @sql +=  N'
    NULL,';
END;

SELECT
    @sql += N'
    context_settings = NULL
FROM
(
    SELECT
        qsrs.*,
        /*
        We need this here to make sure that PARTITION BY runs before GROUP BY but after CROSS APPLY.
        If it were after GROUP BY, then we would be dealing with already aggregated data.
        If it were inside the CROSS APPLY, then we would be dealing with windows of size one.
        Both are very wrong, so we need this.
        */
        partitioned_last_execution_time =
            LAST_VALUE(qsrs.last_execution_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_duration =
            LAST_VALUE(qsrs.last_duration) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_cpu_time =
            LAST_VALUE(qsrs.last_cpu_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_logical_io_reads =
            LAST_VALUE(qsrs.last_logical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_logical_io_writes =
            LAST_VALUE(qsrs.last_logical_io_writes) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_physical_io_reads =
            LAST_VALUE(qsrs.last_physical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_clr_time =
            LAST_VALUE(qsrs.last_clr_time) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_dop =
            LAST_VALUE(qsrs.last_dop) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_query_max_used_memory =
            LAST_VALUE(qsrs.last_query_max_used_memory) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_rowcount =
            LAST_VALUE(qsrs.last_rowcount) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),';

IF @new = 1
BEGIN
    SELECT
        @sql += N'
        partitioned_last_num_physical_io_reads =
            LAST_VALUE(qsrs.last_num_physical_io_reads) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_log_bytes_used =
            LAST_VALUE(qsrs.last_log_bytes_used) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_tempdb_space_used =
            LAST_VALUE(qsrs.last_tempdb_space_used) OVER
            (
                PARTITION BY
                    qsrs.plan_id,
                    qsrs.execution_type
                ORDER BY
                    qsrs.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            )';
END;

IF @new = 0
BEGIN
    SELECT
        @sql += N'
        not_used = NULL';
END;

SELECT
    @sql += N'
    FROM #distinct_plans AS dp
    CROSS APPLY
    (
        SELECT TOP (@queries_top)
            qsrs.*';

    SELECT
        @sql += N'
        FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs';
        IF @regression_mode = 1
        BEGIN
            SELECT
                @sql += N'
        JOIN #regression_changes AS regression
          ON qsrs.plan_id = regression.plan_id
         AND regression.database_id = @database_id';
        END;
        ELSE IF @sort_order = 'plan count by hashes'
        BEGIN
            SELECT
                @sql += N'
        JOIN #plan_ids_with_query_hashes AS hashes
          ON qsrs.plan_id = hashes.plan_id
         AND hashes.database_id = @database_id';
        END;
        ELSE IF @sort_order_is_a_wait = 1
        BEGIN
            /*
            Note that we do not need this join in
            regression mode, even if we are looking
            at a wait. The tables here are only for
            sorting. In regression mode, we sort
            by columns found in #regression_changes.
            */
            SELECT
                @sql += N'
        JOIN #plan_ids_with_total_waits AS waits
          ON qsrs.plan_id = waits.plan_id
         AND waits.database_id = @database_id';
        END;

    SELECT
        @sql += N'
        WHERE qsrs.plan_id = dp.plan_id
        AND   1 = 1
        '
        + CASE
              WHEN @regression_mode = 1
              THEN N' AND ( 1 = 1
          ' +
          @regression_where_clause
          + N' )
    OR
          ( 1 = 1
          ' + @where_clause
          + N' ) '
              ELSE @where_clause
          END
      + N'
    ORDER BY
        ' +
    CASE @regression_mode
    WHEN 1 THEN
        /* As seen when populating #regression_changes. */
        CASE @regression_direction
           WHEN 'regressed' THEN N'regression.change_since_regression_time_period'
           WHEN 'worse' THEN N'regression.change_since_regression_time_period'
           WHEN 'improved' THEN N'regression.change_since_regression_time_period * -1.0'
           WHEN 'better' THEN N'regression.change_since_regression_time_period * -1.0'
           WHEN 'magnitude' THEN N'ABS(regression.change_since_regression_time_period)'
           WHEN 'absolute' THEN N'ABS(regression.change_since_regression_time_period)'
        END
        ELSE
        CASE @sort_order
             WHEN 'cpu' THEN N'qsrs.avg_cpu_time'
             WHEN 'logical reads' THEN N'qsrs.avg_logical_io_reads'
             WHEN 'physical reads' THEN N'qsrs.avg_physical_io_reads'
             WHEN 'writes' THEN N'qsrs.avg_logical_io_writes'
             WHEN 'duration' THEN N'qsrs.avg_duration'
             WHEN 'memory' THEN N'qsrs.avg_query_max_used_memory'
             WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END
             WHEN 'executions' THEN N'qsrs.count_executions'
             WHEN 'recent' THEN N'qsrs.last_execution_time'
             WHEN 'rows' THEN N'qsrs.avg_rowcount'
             WHEN 'total cpu' THEN N'qsrs.avg_cpu_time * qsrs.count_executions'
             WHEN 'total logical reads' THEN N'qsrs.avg_logical_io_reads * qsrs.count_executions'
             WHEN 'total physical reads' THEN N'qsrs.avg_physical_io_reads * qsrs.count_executions'
             WHEN 'total writes' THEN N'qsrs.avg_logical_io_writes * qsrs.count_executions'
             WHEN 'total duration' THEN N'qsrs.avg_duration * qsrs.count_executions'
             WHEN 'total memory' THEN N'qsrs.avg_query_max_used_memory * qsrs.count_executions'
             WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used * qsrs.count_executions' ELSE N'qsrs.avg_cpu_time * qsrs.count_executions' END
             WHEN 'total rows' THEN N'qsrs.avg_rowcount * qsrs.count_executions'
             WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC,
                hashes.query_hash'
             ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END
        END
    END + N' DESC
    ) AS qsrs
) AS qsrs_with_lasts
GROUP BY
    qsrs_with_lasts.plan_id ' +
/*
In regression mode, we do not mind seeing the
same plan_id twice. We need the below to make
the two time periods under consideration
distinct.
*/
CASE @regression_mode
     WHEN 1
     THEN  N',
   CASE
       WHEN qsrs_with_lasts.last_execution_time >= @start_date AND qsrs_with_lasts.last_execution_time < @end_date
       THEN ''No''
       ELSE ''Yes''
   END'
   ELSE N' '
END
+
N'
OPTION(RECOMPILE, OPTIMIZE FOR (@queries_top = 9223372036854775807));' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);

    IF LEN(@sql) > 4000
    BEGIN
        SELECT
            query =
            (
                SELECT
                    [processing-instruction(_)] =
                        @sql
                FOR XML
                    PATH(''),
                    TYPE
            );
    END;
    ELSE
    BEGIN
        PRINT @sql;
    END;
END;

INSERT
    #query_store_runtime_stats
WITH
    (TABLOCK)
(
    database_id, runtime_stats_id, plan_id, runtime_stats_interval_id, execution_type_desc,
    first_execution_time, last_execution_time, count_executions,
    avg_duration_ms, last_duration_ms, min_duration_ms, max_duration_ms,
    avg_cpu_time_ms, last_cpu_time_ms, min_cpu_time_ms, max_cpu_time_ms,
    avg_logical_io_reads_mb, last_logical_io_reads_mb, min_logical_io_reads_mb, max_logical_io_reads_mb,
    avg_logical_io_writes_mb, last_logical_io_writes_mb, min_logical_io_writes_mb, max_logical_io_writes_mb,
    avg_physical_io_reads_mb, last_physical_io_reads_mb, min_physical_io_reads_mb, max_physical_io_reads_mb,
    avg_clr_time_ms, last_clr_time_ms, min_clr_time_ms, max_clr_time_ms,
    last_dop, min_dop, max_dop,
    avg_query_max_used_memory_mb, last_query_max_used_memory_mb, min_query_max_used_memory_mb, max_query_max_used_memory_mb,
    avg_rowcount, last_rowcount, min_rowcount, max_rowcount,
    avg_num_physical_io_reads_mb, last_num_physical_io_reads_mb, min_num_physical_io_reads_mb, max_num_physical_io_reads_mb,
    avg_log_bytes_used_mb, last_log_bytes_used_mb, min_log_bytes_used_mb, max_log_bytes_used_mb,
    avg_tempdb_space_used_mb, last_tempdb_space_used_mb, min_tempdb_space_used_mb, max_tempdb_space_used_mb,
    from_regression_baseline,
    context_settings
)
EXECUTE sys.sp_executesql
    @sql,
    @parameters,
    @top,
    @start_date,
    @end_date,
    @execution_count,
    @duration_ms,
    @execution_type_desc,
    @database_id,
    @queries_top,
    @work_start_utc,
    @work_end_utc,
    @regression_baseline_start_date,
    @regression_baseline_end_date;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting runtime stats*/

/*
This gets the query plans we're after
*/
SELECT
    @current_table = 'inserting #query_store_plan',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @database_id,
    qsp.plan_id,
    qsp.query_id,
    all_plan_ids =
        STUFF
        (
            (
                SELECT DISTINCT
                    '', '' +
                    RTRIM
                        (qsp_plans.plan_id)
                FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp_plans
                WHERE qsp_plans.query_id = qsp.query_id
                FOR XML
                    PATH(''''),
                    TYPE
            ).value(''./text()[1]'', ''varchar(max)''),
            1,
            2,
            ''''
        ),
    qsp.plan_group_id,
    qsp.engine_version,
    qsp.compatibility_level,
    qsp.query_plan_hash,
    qsp.query_plan,
    qsp.is_online_index_plan,
    qsp.is_trivial_plan,
    qsp.is_parallel_plan,
    qsp.is_forced_plan,
    toggle_forcing =
        CASE
            qsp.is_forced_plan
            WHEN 1
            THEN ''EXECUTE ' + @database_name_quoted + '.sys.sp_query_store_unforce_plan @query_id = '' + CONVERT(nvarchar(20), qsp.query_id) +  '', @plan_id = '' + CONVERT(nvarchar(20), qsp.plan_id) + '';''
            WHEN 0
            THEN ''EXECUTE ' + @database_name_quoted + '.sys.sp_query_store_force_plan @query_id = '' + CONVERT(nvarchar(20), qsp.query_id) +  '', @plan_id = '' + CONVERT(nvarchar(20), qsp.plan_id) + '', @disable_optimized_plan_forcing = ? ;''
        END,
    qsp.is_natively_compiled,
    qsp.force_failure_count,
    qsp.last_force_failure_reason_desc,
    qsp.count_compiles,
    qsp.initial_compile_start_time,
    qsp.last_compile_start_time,
    qsp.last_execution_time,
    (qsp.avg_compile_duration / 1000.),
    (qsp.last_compile_duration / 1000.),';

IF
(
      @new = 0
  AND @sql_2022_views = 0
)
BEGIN
    SELECT
        @sql += N'
    NULL,
    NULL,
    NULL,
    NULL';
END;

IF
(
      @new = 1
  AND @sql_2022_views = 0
)
BEGIN
    SELECT
        @sql += N'
    qsp.plan_forcing_type_desc,
    NULL,
    NULL,
    NULL';
END;

IF
(
      @new = 1
  AND @sql_2022_views = 1
)
BEGIN
    SELECT
        @sql += N'
    qsp.plan_forcing_type_desc,
    qsp.has_compile_replay_script,
    qsp.is_optimized_plan_forcing_disabled,
    qsp.plan_type_desc';
END;

SELECT
    @sql += N'
FROM #query_store_runtime_stats AS qsrs
CROSS APPLY
(
    SELECT TOP (@plans_top)
        qsp.*
    FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
    WHERE qsp.plan_id = qsrs.plan_id
    AND   qsp.is_online_index_plan = 0
    ORDER BY
        qsp.last_execution_time DESC
) AS qsp
WHERE qsrs.database_id = @database_id
OPTION(RECOMPILE, OPTIMIZE FOR (@plans_top = 9223372036854775807));' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_plan
WITH
    (TABLOCK)
(
    database_id,
    plan_id,
    query_id,
    all_plan_ids,
    plan_group_id,
    engine_version,
    compatibility_level,
    query_plan_hash,
    query_plan,
    is_online_index_plan,
    is_trivial_plan,
    is_parallel_plan,
    is_forced_plan,
    toggle_forcing,
    is_natively_compiled,
    force_failure_count,
    last_force_failure_reason_desc,
    count_compiles,
    initial_compile_start_time,
    last_compile_start_time,
    last_execution_time,
    avg_compile_duration_ms,
    last_compile_duration_ms,
    plan_forcing_type_desc,
    has_compile_replay_script,
    is_optimized_plan_forcing_disabled,
    plan_type_desc
)
EXECUTE sys.sp_executesql
    @sql,
  N'@plans_top bigint,
    @database_id int',
    @plans_top,
    @database_id;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting query plans*/

/*
This gets some query information
*/
SELECT
    @current_table = 'inserting #query_store_query',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @database_id,
    qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.object_id,
    qsq.batch_sql_handle,
    qsq.query_hash,
    qsq.is_internal_query,
    qsq.query_parameterization_type_desc,
    qsq.initial_compile_start_time,
    qsq.last_compile_start_time,
    qsq.last_execution_time,
    qsq.last_compile_batch_sql_handle,
    qsq.last_compile_batch_offset_start,
    qsq.last_compile_batch_offset_end,
    qsq.count_compiles,
    (qsq.avg_compile_duration / 1000.),
    (qsq.last_compile_duration / 1000.),
    (qsq.avg_bind_duration / 1000.),
    (qsq.last_bind_duration / 1000.),
    (qsq.avg_bind_cpu_time / 1000.),
    (qsq.last_bind_cpu_time / 1000.),
    (qsq.avg_optimize_duration / 1000.),
    (qsq.last_optimize_duration / 1000.),
    (qsq.avg_optimize_cpu_time / 1000.),
    (qsq.last_optimize_cpu_time / 1000.),
    (qsq.avg_compile_memory_kb / 1024.),
    (qsq.last_compile_memory_kb / 1024.),
    (qsq.max_compile_memory_kb / 1024.),
    qsq.is_clouddb_internal_query
FROM #query_store_plan AS qsp
CROSS APPLY
(
    SELECT TOP (1)
        qsq.*
    FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq
    WHERE qsq.query_id = qsp.query_id
    ORDER BY
        qsq.last_execution_time DESC
) AS qsq
WHERE qsp.database_id = @database_id
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_query
WITH
    (TABLOCK)
(
    database_id,
    query_id,
    query_text_id,
    context_settings_id,
    object_id,
    batch_sql_handle,
    query_hash,
    is_internal_query,
    query_parameterization_type_desc,
    initial_compile_start_time,
    last_compile_start_time,
    last_execution_time,
    last_compile_batch_sql_handle,
    last_compile_batch_offset_start,
    last_compile_batch_offset_end,
    count_compiles,
    avg_compile_duration_ms,
    last_compile_duration_ms,
    avg_bind_duration_ms,
    last_bind_duration_ms,
    avg_bind_cpu_time_ms,
    last_bind_cpu_time_ms,
    avg_optimize_duration_ms,
    last_optimize_duration_ms,
    avg_optimize_cpu_time_ms,
    last_optimize_cpu_time_ms,
    avg_compile_memory_mb,
    last_compile_memory_mb,
    max_compile_memory_mb,
    is_clouddb_internal_query
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting query details*/


IF @include_query_hash_totals = 1
BEGIN
    SELECT
        @current_table = 'inserting #query_hash_totals for @include_query_hash_totals',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
    SELECT
        @database_id,
        qsq.query_hash,
        SUM(qsrs.count_executions),
        SUM(qsrs.count_executions * qsrs.avg_duration) / 1000.,
        SUM(qsrs.count_executions * qsrs.avg_cpu_time) / 1000.,
        SUM(qsrs.count_executions * (qsrs.avg_logical_io_reads * 8.)) / 1024.,
        SUM(qsrs.count_executions * (qsrs.avg_physical_io_reads * 8.)) / 1024.,
        SUM(qsrs.count_executions * (qsrs.avg_logical_io_writes * 8.)) / 1024.,
        SUM(qsrs.count_executions * qsrs.avg_clr_time) / 1000.,
        SUM(qsrs.count_executions * (qsrs.avg_query_max_used_memory * 8.)) / 1024.,
        SUM(qsrs.count_executions * qsrs.avg_rowcount)' +
  CASE
      @new
      WHEN 1
      THEN N',
        SUM(qsrs.count_executions * (qsrs.avg_num_physical_io_reads * 8)) / 1024.,
        SUM(qsrs.count_executions * qsrs.avg_log_bytes_used) / 1048576.,
        SUM(qsrs.count_executions * (qsrs.avg_tempdb_space_used * 8)) / 1024.'
      ELSE N',
        NULL,
        NULL,
        NULL'
  END +
  N'
    FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs
    JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp
      ON qsrs.plan_id = qsp.plan_id
    JOIN ' + @database_name_quoted + N'.sys.query_store_query AS qsq
      ON qsp.query_id = qsq.query_id
    WHERE EXISTS
    (
        SELECT
            1/0
        FROM #query_store_query AS qsq2
        WHERE qsq2.query_hash = qsq.query_hash
    )
    GROUP BY
        qsq.query_hash
    OPTION(RECOMPILE);
';

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT INTO
        #query_hash_totals
    WITH
        (TABLOCK)
    (
        database_id,
        query_hash,
        total_executions,
        total_duration_ms,
        total_cpu_time_ms,
        total_logical_reads_mb,
        total_physical_reads_mb,
        total_logical_writes_mb,
        total_clr_time_ms,
        total_memory_mb,
        total_rowcount,
        total_num_physical_io_reads,
        total_log_bytes_used_mb,
        total_tempdb_space_used_mb
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END;


/*
This gets the query text for them!
*/
SELECT
    @current_table = 'inserting #query_store_query_text',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;


SELECT
    @sql += N'
SELECT
    @database_id,
    qsqt.query_text_id,
    query_sql_text =
        (
             SELECT
                 [processing-instruction(query)] =
                     REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                     REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                     REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
                         qsqt.query_sql_text COLLATE Latin1_General_BIN2,
                     NCHAR(31),N''?''),NCHAR(30),N''?''),NCHAR(29),N''?''),NCHAR(28),N''?''),NCHAR(27),N''?''),NCHAR(26),N''?''),NCHAR(25),N''?''),NCHAR(24),N''?''),NCHAR(23),N''?''),NCHAR(22),N''?''),
                     NCHAR(21),N''?''),NCHAR(20),N''?''),NCHAR(19),N''?''),NCHAR(18),N''?''),NCHAR(17),N''?''),NCHAR(16),N''?''),NCHAR(15),N''?''),NCHAR(14),N''?''),NCHAR(12),N''?''),
                     NCHAR(11),N''?''),NCHAR(8),N''?''),NCHAR(7),N''?''),NCHAR(6),N''?''),NCHAR(5),N''?''),NCHAR(4),N''?''),NCHAR(3),N''?''),NCHAR(2),N''?''),NCHAR(1),N''?''),NCHAR(0),N'''')
             FOR XML
                 PATH(''''),
                 TYPE
        ),
    qsqt.statement_sql_handle,
    qsqt.is_part_of_encrypted_module,
    qsqt.has_restricted_text
FROM #query_store_query AS qsq
CROSS APPLY
(
    SELECT TOP (1)
        qsqt.*
    FROM ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt
    WHERE qsqt.query_text_id = qsq.query_text_id
) AS qsqt
WHERE qsq.database_id = @database_id
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #query_store_query_text
WITH
    (TABLOCK)
(
    database_id,
    query_text_id,
    query_sql_text,
    statement_sql_handle,
    is_part_of_encrypted_module,
    has_restricted_text
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting query text*/

/*
Here we try to get some data from the "plan cache"
that isn't available in Query Store :(
*/
SELECT
    @sql = N'',
    @current_table = 'inserting #dm_exec_query_stats';

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

INSERT
    #dm_exec_query_stats
WITH
    (TABLOCK)
(
    statement_sql_handle,
    total_grant_mb,
    last_grant_mb,
    min_grant_mb,
    max_grant_mb,
    total_used_grant_mb,
    last_used_grant_mb,
    min_used_grant_mb,
    max_used_grant_mb,
    total_ideal_grant_mb,
    last_ideal_grant_mb,
    min_ideal_grant_mb,
    max_ideal_grant_mb,
    total_reserved_threads,
    last_reserved_threads,
    min_reserved_threads,
    max_reserved_threads,
    total_used_threads,
    last_used_threads,
    min_used_threads,
    max_used_threads
)
SELECT
    deqs_with_lasts.statement_sql_handle,
    MAX(deqs_with_lasts.total_grant_kb) / 1024.,
    MAX(deqs_with_lasts.partitioned_last_grant_kb) / 1024.,
    MAX(deqs_with_lasts.min_grant_kb) / 1024.,
    MAX(deqs_with_lasts.max_grant_kb) / 1024.,
    MAX(deqs_with_lasts.total_used_grant_kb) / 1024.,
    MAX(deqs_with_lasts.partitioned_last_used_grant_kb) / 1024.,
    MAX(deqs_with_lasts.min_used_grant_kb) / 1024.,
    MAX(deqs_with_lasts.max_used_grant_kb) / 1024.,
    MAX(deqs_with_lasts.total_ideal_grant_kb) / 1024.,
    MAX(deqs_with_lasts.partitioned_last_ideal_grant_kb) / 1024.,
    MAX(deqs_with_lasts.min_ideal_grant_kb) / 1024.,
    MAX(deqs_with_lasts.max_ideal_grant_kb) / 1024.,
    MAX(deqs_with_lasts.total_reserved_threads),
    MAX(deqs_with_lasts.partitioned_last_reserved_threads),
    MAX(deqs_with_lasts.min_reserved_threads),
    MAX(deqs_with_lasts.max_reserved_threads),
    MAX(deqs_with_lasts.total_used_threads),
    MAX(deqs_with_lasts.partitioned_last_used_threads),
    MAX(deqs_with_lasts.min_used_threads),
    MAX(deqs_with_lasts.max_used_threads)
FROM
(
    SELECT
        deqs.statement_sql_handle,
        deqs.total_grant_kb,
        deqs.min_grant_kb,
        deqs.max_grant_kb,
        deqs.total_used_grant_kb,
        deqs.min_used_grant_kb,
        deqs.max_used_grant_kb,
        deqs.total_ideal_grant_kb,
        deqs.min_ideal_grant_kb,
        deqs.max_ideal_grant_kb,
        deqs.total_reserved_threads,
        deqs.min_reserved_threads,
        deqs.max_reserved_threads,
        deqs.total_used_threads,
        deqs.min_used_threads,
        deqs.max_used_threads,
        partitioned_last_grant_kb =
            LAST_VALUE(deqs.last_grant_kb) OVER
            (
                PARTITION BY
                    deqs.sql_handle
                ORDER BY
                    deqs.last_execution_time ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_used_grant_kb =
            LAST_VALUE(deqs.last_used_grant_kb) OVER
            (
                PARTITION BY
                    deqs.sql_handle
                ORDER BY
                    deqs.last_execution_time ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
        partitioned_last_ideal_grant_kb =
            LAST_VALUE(deqs.last_ideal_grant_kb) OVER
            (
                PARTITION BY
                    deqs.sql_handle
                ORDER BY
                    deqs.last_execution_time ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            ),
       partitioned_last_reserved_threads =
           LAST_VALUE(deqs.last_reserved_threads) OVER
           (
               PARTITION BY
                   deqs.sql_handle
               ORDER BY
                   deqs.last_execution_time ASC
               ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
           ),
       partitioned_last_used_threads =
           LAST_VALUE(deqs.last_used_threads) OVER
           (
               PARTITION BY
                   deqs.sql_handle
               ORDER BY
                   deqs.last_execution_time ASC
               ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
           )
    FROM sys.dm_exec_query_stats AS deqs
    WHERE EXISTS
          (
              SELECT
                  1/0
              FROM #query_store_query_text AS qsqt
              WHERE qsqt.statement_sql_handle = deqs.statement_sql_handle
          )
) AS deqs_with_lasts
GROUP BY
    deqs_with_lasts.statement_sql_handle
OPTION(RECOMPILE);

SELECT
    @rc = ROWCOUNT_BIG();

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting runtime stats*/

/*Only update if we got anything*/
IF @rc > 0
BEGIN
    SELECT
        @current_table = 'updating #dm_exec_query_stats';

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    UPDATE
        qsqt
    SET
        qsqt.total_grant_mb = deqs.total_grant_mb,
        qsqt.last_grant_mb = deqs.last_grant_mb,
        qsqt.min_grant_mb = deqs.min_grant_mb,
        qsqt.max_grant_mb = deqs.max_grant_mb,
        qsqt.total_used_grant_mb = deqs.total_used_grant_mb,
        qsqt.last_used_grant_mb = deqs.last_used_grant_mb,
        qsqt.min_used_grant_mb = deqs.min_used_grant_mb,
        qsqt.max_used_grant_mb = deqs.max_used_grant_mb,
        qsqt.total_ideal_grant_mb = deqs.total_ideal_grant_mb,
        qsqt.last_ideal_grant_mb = deqs.last_ideal_grant_mb,
        qsqt.min_ideal_grant_mb = deqs.min_ideal_grant_mb,
        qsqt.max_ideal_grant_mb = deqs.max_ideal_grant_mb,
        qsqt.total_reserved_threads = deqs.total_reserved_threads,
        qsqt.last_reserved_threads = deqs.last_reserved_threads,
        qsqt.min_reserved_threads = deqs.min_reserved_threads,
        qsqt.max_reserved_threads = deqs.max_reserved_threads,
        qsqt.total_used_threads = deqs.total_used_threads,
        qsqt.last_used_threads = deqs.last_used_threads,
        qsqt.min_used_threads = deqs.min_used_threads,
        qsqt.max_used_threads = deqs.max_used_threads
    FROM #query_store_query_text AS qsqt
    JOIN #dm_exec_query_stats AS deqs
      ON qsqt.statement_sql_handle = deqs.statement_sql_handle
    OPTION(RECOMPILE);

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END; /*End updating runtime stats*/

/*
Check on settings, etc.
We do this first so we can see if wait stats capture mode is true more easily.
We do not truncate this table as part of the looping over databases.
Not truncating it makes it easier to show all set options when hitting multiple databases in expert mode.
*/
SELECT
    @current_table = 'inserting #database_query_store_options',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @database_id,
    dqso.desired_state_desc,
    dqso.actual_state_desc,
    readonly_reason =
        CASE dqso.readonly_reason
             WHEN 0
             THEN ''None''
             WHEN 2
             THEN ''Database in single user mode''
             WHEN 4
             THEN ''Database is in emergency mode''
             WHEN 8
             THEN ''Database is AG secondary''
             WHEN 65536
             THEN ''Reached max size: '' +
                  FORMAT(dqso.current_storage_size_mb, ''N0'') +
                  '' of '' +
                  FORMAT(dqso.max_storage_size_mb, ''N0'') +
                  ''.''
             WHEN 131072
             THEN ''The number of different statements in Query Store has reached the internal memory limit''
             WHEN 262144
             THEN ''Size of in-memory items waiting to be persisted on disk has reached the internal memory limit''
             WHEN 524288
             THEN ''Database has reached disk size limit''
             ELSE ''WOAH''
        END,
    dqso.current_storage_size_mb,
    dqso.flush_interval_seconds,
    dqso.interval_length_minutes,
    dqso.max_storage_size_mb,
    dqso.stale_query_threshold_days,
    dqso.max_plans_per_query,
    dqso.query_capture_mode_desc,'
    +
    CASE
        WHEN
        (
             @product_version > 14
          OR @azure = 1
        )
        THEN N'
    dqso.capture_policy_execution_count,
    dqso.capture_policy_total_compile_cpu_time_ms,
    dqso.capture_policy_total_execution_cpu_time_ms,
    dqso.capture_policy_stale_threshold_hours,'
        ELSE N'
    NULL,
    NULL,
    NULL,
    NULL,'
    END
    + N'
    dqso.size_based_cleanup_mode_desc,'
    +
    CASE
        WHEN
        (
              @product_version = 13
          AND @azure = 0
        )
        THEN N'
    NULL'
        ELSE N'
    dqso.wait_stats_capture_mode_desc'
    END
    + N'
FROM ' + @database_name_quoted + N'.sys.database_query_store_options AS dqso
OPTION(RECOMPILE);' + @nc10;

IF @debug = 1
BEGIN
    PRINT LEN(@sql);
    PRINT @sql;
END;

INSERT
    #database_query_store_options
WITH
    (TABLOCK)
(
    database_id,
    desired_state_desc,
    actual_state_desc,
    readonly_reason,
    current_storage_size_mb,
    flush_interval_seconds,
    interval_length_minutes,
    max_storage_size_mb,
    stale_query_threshold_days,
    max_plans_per_query,
    query_capture_mode_desc,
    capture_policy_execution_count,
    capture_policy_total_compile_cpu_time_ms,
    capture_policy_total_execution_cpu_time_ms,
    capture_policy_stale_threshold_hours,
    size_based_cleanup_mode_desc,
    wait_stats_capture_mode_desc
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting query store settings*/

/*
If wait stats are available, we'll grab them here
*/
IF
(
    @new = 1
    /*
    Recall that we do not care about the edge case of a database holding
    wait stats despite capturing wait stats being turned off.
    */
    AND @database_id IN
        (
            SELECT
                dqso.database_id
            FROM #database_query_store_options AS dqso
            WHERE dqso.wait_stats_capture_mode_desc = N'ON'
            AND   dqso.database_id = @database_id
        )
)
BEGIN
    SELECT
        @current_table = 'inserting #query_store_wait_stats',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsws_with_lasts.plan_id,
    qsws_with_lasts.wait_category_desc,
    total_query_wait_time_ms =
        SUM(qsws_with_lasts.total_query_wait_time_ms),
    avg_query_wait_time_ms =
        SUM(qsws_with_lasts.avg_query_wait_time_ms),
    last_query_wait_time_ms =
        MAX(qsws_with_lasts.partitioned_last_query_wait_time_ms),
    min_query_wait_time_ms =
        SUM(qsws_with_lasts.min_query_wait_time_ms),
    max_query_wait_time_ms =
        SUM(qsws_with_lasts.max_query_wait_time_ms)
FROM
(
    SELECT
        qsws.*,
        /*
        We need this here to make sure that PARTITION BY runs before GROUP BY but after CROSS APPLY.
        If it were after GROUP BY, then we would be dealing with already aggregated data.
        If it were inside the CROSS APPLY, then we would be dealing with windows of size one.
        Both are very wrong, so we need this.
        */
        partitioned_last_query_wait_time_ms =
            LAST_VALUE(qsws.last_query_wait_time_ms) OVER
            (
                PARTITION BY
                    qsws.plan_id,
                    qsws.execution_type,
                    qsws.wait_category_desc
                ORDER BY
                    qsws.runtime_stats_interval_id ASC
                ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
            )
    FROM #query_store_runtime_stats AS qsrs
    CROSS APPLY
    (
        SELECT TOP (5)
            qsws.*
        FROM ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws
        WHERE qsws.runtime_stats_interval_id = qsrs.runtime_stats_interval_id
        AND   qsws.plan_id = qsrs.plan_id
        AND   qsws.wait_category > 0
        AND   qsws.min_query_wait_time_ms > 0
        ORDER BY
            qsws.avg_query_wait_time_ms DESC
    ) AS qsws
    WHERE qsrs.database_id = @database_id
) AS qsws_with_lasts
GROUP BY
    qsws_with_lasts.plan_id,
    qsws_with_lasts.wait_category_desc
HAVING
    SUM(qsws_with_lasts.min_query_wait_time_ms) > 0.
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_wait_stats
    WITH
        (TABLOCK)
    (
        database_id,
        plan_id,
        wait_category_desc,
        total_query_wait_time_ms,
        avg_query_wait_time_ms,
        last_query_wait_time_ms,
        min_query_wait_time_ms,
        max_query_wait_time_ms
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END; /*End getting wait stats*/

/*
This gets context info and settings
*/
SELECT
    @current_table = 'inserting #query_context_settings',
    @sql = @isolation_level;

IF @troubleshoot_performance = 1
BEGIN
    EXECUTE sys.sp_executesql
        @troubleshoot_insert,
      N'@current_table nvarchar(100)',
        @current_table;

    SET STATISTICS XML ON;
END;

SELECT
    @sql += N'
SELECT
    @database_id,
    context_settings_id,
    set_options,
    language_id,
    date_format,
    date_first,
    status,
    required_cursor_options,
    acceptable_cursor_options,
    merge_action_type,
    default_schema_id,
    is_replication_specific,
    is_contained
FROM ' + @database_name_quoted + N'.sys.query_context_settings AS qcs
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_runtime_stats AS qsrs
          JOIN #query_store_plan AS qsp
            ON  qsrs.plan_id = qsp.plan_id
            AND qsrs.database_id = qsp.database_id
          JOIN #query_store_query AS qsq
            ON  qsp.query_id = qsq.query_id
            AND qsp.database_id = qsq.database_id
          WHERE qsq.context_settings_id = qcs.context_settings_id
      )
OPTION(RECOMPILE);' + @nc10;

INSERT
    #query_context_settings
WITH
    (TABLOCK)
(
    database_id,
    context_settings_id,
    set_options,
    language_id,
    date_format,
    date_first,
    status,
    required_cursor_options,
    acceptable_cursor_options,
    merge_action_type,
    default_schema_id,
    is_replication_specific,
    is_contained
)
EXECUTE sys.sp_executesql
    @sql,
  N'@database_id integer',
    @database_id;

IF @troubleshoot_performance = 1
BEGIN
    SET STATISTICS XML OFF;

    EXECUTE sys.sp_executesql
        @troubleshoot_update,
      N'@current_table nvarchar(100)',
        @current_table;

    EXECUTE sys.sp_executesql
        @troubleshoot_info,
      N'@sql nvarchar(max),
        @current_table nvarchar(100)',
        @sql,
        @current_table;
END; /*End getting context settings*/

/*
Update things to get the context settings for each query
*/
SELECT
    @current_table = 'updating context_settings in #query_store_runtime_stats';

UPDATE
    qsrs
SET
    qsrs.context_settings =
        SUBSTRING
        (
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 1 = 1
                THEN ', ANSI_PADDING'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 8 = 8
                THEN ', CONCAT_NULL_YIELDS_NULL'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 16 = 16
                THEN ', ANSI_WARNINGS'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 32 = 32
                THEN ', ANSI_NULLS'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 64 = 64
                THEN ', QUOTED_IDENTIFIER'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 4096 = 4096
                THEN ', ARITH_ABORT'
                ELSE ''
            END +
            CASE
                WHEN
                    CONVERT
                    (
                        integer,
                        qcs.set_options
                    ) & 8192 = 8192
                THEN ', NUMERIC_ROUNDABORT'
                ELSE ''
            END,
            2,
            256
        )
FROM #query_store_runtime_stats AS qsrs
JOIN #query_store_plan AS qsp
  ON  qsrs.plan_id = qsp.plan_id
  AND qsrs.database_id = qsp.database_id
JOIN #query_store_query AS qsq
  ON  qsp.query_id = qsq.query_id
  AND qsp.database_id = qsq.database_id
JOIN #query_context_settings AS qcs
  ON  qsq.context_settings_id = qcs.context_settings_id
  AND qsq.database_id = qcs.database_id
OPTION(RECOMPILE);

IF @sql_2022_views = 1
BEGIN
    /*query_store_plan_feedback*/
    SELECT
        @current_table = 'inserting #query_store_plan_feedback',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @database_id,
    qspf.plan_feedback_id,
    qspf.plan_id,
    qspf.feature_desc,
    qspf.feedback_data,
    qspf.state_desc,
    qspf.create_time,
    qspf.last_updated_time
FROM ' + @database_name_quoted + N'.sys.query_store_plan_feedback AS qspf
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qspf.plan_id = qsp.plan_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_plan_feedback
    WITH
        (TABLOCK)
    (
        database_id,
        plan_feedback_id,
        plan_id,
        feature_desc,
        feedback_data,
        state_desc,
        create_time,
        last_updated_time
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    /*query_store_query_variant*/
    SELECT
        @current_table = 'inserting #query_store_query_variant',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsqv.query_variant_query_id,
    qsqv.parent_query_id,
    qsqv.dispatcher_plan_id
FROM ' + @database_name_quoted + N'.sys.query_store_query_variant AS qsqv
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qsqv.query_variant_query_id = qsp.query_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_query_variant
    WITH
        (TABLOCK)
    (
        database_id,
        query_variant_query_id,
        parent_query_id,
        dispatcher_plan_id
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    /*query_store_query_hints*/
    SELECT
        @current_table = 'inserting #query_store_query_hints',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @database_id,
    qsqh.query_hint_id,
    qsqh.query_id,
    qsqh.query_hint_text,
    qsqh.last_query_hint_failure_reason_desc,
    qsqh.query_hint_failure_count,
    qsqh.source_desc
FROM ' + @database_name_quoted + N'.sys.query_store_query_hints AS qsqh
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qsqh.query_id = qsp.query_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #query_store_query_hints
    WITH
        (TABLOCK)
    (
        database_id,
        query_hint_id,
        query_id,
        query_hint_text,
        last_query_hint_failure_reason_desc,
        query_hint_failure_count,
        source_desc
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;

    IF @ags_present = 1
    BEGIN
        /*query_store_plan_forcing_locations*/
        SELECT
            @current_table = 'inserting #query_store_plan_forcing_locations',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
SELECT
    @database_id,
    qspfl.plan_forcing_location_id,
    qspfl.query_id,
    qspfl.plan_id,
    qspfl.replica_group_id
FROM ' + @database_name_quoted + N'.sys.query_store_plan_forcing_locations AS qspfl
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE qspfl.query_id = qsp.query_id
          AND   qspfl.plan_id = qsp.plan_id
      )
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #query_store_plan_forcing_locations
        WITH
            (TABLOCK)
        (
            database_id,
            plan_forcing_location_id,
            query_id,
            plan_id,
            replica_group_id
        )
        EXECUTE sys.sp_executesql
            @sql,
          N'@database_id integer',
            @database_id;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;

        /*query_store_replicas*/
        SELECT
            @current_table = 'inserting #query_store_replicas',
            @sql = @isolation_level;

        IF @troubleshoot_performance = 1
        BEGIN
            EXECUTE sys.sp_executesql
                @troubleshoot_insert,
              N'@current_table nvarchar(100)',
                @current_table;

            SET STATISTICS XML ON;
        END;

        SELECT
            @sql += N'
SELECT
    @database_id,
    qsr.replica_group_id,
    qsr.role_type,
    qsr.replica_name
FROM ' + @database_name_quoted + N'.sys.query_store_replicas AS qsr
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan_forcing_locations AS qspfl
          WHERE qspfl.replica_group_id = qsr.replica_group_id
      )
OPTION(RECOMPILE);' + @nc10;

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        INSERT
            #query_store_replicas
        WITH
            (TABLOCK)
        (
            database_id,
            replica_group_id,
            role_type,
            replica_name
        )
        EXECUTE sys.sp_executesql
            @sql,
          N'@database_id integer',
            @database_id;

        IF @troubleshoot_performance = 1
        BEGIN
            SET STATISTICS XML OFF;

            EXECUTE sys.sp_executesql
                @troubleshoot_update,
              N'@current_table nvarchar(100)',
                @current_table;

            EXECUTE sys.sp_executesql
                @troubleshoot_info,
              N'@sql nvarchar(max),
                @current_table nvarchar(100)',
                @sql,
                @current_table;
        END;
    END; /*End AG queries*/

    /*database_automatic_tuning_configurations*/
    SELECT
        @current_table = 'inserting #database_automatic_tuning_configurations',
        @sql = @isolation_level;

    IF @troubleshoot_performance = 1
    BEGIN
        EXECUTE sys.sp_executesql
            @troubleshoot_insert,
          N'@current_table nvarchar(100)',
            @current_table;

        SET STATISTICS XML ON;
    END;

    SELECT
        @sql += N'
SELECT
    @database_id,
    datc.[option],
    datc.option_value,
    datc.[type],
    type_value =
        CONVERT
        (
            nvarchar(120),
            datc.type_value
        ),
    datc.details,
    datc.[state]
FROM ' + @database_name_quoted + N'.sys.database_automatic_tuning_configurations AS datc
WHERE EXISTS
      (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
          WHERE TRY_CAST(datc.type_value AS bigint) = qsp.query_id
      )
OPTION(RECOMPILE);' + @nc10;

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT @sql;
    END;

    INSERT
        #database_automatic_tuning_configurations
    WITH
        (TABLOCK)
    (
        database_id,
        [option],
        option_value,
        [type],
        type_value,
        details,
        [state]
    )
    EXECUTE sys.sp_executesql
        @sql,
      N'@database_id integer',
        @database_id;

    IF @troubleshoot_performance = 1
    BEGIN
        SET STATISTICS XML OFF;

        EXECUTE sys.sp_executesql
            @troubleshoot_update,
          N'@current_table nvarchar(100)',
            @current_table;

        EXECUTE sys.sp_executesql
            @troubleshoot_info,
          N'@sql nvarchar(max),
            @current_table nvarchar(100)',
            @sql,
            @current_table;
    END;
END; /*End SQL 2022 views*/

FETCH NEXT
FROM @database_cursor
INTO @database_name;
END;

/*
This is where we start returning results
*/
IF EXISTS
   (
      SELECT
          1/0
      FROM #query_store_runtime_stats AS qsrs
   )
BEGIN
    SELECT
        @sql = @isolation_level,
        @current_table = 'selecting final results';

    SELECT
        @sql +=
        CONVERT
        (
            nvarchar(max),
        N'
SELECT
    x.*
FROM
(
    SELECT
        source = ''runtime_stats'',
        database_name = DB_NAME(qsrs.database_id),
        qsp.query_id,
        qsrs.plan_id,
        qsp.all_plan_ids,' +
        CASE
            WHEN @include_plan_hashes IS NOT NULL
            OR   @ignore_plan_hashes IS NOT NULL
            OR   @sort_order = 'plan count by hashes'
            OR   @expert_mode = 1
            THEN N'
        qsp.query_plan_hash,'
            ELSE N''
        END +
        CASE
            WHEN @include_query_hashes IS NOT NULL
            OR   @ignore_query_hashes IS NOT NULL
            OR   @sort_order = 'plan count by hashes'
            OR   @include_query_hash_totals = 1
            THEN N'
        qsq.query_hash,'
            ELSE N''
        END +
        CASE
            WHEN @include_sql_handles IS NOT NULL
            OR   @ignore_sql_handles IS NOT NULL
            THEN N'
        qsqt.statement_sql_handle,'
            ELSE N''
        END + N'
        qsrs.execution_type_desc,
        qsq.object_name,
        qsqt.query_sql_text,
        query_plan =
             CASE
                 WHEN TRY_CAST(qsp.query_plan AS xml) IS NOT NULL
                 THEN TRY_CAST(qsp.query_plan AS xml)
                 WHEN TRY_CAST(qsp.query_plan AS xml) IS NULL
                 THEN
                     (
                         SELECT
                             [processing-instruction(query_plan)] =
                                 N''-- '' + NCHAR(13) + NCHAR(10) +
                                 N''-- This is a huge query plan.'' + NCHAR(13) + NCHAR(10) +
                                 N''-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.'' + NCHAR(13) + NCHAR(10) +
                                 NCHAR(13) + NCHAR(10) +
                                 REPLACE(qsp.query_plan, N''<RelOp'', NCHAR(13) + NCHAR(10) + N''<RelOp'') +
                                 NCHAR(13) + NCHAR(10) COLLATE Latin1_General_Bin2
                         FOR XML
                             PATH(N''''),
                             TYPE
                     )
             END,
        qsp.compatibility_level,
'
        );

    /* Build column list according to mode (expert vs. non-expert) and format_output */
    SELECT
        @column_sql =
        (
            SELECT
                CASE
                    /* Non-formatted columns */
                    WHEN @format_output = 0
                    THEN
                            N'
                        ' +
                        cd.column_name +
                        N' = ' +
                        cd.column_source +
                        N','
                        /* Formatted columns with FORMAT function */
                        ELSE
                            N'
                        ' +
                        cd.column_name +
                        N' = ' +
                        CASE
                            WHEN cd.format_pattern IS NOT NULL
                            THEN N'FORMAT(' +
                                 cd.column_source +
                                 N', ''' +
                                 cd.format_pattern +
                                 N''')'
                            ELSE cd.column_source
                        END +
                        N','
                END
            FROM @ColumnDefinitions AS cd
            WHERE (@expert_mode = 1 OR cd.expert_only = 0) /* Only include expert columns in expert mode */
            AND
            (
                cd.is_conditional = 0  /* Either non-conditional columns */
                OR /* Or conditional columns where the condition is met */
                (
                   cd.is_conditional = 1
                   AND cd.condition_param IS NOT NULL
                   AND CASE
                           WHEN cd.condition_param = N'sql_2022_views'
                           THEN @sql_2022_views
                           WHEN cd.condition_param = N'new'
                           THEN @new
                           WHEN cd.condition_param = N'regression_mode'
                           THEN @regression_mode
                           WHEN cd.condition_param = N'include_query_hash_totals'
                           THEN @include_query_hash_totals
                           WHEN cd.condition_param = N'new_with_hash_totals'
                           THEN CASE
                                    WHEN @new = 1
                                    AND  @include_query_hash_totals = 1
                                    THEN 1
                                    ELSE 0
                                END
                           ELSE 0
                       END = cd.condition_value
                )
            )
            ORDER BY
                cd.column_id
            FOR
                XML
                PATH(''),
                TYPE
        ).value('.', 'nvarchar(max)');

    /* Remove the trailing comma */
    IF LEN(@column_sql) > 0
    BEGIN
        SET @column_sql =
            LEFT
            (
                @column_sql,
                LEN(@column_sql) - 1
            );
    END;

    /* Append the column SQL to the main SQL */
    SELECT
        @sql += @column_sql;

    /*
    Add on the from and stuff
    */
    SELECT
        @sql +=
    CONVERT
    (
        nvarchar(max),
        N'
    FROM #query_store_runtime_stats AS qsrs'
    );

    /*
    Bolt on any sort-helping tables.
    */
    IF @regression_mode = 1
    BEGIN
        SELECT
            @sql += N'
        JOIN #regression_changes AS regression
          ON  qsrs.plan_id = regression.plan_id
          AND qsrs.database_id = regression.database_id';
    END;

    IF @sort_order = 'plan count by hashes'
    BEGIN
        SELECT
            @sql += N'
        JOIN #plan_ids_with_query_hashes AS hashes
          ON  qsrs.plan_id = hashes.plan_id
          AND qsrs.database_id = hashes.database_id';
    END;

    IF @sort_order_is_a_wait = 1
    BEGIN
        SELECT
            @sql += N'
        JOIN #plan_ids_with_total_waits AS waits
          ON  qsrs.plan_id = waits.plan_id
          AND qsrs.database_id = waits.database_id';

        IF @regression_mode = 1
        BEGIN
            SELECT
                @sql += N'
        AND qsrs.from_regression_baseline = waits.from_regression_baseline';
        END;
    END;

/*Get more stuff, like query plans and query text*/
SELECT
    @sql +=
    CONVERT
    (
        nvarchar(max),
        N'
    CROSS APPLY
    (
        SELECT
            x.*
        FROM
        (
            SELECT
                qsp.*,
                pn =
                    ROW_NUMBER() OVER
                    (
                        PARTITION BY
                            qsp.query_plan_hash
                        ORDER BY
                            qsp.last_execution_time DESC
                    )
            FROM #query_store_plan AS qsp
            WHERE qsp.plan_id = qsrs.plan_id
            AND   qsp.database_id = qsrs.database_id
        ) AS x
        WHERE x.pn = 1
    ) AS qsp
    CROSS APPLY
    (
        SELECT TOP (1)
            qsqt.*
        FROM #query_store_query AS qsq
        JOIN #query_store_query_text AS qsqt
          ON  qsqt.query_text_id = qsq.query_text_id
          AND qsqt.database_id = qsq.database_id
        WHERE qsq.query_id = qsp.query_id
        AND   qsq.database_id = qsp.database_id
        ORDER BY
            qsq.last_execution_time DESC
    ) AS qsqt
    CROSS APPLY
    (
        SELECT TOP (1)
            qsq.*
        FROM #query_store_query AS qsq
        WHERE qsq.query_id = qsp.query_id
        AND   qsq.database_id = qsp.database_id
        ORDER BY
            qsq.last_execution_time DESC
    ) AS qsq'
    );

    /*
    Get wait stats if we can
    */
    IF
    (
        @new = 1
    )
    BEGIN
        SELECT
            @sql +=
        CONVERT
        (
            nvarchar(max),
            N'
    CROSS APPLY
    (
        SELECT TOP (1)
            top_waits =
                STUFF
                (
                    (
                       SELECT TOP (5)
                            '', '' +
                            qsws.wait_category_desc +
                            '' ('' + ' +
                            CASE
                                @format_output
                                WHEN 0
                                THEN N'
                            CONVERT
                            (
                                varchar(20),
                                SUM
                                (
                                    CONVERT
                                    (
                                        bigint,
                                        qsws.avg_query_wait_time_ms
                                    )
                                )
                            ) + '
                                 ELSE N'
                            FORMAT
                            (
                                SUM
                                (
                                    CONVERT
                                    (
                                        bigint,
                                        qsws.avg_query_wait_time_ms
                                    )
                                ),
                                ''N0''
                            ) + '
                            END + N' '' ms)''
                       FROM #query_store_wait_stats AS qsws
                       WHERE qsws.plan_id = qsrs.plan_id
                       AND   qsws.database_id = qsrs.database_id
                       GROUP BY
                           qsws.wait_category_desc
                       ORDER BY
                           SUM(qsws.avg_query_wait_time_ms) DESC
                       FOR XML
                           PATH(''''),
                           TYPE
                    ).value(''./text()[1]'', ''varchar(max)''),
                    1,
                    2,
                    ''''
                )
    ) AS w'
    );
    END; /*End wait stats query*/

    /*Strap on the query hash totals table*/
    IF @include_query_hash_totals = 1
    BEGIN
        SELECT
            @sql += N'
    JOIN #query_hash_totals AS qht
      ON  qsq.query_hash = qht.query_hash
      AND qsq.database_id = qht.database_id';
    END;

    SELECT
        @sql +=
    CONVERT
    (
        nvarchar(max),
        N'
) AS x
' + CASE WHEN @regression_mode = 1 THEN N'' ELSE N'WHERE x.n = 1 ' END
+ N'
ORDER BY
    ' +
    CASE @format_output
         WHEN 0
         THEN
             CASE WHEN @regression_mode = 1
             AND @regression_direction IN ('improved', 'better')
             THEN 'x.change_in_average_for_query_hash_since_regression_time_period ASC,
                   x.query_hash_from_regression_checking,
                   x.from_regression_baseline_time_period'
             WHEN @regression_mode = 1
             AND @regression_direction IN ('regressed', 'worse')
             THEN 'x.change_in_average_for_query_hash_since_regression_time_period DESC,
                   x.query_hash_from_regression_checking,
                   x.from_regression_baseline_time_period'
             WHEN @regression_mode = 1
             AND @regression_direction IN ('magnitude', 'absolute')
             THEN 'ABS(x.change_in_average_for_query_hash_since_regression_time_period) DESC,
                   x.query_hash_from_regression_checking,
                   x.from_regression_baseline_time_period'
             ELSE
             CASE @sort_order
                  WHEN 'cpu' THEN N'x.avg_cpu_time_ms'
                  WHEN 'logical reads' THEN N'x.avg_logical_io_reads_mb'
                  WHEN 'physical reads' THEN N'x.avg_physical_io_reads_mb'
                  WHEN 'writes' THEN N'x.avg_logical_io_writes_mb'
                  WHEN 'duration' THEN N'x.avg_duration_ms'
                  WHEN 'memory' THEN N'x.avg_query_max_used_memory_mb'
                  WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time_ms' END
                  WHEN 'executions' THEN N'x.count_executions'
                  WHEN 'recent' THEN N'x.last_execution_time'
                  WHEN 'rows' THEN N'x.avg_rowcount'
                  WHEN 'total cpu' THEN N'x.total_cpu_time_ms'
                  WHEN 'total logical reads' THEN N'x.total_logical_io_reads_mb'
                  WHEN 'total physical reads' THEN N'x.total_physical_io_reads_mb'
                  WHEN 'total writes' THEN N'x.total_logical_io_writes_mb'
                  WHEN 'total duration' THEN N'x.total_duration_ms'
                  WHEN 'total memory' THEN N'x.total_query_max_used_memory_mb'
                  WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'x.total_tempdb_space_used_mb' ELSE N'x.total_cpu_time_ms' END
                  WHEN 'total rows' THEN N'x.total_rowcount'
                  WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC,
    x.query_hash_from_hash_counting'
                  ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time_ms' END
             END END
         /*
         The ORDER BY is on the same level as the topmost SELECT, which is just SELECT x.*.
         This means that to sort formatted output, we have to un-format it.
         */
         WHEN 1
         THEN
             CASE WHEN @regression_mode = 1
                  AND @regression_direction IN ('improved', 'better')
                  THEN 'TRY_PARSE(REPLACE(x.change_in_average_for_query_hash_since_regression_time_period, ''%'', '''') AS decimal(19,2)) ASC,
                        x.query_hash_from_regression_checking,
                        x.from_regression_baseline_time_period'
                  WHEN @regression_mode = 1
                  AND @regression_direction IN ('regressed', 'worse')
                  THEN 'TRY_PARSE(REPLACE(x.change_in_average_for_query_hash_since_regression_time_period, ''%'', '''') AS decimal(19,2)) DESC,
                        x.query_hash_from_regression_checking,
                        x.from_regression_baseline_time_period'
                  WHEN @regression_mode = 1
                  AND @regression_direction IN ('magnitude', 'absolute')
                  THEN 'ABS(TRY_PARSE(REPLACE(x.change_in_average_for_query_hash_since_regression_time_period, ''%'', '''') AS decimal(19,2))) DESC,
                        x.query_hash_from_regression_checking,
                        x.from_regression_baseline_time_period'
             ELSE
             CASE @sort_order
                  WHEN 'cpu' THEN N'TRY_PARSE(x.avg_cpu_time_ms AS decimal(19,2))'
                  WHEN 'logical reads' THEN N'TRY_PARSE(x.avg_logical_io_reads_mb AS decimal(19,2))'
                  WHEN 'physical reads' THEN N'TRY_PARSE(x.avg_physical_io_reads_mb AS decimal(19,2))'
                  WHEN 'writes' THEN N'TRY_PARSE(x.avg_logical_io_writes_mb AS decimal(19,2))'
                  WHEN 'duration' THEN N'TRY_PARSE(x.avg_duration_ms AS decimal(19,2))'
                  WHEN 'memory' THEN N'TRY_PARSE(x.avg_query_max_used_memory_mb AS decimal(19,2))'
                  WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.avg_tempdb_space_used_mb AS decimal(19,2))' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS decimal(19,2))' END
                  WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS decimal(19,2))'
                  WHEN 'recent' THEN N'x.last_execution_time'
                  WHEN 'rows' THEN N'TRY_PARSE(x.avg_rowcount AS decimal(19,2))'
                  WHEN 'total cpu' THEN N'TRY_PARSE(x.total_cpu_time_ms AS decimal(19,2))'
                  WHEN 'total logical reads' THEN N'TRY_PARSE(x.total_logical_io_reads_mb AS decimal(19,2))'
                  WHEN 'total physical reads' THEN N'TRY_PARSE(x.total_physical_io_reads_mb AS decimal(19,2))'
                  WHEN 'total writes' THEN N'TRY_PARSE(x.total_logical_io_writes_mb AS decimal(19,2))'
                  WHEN 'total duration' THEN N'TRY_PARSE(x.total_duration_ms AS decimal(19,2))'
                  WHEN 'total memory' THEN N'TRY_PARSE(x.total_query_max_used_memory_mb AS decimal(19,2))'
                  WHEN 'total tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.total_tempdb_space_used_mb AS decimal(19,2))' ELSE N'TRY_PARSE(x.total_cpu_time_ms AS decimal(19,2))' END
                  WHEN 'total rows' THEN N'TRY_PARSE(x.total_rowcount AS decimal(19,2))'
                  WHEN 'plan count by hashes' THEN N'TRY_PARSE(x.plan_hash_count_for_query_hash AS decimal(19,2)) DESC,
    x.query_hash_from_hash_counting'
                  ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'TRY_PARSE(x.total_wait_time_from_sort_order_ms AS decimal(19,2))' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS decimal(19,2))' END
             END END
    END
             + N' DESC
OPTION(RECOMPILE);' + @nc10
    );

    IF @debug = 1
    BEGIN
        PRINT LEN(@sql);
        PRINT SUBSTRING(@sql, 0, 4000);
        PRINT SUBSTRING(@sql, 4001, 8000);
        PRINT SUBSTRING(@sql, 8001, 12000);
        PRINT SUBSTRING(@sql, 12001, 16000);
    END;

    EXECUTE sys.sp_executesql
        @sql,
      N'@utc_offset_string nvarchar(6),
        @timezone sysname',
        @utc_offset_string,
        @timezone;
END; /*End runtime stats main query*/
ELSE
BEGIN
    SELECT
        result =
            '#query_store_runtime_stats is empty';
END;

/*
Return special things: plan feedback, query hints, query variants, query text, wait stats, and query store options
This section handles all expert mode and special output formats
Format numeric values based on @format_output
*/
IF
(
    @expert_mode = 1
  OR
  (
       @only_queries_with_hints = 1
    OR @only_queries_with_feedback = 1
    OR @only_queries_with_variants = 1
  )
)
BEGIN
    /*
    SQL 2022+ features: plan feedback, query hints, and query variants
    */
    IF @sql_2022_views = 1
    BEGIN
        /*
        Handle query_store_plan_feedback
        */
        IF @expert_mode = 1
        OR @only_queries_with_feedback = 1
        BEGIN
            IF EXISTS
               (
                   SELECT
                       1/0
                   FROM #query_store_plan_feedback AS qspf
               )
            BEGIN
                SELECT
                    @current_table = 'selecting plan feedback';

                /*
                Use dynamic SQL to handle formatting differences based on @format_output
                */
                SELECT
                    @sql = @isolation_level;

                SELECT
                    @sql += N'
                SELECT
                    database_name =
                        DB_NAME(qspf.database_id),
                    qspf.plan_feedback_id,
                    qspf.plan_id,
                    qspf.feature_desc,
                    qspf.feedback_data,
                    qspf.state_desc,
                    create_time =
                        CASE
                            WHEN @timezone IS NULL
                            THEN
                                SWITCHOFFSET
                                (
                                    qspf.create_time,
                                    @utc_offset_string
                                )
                            WHEN @timezone IS NOT NULL
                            THEN qspf.create_time AT TIME ZONE @timezone
                        END,
                    create_time_utc =
                        qspf.create_time,
                    last_updated_time =
                        CASE
                            WHEN @timezone IS NULL
                            THEN
                                SWITCHOFFSET
                                (
                                    qspf.last_updated_time,
                                    @utc_offset_string
                                )
                            WHEN @timezone IS NOT NULL
                            THEN qspf.last_updated_time AT TIME ZONE @timezone
                        END,
                    last_updated_time_utc =
                        qspf.last_updated_time
                FROM #query_store_plan_feedback AS qspf
                ORDER BY
                    qspf.plan_id
                OPTION(RECOMPILE);' + @nc10;

                IF @debug = 1
                BEGIN
                    PRINT LEN(@sql);
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql,
                  N'@timezone sysname, @utc_offset_string nvarchar(6)',
                    @timezone, @utc_offset_string;
            END;
            ELSE IF @only_queries_with_feedback = 1
            BEGIN
                SELECT
                    result = '#query_store_plan_feedback is empty';
            END;
        END; /*@only_queries_with_feedback*/

        IF @expert_mode = 1
        OR @only_queries_with_hints = 1
        BEGIN
            IF EXISTS
               (
                   SELECT
                       1/0
                   FROM #query_store_query_hints AS qsqh
               )
            BEGIN
                SELECT
                    @current_table = 'selecting query hints';

                /*
                Use dynamic SQL to handle formatting differences based on @format_output
                */
                SELECT
                    @sql = @isolation_level;

                SELECT
                    @sql += N'
                SELECT
                    database_name =
                        DB_NAME(qsqh.database_id),
                    qsqh.query_hint_id,
                    qsqh.query_id,
                    qsqh.query_hint_text,
                    remove_hint =
                        ''EXECUTE '' + QUOTENAME(DB_NAME(qsqh.database_id)) + ''.sys.sp_query_store_clear_hints @query_id = '' + CONVERT(nvarchar(20), qsqh.query_id) + '';'',
                    qsqh.last_query_hint_failure_reason_desc,
                    query_hint_failure_count = ' +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsqh.query_hint_failure_count, ''N0'')'
                        ELSE N'qsqh.query_hint_failure_count'
                    END + N',
                    qsqh.source_desc
                FROM #query_store_query_hints AS qsqh
                ORDER BY
                    qsqh.query_id
                OPTION(RECOMPILE);' + @nc10;

                IF @debug = 1
                BEGIN
                    PRINT LEN(@sql);
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql;
            END;
            ELSE IF @only_queries_with_hints = 1
            BEGIN
                SELECT
                    result = '#query_store_query_hints is empty';
            END;
        END; /*@only_queries_with_hints*/

        IF @expert_mode = 1
        OR @only_queries_with_variants = 1
        BEGIN
            IF EXISTS
               (
                   SELECT
                       1/0
                   FROM #query_store_query_variant AS qsqv
               )
            BEGIN
                SELECT
                    @current_table = 'selecting query variants';

                /*
                Use dynamic SQL to handle formatting differences based on @format_output
                */
                SELECT
                    @sql = @isolation_level;

                SELECT
                    @sql += N'
                SELECT
                    database_name =
                        DB_NAME(qsqv.database_id),
                    qsqv.query_variant_query_id,
                    qsqv.parent_query_id,
                    qsqv.dispatcher_plan_id
                FROM #query_store_query_variant AS qsqv
                ORDER BY
                    qsqv.parent_query_id
                OPTION(RECOMPILE);' + @nc10;

                IF @debug = 1
                BEGIN
                    PRINT LEN(@sql);
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql;
            END;
            ELSE IF @only_queries_with_variants = 1
            BEGIN
                SELECT
                    result = '#query_store_query_variant is empty';
            END;
        END; /*@only_queries_with_variants*/

        IF
        (
            @sql_2022_views = 1
        AND @ags_present = 1
        )
        BEGIN
            IF @expert_mode = 1
            BEGIN
                IF EXISTS
                (
                    SELECT
                        1/0
                    FROM #query_store_replicas AS qsr
                    JOIN #query_store_plan_forcing_locations AS qspfl
                      ON  qsr.replica_group_id = qspfl.replica_group_id
                      AND qsr.database_id = qspfl.database_id
                )
                BEGIN
                    SELECT
                        @current_table = 'selecting #query_store_replicas and #query_store_plan_forcing_locations';

                    SELECT
                        database_name =
                            DB_NAME(qsr.database_id),
                        qsr.replica_group_id,
                        qsr.role_type,
                        qsr.replica_name,
                        qspfl.plan_forcing_location_id,
                        qspfl.query_id,
                        qspfl.plan_id,
                        qspfl.replica_group_id
                    FROM #query_store_replicas AS qsr
                    JOIN #query_store_plan_forcing_locations AS qspfl
                      ON qsr.replica_group_id = qspfl.replica_group_id
                    ORDER BY
                        qsr.replica_group_id
                    OPTION(RECOMPILE);
                END;
                ELSE
                BEGIN
                    SELECT
                        result = 'Availability Group information is empty';
                END;
            END;
        END; /*@ags_present*/

        IF @expert_mode = 1
        BEGIN
            IF EXISTS
               (
                   SELECT
                       1/0
                   FROM #database_automatic_tuning_configurations AS datc
               )
            BEGIN
                SELECT
                    @current_table = 'selecting #database_automatic_tuning_configurations';

                SELECT
                    database_name =
                        DB_NAME(datc.database_id),
                    datc.[option],
                    datc.option_value,
                    datc.[type],
                    datc.type_value,
                    datc.details,
                    datc.[state]
                FROM #database_automatic_tuning_configurations AS datc
                ORDER BY
                    datc.[option]
                OPTION(RECOMPILE);
            END;
        END;
    END; /*End 2022 views*/

    IF @expert_mode = 1
    BEGIN
        IF EXISTS
           (
              SELECT
                  1/0
              FROM #query_store_query AS qsq
           )
        BEGIN
            SELECT
                @current_table = 'selecting compilation stats';

            /*
            Use dynamic SQL to handle formatting differences based on @format_output
            */
            SELECT
                @sql = @isolation_level;

            SELECT
                @sql += N'
            SELECT
                x.*
            FROM
            (
                SELECT
                    source =
                        ''compilation_stats'',
                    database_name =
                        DB_NAME(qsq.database_id),
                    qsq.query_id,
                    qsq.object_name,
                    qsq.query_text_id,
                    qsq.query_parameterization_type_desc,
                    initial_compile_start_time =
                        CASE
                            WHEN @timezone IS NULL
                            THEN
                                SWITCHOFFSET
                                (
                                    qsq.initial_compile_start_time,
                                    @utc_offset_string
                                )
                            WHEN @timezone IS NOT NULL
                            THEN qsq.initial_compile_start_time AT TIME ZONE @timezone
                        END,
                    initial_compile_start_time_utc =
                        qsq.initial_compile_start_time,
                    last_compile_start_time =
                        CASE
                            WHEN @timezone IS NULL
                            THEN
                                SWITCHOFFSET
                                (
                                    qsq.last_compile_start_time,
                                    @utc_offset_string
                                )
                            WHEN @timezone IS NOT NULL
                            THEN qsq.last_compile_start_time AT TIME ZONE @timezone
                        END,
                    last_compile_start_time_utc =
                        qsq.last_compile_start_time,
                    last_execution_time =
                        CASE
                            WHEN @timezone IS NULL
                            THEN
                                SWITCHOFFSET
                                (
                                    qsq.last_execution_time,
                                    @utc_offset_string
                                )
                            WHEN @timezone IS NOT NULL
                            THEN qsq.last_execution_time AT TIME ZONE @timezone
                        END,
                    last_execution_time_utc =
                        qsq.last_execution_time,
                    count_compiles = ' +
                    CONVERT
                    (
                        nvarchar(max),
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.count_compiles, ''N0'')'
                            ELSE N'qsq.count_compiles'
                        END + N',
                    avg_compile_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_compile_duration_ms, ''N0'')'
                            ELSE N'qsq.avg_compile_duration_ms'
                        END + N',
                    total_compile_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_compile_duration_ms, ''N0'')'
                            ELSE N'qsq.total_compile_duration_ms'
                        END + N',
                    last_compile_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_compile_duration_ms, ''N0'')'
                            ELSE N'qsq.last_compile_duration_ms'
                        END + N',
                    avg_bind_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_bind_duration_ms, ''N0'')'
                            ELSE N'qsq.avg_bind_duration_ms'
                        END + N',
                    total_bind_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_bind_duration_ms, ''N0'')'
                            ELSE N'qsq.total_bind_duration_ms'
                        END + N',
                    last_bind_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_bind_duration_ms, ''N0'')'
                            ELSE N'qsq.last_bind_duration_ms'
                        END + N',
                    avg_bind_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_bind_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.avg_bind_cpu_time_ms'
                        END + N',
                    total_bind_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_bind_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.total_bind_cpu_time_ms'
                        END + N',
                    last_bind_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_bind_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.last_bind_cpu_time_ms'
                        END + N',
                    avg_optimize_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_optimize_duration_ms, ''N0'')'
                            ELSE N'qsq.avg_optimize_duration_ms'
                        END + N',
                    total_optimize_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_optimize_duration_ms, ''N0'')'
                            ELSE N'qsq.total_optimize_duration_ms'
                        END + N',
                    last_optimize_duration_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_optimize_duration_ms, ''N0'')'
                            ELSE N'qsq.last_optimize_duration_ms'
                        END + N',
                    avg_optimize_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_optimize_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.avg_optimize_cpu_time_ms'
                        END + N',
                    total_optimize_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_optimize_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.total_optimize_cpu_time_ms'
                        END + N',
                    last_optimize_cpu_time_ms = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_optimize_cpu_time_ms, ''N0'')'
                            ELSE N'qsq.last_optimize_cpu_time_ms'
                        END + N',
                    avg_compile_memory_mb = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.avg_compile_memory_mb, ''N0'')'
                            ELSE N'qsq.avg_compile_memory_mb'
                        END + N',
                    total_compile_memory_mb = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.total_compile_memory_mb, ''N0'')'
                            ELSE N'qsq.total_compile_memory_mb'
                        END + N',
                    last_compile_memory_mb = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.last_compile_memory_mb, ''N0'')'
                            ELSE N'qsq.last_compile_memory_mb'
                        END + N',
                    max_compile_memory_mb = ' +
                        CASE
                            WHEN @format_output = 1
                            THEN N'FORMAT(qsq.max_compile_memory_mb, ''N0'')'
                            ELSE N'qsq.max_compile_memory_mb'
                        END
                   ) + N',
                    qsq.query_hash,
                    qsq.batch_sql_handle,
                    qsqt.statement_sql_handle,
                    qsq.last_compile_batch_sql_handle,
                    qsq.last_compile_batch_offset_start,
                    qsq.last_compile_batch_offset_end,
                    ROW_NUMBER() OVER
                    (
                        PARTITION BY
                            qsq.query_id,
                            qsq.query_text_id
                        ORDER BY
                            qsq.query_id
                    ) AS n
                FROM #query_store_query AS qsq
                CROSS APPLY
                (
                    SELECT TOP (1)
                        qsqt.*
                    FROM #query_store_query_text AS qsqt
                    WHERE qsqt.query_text_id = qsq.query_text_id
                    AND   qsqt.database_id = qsq.database_id
                ) AS qsqt
            ) AS x
            WHERE x.n = 1
            ORDER BY
                x.query_id
            OPTION(RECOMPILE);' + @nc10;

            IF @debug = 1
            BEGIN
                PRINT LEN(@sql);
                PRINT @sql;
            END;

            EXECUTE sys.sp_executesql
                @sql,
              N'@timezone sysname, @utc_offset_string nvarchar(6)',
                @timezone, @utc_offset_string;

        END; /*End compilation query section*/
        ELSE
        BEGIN
            SELECT
                result =
                    '#query_store_query is empty';
        END;
    END; /*compilation stats*/

    IF @rc > 0
    BEGIN
        SELECT
            @current_table = 'selecting resource stats',
            @sql = @isolation_level;

        SELECT
            @sql +=
        CONVERT
        (
            nvarchar(max),
            N'
        SELECT
            source =
                ''resource_stats'',
            database_name =
                DB_NAME(qsq.database_id),
            qsq.query_id,
            qsq.object_name,
            total_grant_mb = '
            +
            CONVERT
            (
                nvarchar(max),
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.total_grant_mb, ''N0'')'
                ELSE N'qsqt.total_grant_mb'
            END
            + N',
            last_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.last_grant_mb, ''N0'')'
                ELSE N'qsqt.last_grant_mb'
            END
            + N',
            min_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.min_grant_mb, ''N0'')'
                ELSE N'qsqt.min_grant_mb'
            END
            + N',
            max_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.max_grant_mb, ''N0'')'
                ELSE N'qsqt.max_grant_mb'
            END
            + N',
            total_used_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.total_used_grant_mb, ''N0'')'
                ELSE N'qsqt.total_used_grant_mb'
            END
            + N',
            last_used_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.last_used_grant_mb, ''N0'')'
                ELSE N'qsqt.last_used_grant_mb'
            END
            + N',
            min_used_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.min_used_grant_mb, ''N0'')'
                ELSE N'qsqt.min_used_grant_mb'
            END
            + N',
            max_used_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.max_used_grant_mb, ''N0'')'
                ELSE N'qsqt.max_used_grant_mb'
            END
            + N',
            total_ideal_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.total_ideal_grant_mb, ''N0'')'
                ELSE N'qsqt.total_ideal_grant_mb'
            END
            + N',
            last_ideal_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.last_ideal_grant_mb, ''N0'')'
                ELSE N'qsqt.last_ideal_grant_mb'
            END
            + N',
            min_ideal_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.min_ideal_grant_mb, ''N0'')'
                ELSE N'qsqt.min_ideal_grant_mb'
            END
            + N',
            max_ideal_grant_mb = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.max_ideal_grant_mb, ''N0'')'
                ELSE N'qsqt.max_ideal_grant_mb'
            END
            + N',
            total_reserved_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.total_reserved_threads, ''N0'')'
                ELSE N'qsqt.total_reserved_threads'
            END
            + N',
            last_reserved_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.last_reserved_threads, ''N0'')'
                ELSE N'qsqt.last_reserved_threads'
            END
            + N',
            min_reserved_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.min_reserved_threads, ''N0'')'
                ELSE N'qsqt.min_reserved_threads'
            END
            + N',
            max_reserved_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.max_reserved_threads, ''N0'')'
                ELSE N'qsqt.max_reserved_threads'
            END
            + N',
            total_used_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.total_used_threads, ''N0'')'
                ELSE N'qsqt.total_used_threads'
            END
            + N',
            last_used_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.last_used_threads, ''N0'')'
                ELSE N'qsqt.last_used_threads'
            END
            + N',
            min_used_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.min_used_threads, ''N0'')'
                ELSE N'qsqt.min_used_threads'
            END
            + N',
            max_used_threads = '
            +
            CASE
                WHEN @format_output = 1
                THEN N'FORMAT(qsqt.max_used_threads, ''N0'')'
                ELSE N'qsqt.max_used_threads'
            END
            ) + N'
        FROM #query_store_query AS qsq
        JOIN #query_store_query_text AS qsqt
          ON  qsq.query_text_id = qsqt.query_text_id
          AND qsq.database_id = qsqt.database_id
        WHERE
        (
            qsqt.total_grant_mb IS NOT NULL
        OR qsqt.total_reserved_threads IS NOT NULL
        )
        ORDER BY
            qsq.query_id
        OPTION(RECOMPILE);' + @nc10
        );

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql;

    END; /*End resource stats query*/
    ELSE
    BEGIN
        SELECT
            result =
                '#dm_exec_query_stats is empty';
    END;

    IF @new = 1
    BEGIN
        IF @expert_mode = 1
        BEGIN
            IF EXISTS
            (
                SELECT
                    1/0
                FROM #query_store_wait_stats AS qsws
            )
            BEGIN
                /*
                Wait stats by query
                */
                SELECT
                    @current_table = 'selecting wait stats by query',
                    @sql = @isolation_level;

                SELECT
                    @sql +=
                CONVERT
                (
                    nvarchar(max),
                    N'
                SELECT
                    source =
                        ''query_store_wait_stats_by_query'',
                    database_name =
                        DB_NAME(qsws.database_id),
                    qsws.plan_id,
                    x.object_name,
                    qsws.wait_category_desc,
                    total_query_wait_time_ms = '
                    +
                    CONVERT
                    (
                        nvarchar(max),
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsws.total_query_wait_time_ms, ''N0'')'
                        ELSE N'qsws.total_query_wait_time_ms'
                    END
                    + N',
                    total_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(x.total_duration_ms, ''N0'')'
                        ELSE N'x.total_duration_ms'
                    END
                    + N',
                    avg_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsws.avg_query_wait_time_ms, ''N0'')'
                        ELSE N'qsws.avg_query_wait_time_ms'
                    END
                    + N',
                    avg_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(x.avg_duration_ms, ''N0'')'
                        ELSE N'x.avg_duration_ms'
                    END
                    + N',
                    last_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsws.last_query_wait_time_ms, ''N0'')'
                        ELSE N'qsws.last_query_wait_time_ms'
                    END
                    + N',
                    last_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(x.last_duration_ms, ''N0'')'
                        ELSE N'x.last_duration_ms'
                    END
                    + N',
                    min_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsws.min_query_wait_time_ms, ''N0'')'
                        ELSE N'qsws.min_query_wait_time_ms'
                    END
                    + N',
                    min_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(x.min_duration_ms, ''N0'')'
                        ELSE N'x.min_duration_ms'
                    END
                    + N',
                    max_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(qsws.max_query_wait_time_ms, ''N0'')'
                        ELSE N'qsws.max_query_wait_time_ms'
                    END
                    + N',
                    max_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(x.max_duration_ms, ''N0'')'
                        ELSE N'x.max_duration_ms'
                    END
                    ) + N'
                FROM #query_store_wait_stats AS qsws
                CROSS APPLY
                (
                    SELECT
                        qsrs.avg_duration_ms,
                        qsrs.last_duration_ms,
                        qsrs.min_duration_ms,
                        qsrs.max_duration_ms,
                        qsrs.total_duration_ms,
                        qsq.object_name
                    FROM #query_store_runtime_stats AS qsrs
                    JOIN #query_store_plan AS qsp
                      ON  qsrs.plan_id = qsp.plan_id
                      AND qsrs.database_id = qsp.database_id
                    JOIN #query_store_query AS qsq
                      ON  qsp.query_id = qsq.query_id
                      AND qsp.database_id = qsq.database_id
                    WHERE qsws.plan_id = qsrs.plan_id
                    AND   qsws.database_id = qsrs.database_id
                ) AS x
                ORDER BY
                    qsws.plan_id,
                    qsws.total_query_wait_time_ms DESC
                OPTION(RECOMPILE);' + @nc10
                );

                IF @debug = 1
                BEGIN
                    PRINT LEN(@sql);
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql;

                /*
                Wait stats in total
                */
                SELECT
                    @current_table = 'selecting wait stats in total',
                    @sql = @isolation_level;

                SELECT
                    @sql +=
                CONVERT
                (
                    nvarchar(max),
                    N'
                SELECT
                    source =
                        ''query_store_wait_stats_total'',
                    database_name =
                        DB_NAME(qsws.database_id),
                    qsws.wait_category_desc,
                    total_query_wait_time_ms = '
                    +
                    CONVERT
                    (
                        nvarchar(max),
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(qsws.total_query_wait_time_ms), ''N0'')'
                        ELSE N'SUM(qsws.total_query_wait_time_ms)'
                    END
                    + N',
                    total_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(x.total_duration_ms), ''N0'')'
                        ELSE N'SUM(x.total_duration_ms)'
                    END
                    + N',
                    avg_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(qsws.avg_query_wait_time_ms), ''N0'')'
                        ELSE N'SUM(qsws.avg_query_wait_time_ms)'
                    END
                    + N',
                    avg_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(x.avg_duration_ms), ''N0'')'
                        ELSE N'SUM(x.avg_duration_ms)'
                    END
                    + N',
                    last_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(qsws.last_query_wait_time_ms), ''N0'')'
                        ELSE N'SUM(qsws.last_query_wait_time_ms)'
                    END
                    + N',
                    last_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(x.last_duration_ms), ''N0'')'
                        ELSE N'SUM(x.last_duration_ms)'
                    END
                    + N',
                    min_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(qsws.min_query_wait_time_ms), ''N0'')'
                        ELSE N'SUM(qsws.min_query_wait_time_ms)'
                    END
                    + N',
                    min_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(x.min_duration_ms), ''N0'')'
                        ELSE N'SUM(x.min_duration_ms)'
                    END
                    + N',
                    max_query_wait_time_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(qsws.max_query_wait_time_ms), ''N0'')'
                        ELSE N'SUM(qsws.max_query_wait_time_ms)'
                    END
                    + N',
                    max_query_duration_ms = '
                    +
                    CASE
                        WHEN @format_output = 1
                        THEN N'FORMAT(SUM(x.max_duration_ms), ''N0'')'
                        ELSE N'SUM(x.max_duration_ms)'
                    END
                    ) + N'
                FROM #query_store_wait_stats AS qsws
                CROSS APPLY
                (
                    SELECT
                        qsrs.avg_duration_ms,
                        qsrs.last_duration_ms,
                        qsrs.min_duration_ms,
                        qsrs.max_duration_ms,
                        qsrs.total_duration_ms,
                        qsq.object_name
                    FROM #query_store_runtime_stats AS qsrs
                    JOIN #query_store_plan AS qsp
                      ON  qsrs.plan_id = qsp.plan_id
                      AND qsrs.database_id = qsp.database_id
                    JOIN #query_store_query AS qsq
                      ON  qsp.query_id = qsq.query_id
                      AND qsp.database_id = qsq.database_id
                    WHERE qsws.plan_id = qsrs.plan_id
                ) AS x
                GROUP BY
                    qsws.wait_category_desc,
                    qsws.database_id
                ORDER BY
                    SUM(qsws.total_query_wait_time_ms) DESC
                OPTION(RECOMPILE);' + @nc10
                );

                IF @debug = 1
                BEGIN
                    PRINT LEN(@sql);
                    PRINT @sql;
                END;

                EXECUTE sys.sp_executesql
                    @sql;
            END;
            ELSE
            BEGIN
                SELECT
                    result =
                        '#query_store_wait_stats is empty' +
                        CASE
                            WHEN
                            (
                                  @product_version = 13
                              AND @azure = 0
                            )
                            THEN ' because it''s not available < 2017'
                            WHEN EXISTS
                                 (
                                     SELECT
                                         1/0
                                     FROM #database_query_store_options AS dqso
                                     WHERE dqso.wait_stats_capture_mode_desc <> N'ON'
                                 )
                            AND EXISTS
                                (
                                    SELECT
                                        1/0
                                    FROM #database_query_store_options AS dqso
                                    WHERE dqso.wait_stats_capture_mode_desc = N'ON'
                                )
                            THEN ' because we ignore wait stats if you have disabled capturing them in your Query Store options and everywhere that had it enabled had no data'
                            WHEN EXISTS
                                 (
                                     SELECT
                                         1/0
                                     FROM #database_query_store_options AS dqso
                                     WHERE dqso.wait_stats_capture_mode_desc <> N'ON'
                                 )
                            THEN ' because we ignore wait stats if you have disabled capturing them in your Query Store options'
                            ELSE ' for the queries in the results'
                        END;
            END;
        END;
    END; /*End wait stats queries*/

    IF @expert_mode = 1
    BEGIN
        SELECT
            @current_table = 'selecting query store options',
            @sql = @isolation_level;

        SELECT
            @sql +=
        CONVERT
        (
            nvarchar(max),
            N'
        SELECT
            source =
                ''query_store_options'',
            database_name =
                DB_NAME(dqso.database_id),
            dqso.desired_state_desc,
            dqso.actual_state_desc,
            dqso.readonly_reason,
            dqso.current_storage_size_mb,
            dqso.flush_interval_seconds,
            dqso.interval_length_minutes,
            dqso.max_storage_size_mb,
            dqso.stale_query_threshold_days,
            dqso.max_plans_per_query,
            dqso.query_capture_mode_desc,'
            +
            CASE
                WHEN
                (
                    @azure = 1
                OR @product_version > 13
                )
                THEN N'
            dqso.wait_stats_capture_mode_desc,'
                ELSE N''
            END
            +
            CASE
                WHEN
                (
                    @azure = 1
                OR @product_version > 14
                )
                THEN N'
            dqso.capture_policy_execution_count,
            dqso.capture_policy_total_compile_cpu_time_ms,
            dqso.capture_policy_total_execution_cpu_time_ms,
            dqso.capture_policy_stale_threshold_hours,'
                ELSE N''
            END
        );

        SELECT
            @sql +=
        CONVERT
        (
            nvarchar(max),
            N'
            dqso.size_based_cleanup_mode_desc
        FROM #database_query_store_options AS dqso
        OPTION(RECOMPILE);' + @nc10
        );

        IF @debug = 1
        BEGIN
            PRINT LEN(@sql);
            PRINT @sql;
        END;

        EXECUTE sys.sp_executesql
            @sql;
    END;
END; /*End Expert Mode*/

IF @query_store_trouble = 1
BEGIN
    SELECT
        query_store_trouble =
             'Query Store may be in a disagreeable state',
        database_name =
            DB_NAME(qst.database_id),
        qst.desired_state_desc,
        qst.actual_state_desc,
        qst.readonly_reason,
        qst.current_storage_size_mb,
        qst.flush_interval_seconds,
        qst.interval_length_minutes,
        qst.max_storage_size_mb,
        qst.stale_query_threshold_days,
        qst.max_plans_per_query,
        qst.query_capture_mode_desc,
        qst.size_based_cleanup_mode_desc
    FROM #query_store_trouble AS qst
    OPTION(RECOMPILE);
END;

/*
Return help table, unless told not to
*/
IF
(
    @hide_help_table <> 1
)
BEGIN
    SELECT
        x.all_done,
        x.period,
        x.databases,
        x.support,
        x.help,
        x.problems,
        x.performance,
        x.version_and_date,
        x.thanks
    FROM
    (
        SELECT
            sort =
                1,
            period =
                N'query store data for period ' +
                CONVERT
                (
                    nvarchar(19),
                    ISNULL
                    (
                        @start_date_original,
                        DATEADD
                        (
                            DAY,
                            -7,
                            DATEDIFF
                            (
                                DAY,
                                '19000101',
                                SYSDATETIME()
                            )
                        )
                    ),
                    21
                ) +
                N' through ' +
                CONVERT
                (
                    nvarchar(19),
                    ISNULL
                    (
                        @end_date_original,
                        SYSDATETIME()
                    ),
                    21
                ),
            all_done =
                'brought to you by darling data!',
            databases =
                N'processed: ' +
                CASE
                    WHEN @get_all_databases = 0
                    THEN ISNULL(@database_name, N'None')
                    ELSE
                        ISNULL
                        (
                            STUFF
                            (
                                (
                                    SELECT
                                        N', ' +
                                        d.database_name
                                    FROM #databases AS d
                                    ORDER BY
                                        d.database_name
                                    FOR
                                        XML
                                        PATH(''),
                                        TYPE
                                ).value('.', 'nvarchar(max)'),
                                1,
                                2,
                                N''
                            ),
                            N'None'
                        )
                END,
            support =
                'for support, head over to github',
            help =
                'for local help, use @help = 1',
            problems =
                'to debug issues, use @debug = 1;',
            performance =
                'if this runs slowly, use to get query plans',
            version_and_date =
                N'version: ' + CONVERT(nvarchar(10), @version),
            thanks =
                'thanks for using sp_QuickieStore!'

        UNION ALL

        SELECT
            sort =
                2,
            period =
                N'query store data for period ' +
                CONVERT
                (
                    nvarchar(19),
                    ISNULL
                    (
                        @start_date_original,
                        DATEADD
                        (
                            DAY,
                            -7,
                            DATEDIFF
                            (
                                DAY,
                                '19000101',
                                SYSDATETIME()
                            )
                        )
                    ),
                    21
                ) +
                N' through ' +
                CONVERT
                (
                    nvarchar(19),
                    ISNULL
                    (
                        @end_date_original,
                        SYSDATETIME()
                    ),
                    21
                ),
            all_done =
                'https://www.erikdarling.com/',
            databases =
                N'skipped: ' +
                ISNULL
                (
                    STUFF
                    (
                        (
                            SELECT
                                N', ' +
                                rbs.database_name +
                                N' (' +
                                rbs.reason +
                                N')'
                            FROM #requested_but_skipped_databases AS rbs
                            ORDER BY
                                rbs.database_name
                            FOR
                                XML
                                PATH(''),
                                TYPE
                        ).value('.', 'nvarchar(max)'),
                        1,
                        2,
                        N''
                    ),
                    N'None'
                ),
            support =
                'https://code.erikdarling.com',
            help =
                'EXECUTE sp_QuickieStore @help = 1;',
            problems =
                'EXECUTE sp_QuickieStore @debug = 1;',
            performance =
                'EXECUTE sp_QuickieStore @troubleshoot_performance = 1;',
            version_and_date =
                N'version date: ' + CONVERT(nvarchar(10), @version_date, 23),
            thanks =
                'i hope you find it useful, or whatever'
    ) AS x
    ORDER BY
        x.sort;
END; /*End hide_help_table <> 1 */

END TRY

/*Error handling!*/
BEGIN CATCH
    /*
    Where the error happened and the message
    */
    IF @current_table IS NOT NULL
    BEGIN
        RAISERROR('current dynamic activity', 10, 1) WITH NOWAIT;
        RAISERROR('error while %s with @expert mode = %i and format_output = %i', 10, 1, @current_table, @em, @fo) WITH NOWAIT;
    END;

    /*
    Query that caused the error
    */
    IF @sql IS NOT NULL
    BEGIN
        RAISERROR('current dynamic sql:', 10, 1) WITH NOWAIT;
        RAISERROR('%s', 10, 1, @sql) WITH NOWAIT;
    END;

    IF @debug = 1
    BEGIN
        GOTO DEBUG;
    END;
    IF @debug = 0
    BEGIN;
        THROW;
    END;
END CATCH;

/*
Debug elements!
*/
DEBUG:
IF @debug = 1
BEGIN
    SELECT
        parameter_type =
            'procedure_parameters',
        database_name =
            @database_name,
        sort_order =
            @sort_order,
        [top] =
            @top,
        start_date =
            @start_date,
        end_date =
            @end_date,
        timezone =
            @timezone,
        execution_count =
            @execution_count,
        duration_ms =
            @duration_ms,
        execution_type_desc =
            @execution_type_desc,
        procedure_schema =
            @procedure_schema,
        procedure_name =
            @procedure_name,
        include_plan_ids =
            @include_plan_ids,
        include_query_ids =
            @include_query_ids,
        include_query_hashes =
            @include_query_hashes,
        include_plan_hashes =
            @include_plan_hashes,
        include_sql_handles =
            @include_sql_handles,
        ignore_plan_ids =
            @ignore_plan_ids,
        ignore_query_ids =
            @ignore_query_ids,
        ignore_query_hashes =
            @ignore_query_hashes,
        ignore_plan_hashes =
            @ignore_plan_hashes,
        ignore_sql_handles =
            @ignore_sql_handles,
        query_text_search =
            @query_text_search,
        query_text_search_not =
            @query_text_search_not,
        escape_brackets =
            @escape_brackets,
        escape_character =
            @escape_character,
        only_query_with_hints =
            @only_queries_with_hints,
        only_query_with_feedback =
            @only_queries_with_feedback,
        only_query_with_variants =
            @only_queries_with_variants,
        only_queries_with_forced_plans =
            @only_queries_with_forced_plans,
        only_queries_with_forced_plan_failures =
            @only_queries_with_forced_plan_failures,
        wait_filter =
            @wait_filter,
        query_type =
            @query_type,
        expert_mode =
            @expert_mode,
        hide_help_table =
            @hide_help_table,
        format_output =
            @format_output,
        get_all_databases =
            @get_all_databases,
        include_databases =
            @include_databases,
        exclude_databases =
            @exclude_databases,
        workdays =
            @workdays,
        work_start =
            @work_start,
        work_end =
            @work_end,
        regression_baseline_start_date =
            @regression_baseline_start_date,
        regression_baseline_end_date =
            @regression_baseline_end_date,
        regression_comparator =
            @regression_comparator,
        regression_direction =
            @regression_direction,
        include_query_hash_totals =
            @include_query_hash_totals,
        include_maintenance =
            @include_maintenance,
        help =
            @help,
        debug =
            @debug,
        troubleshoot_performance =
            @troubleshoot_performance,
        version =
            @version,
        version_date =
            @version_date;

    SELECT
        parameter_type =
            'declared_variables',
        azure =
            @azure,
        engine =
            @engine,
        product_version =
            @product_version,
        database_id =
            @database_id,
        database_name_quoted =
            @database_name_quoted,
        procedure_name_quoted =
            @procedure_name_quoted,
        collation =
            @collation,
        new =
            @new,
        sql =
            @sql,
         len_sql =
             LEN(@sql),
        isolation_level =
            @isolation_level,
        parameters =
            @parameters,
        plans_top =
            @plans_top,
        queries_top =
            @queries_top,
        nc10 =
            @nc10,
        where_clause =
            @where_clause,
        regression_where_clause =
            @regression_where_clause,
        procedure_exists =
            @procedure_exists,
        query_store_exists =
            @query_store_exists,
        query_store_trouble =
            @query_store_trouble,
        query_store_waits_enabled =
            @query_store_waits_enabled,
        sort_order_is_a_wait =
            @sort_order_is_a_wait,
        sql_2022_views =
            @sql_2022_views,
        ags_present =
            @ags_present,
        string_split_ints =
            @string_split_ints,
        string_split_strings =
            @string_split_strings,
        current_table =
            @current_table,
        troubleshoot_insert =
            @troubleshoot_insert,
        troubleshoot_update =
            @troubleshoot_update,
        troubleshoot_info =
            @troubleshoot_info,
        rc =
            @rc,
       em =
           @em,
       fo =
          @fo,
       start_date_original =
           @start_date_original,
       end_date_original =
           @end_date_original,
       regression_baseline_start_date_original =
           @regression_baseline_start_date_original,
       regression_baseline_end_date_original =
           @regression_baseline_end_date_original,
       regression_mode =
           @regression_mode,
       timezone =
           @timezone,
       utc_minutes_difference =
           @utc_minutes_difference,
       utc_offset_string =
           @utc_offset_string,
       df =
           @df,
       work_start_utc =
           @work_start_utc,
       work_end_utc =
           @work_end_utc,
       column_sql =
           @column_sql,
       param_name =
           @param_name,
       param_value =
           @param_value,
       temp_table =
           @temp_table,
       column_name =
           @column_name,
       data_type =
           @data_type,
       is_include =
           @is_include,
       requires_secondary_processing =
           @requires_secondary_processing,
       split_sql =
           @split_sql;

    SELECT
        table_name = '@ColumnDefinitions',
        cd.*
    FROM @ColumnDefinitions AS cd
    WHERE cd.column_id LIKE '%15'
    ORDER BY
        cd.column_id;

    SELECT
        table_name = '@FilterParameters',
        fp.*
    FROM @FilterParameters AS fp
    ORDER BY
        fp.parameter_name;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #databases AS d
       )
    BEGIN
        SELECT
            table_name =
                '#databases',
            d.*
        FROM #databases AS d
        ORDER BY
            d.database_name
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#databases is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_databases AS id
       )
    BEGIN
        SELECT
            table_name =
                '#include_databases',
            id.*
        FROM #include_databases AS id
        ORDER BY
            id.database_name
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_databases is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #exclude_databases AS ed
       )
    BEGIN
        SELECT
            table_name =
                '#exclude_databases',
            ed.*
        FROM #exclude_databases AS ed
        ORDER BY
            ed.database_name
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#exclude_databases is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #requested_but_skipped_databases AS rsdb
       )
    BEGIN
        SELECT
            table_name =
                '#requested_but_skipped_databases',
            rsdb.*
        FROM #requested_but_skipped_databases AS rsdb
        ORDER BY
            rsdb.database_name
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#requested_but_skipped_databases is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #distinct_plans AS dp
       )
    BEGIN
        SELECT
            table_name =
                '#distinct_plans',
            dp.*
        FROM #distinct_plans AS dp
        ORDER BY
            dp.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#distinct_plans is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #procedure_plans AS pp
       )
    BEGIN
        SELECT
            table_name =
                '#procedure_plans',
            pp.*
        FROM #procedure_plans AS pp
        ORDER BY
            pp.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#procedure_plans is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #procedure_object_ids AS poi
       )
    BEGIN
        SELECT
            table_name =
                '#procedure_object_ids',
            poi.*
        FROM #procedure_object_ids AS poi
        ORDER BY
            poi.object_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#procedure_object_ids is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #query_types AS qt
       )
    BEGIN
        SELECT
            table_name =
                '#query_types',
            qt.*
        FROM #query_types AS qt
        ORDER BY
            qt.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_types is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_plan_ids AS ipi
       )
    BEGIN
        SELECT
            table_name =
                '#include_plan_ids',
            ipi.*
        FROM #include_plan_ids AS ipi
        ORDER BY
            ipi.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_plan_ids is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_query_ids AS iqi
       )
    BEGIN
        SELECT
            table_name =
                '#include_query_ids',
            iqi.*
        FROM #include_query_ids AS iqi
        ORDER BY
            iqi.query_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_query_ids is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_query_hashes AS iqh
       )
    BEGIN
        SELECT
            table_name =
                '#include_query_hashes',
            iqh.*
        FROM #include_query_hashes AS iqh
        ORDER BY
            iqh.query_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_query_hashes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #plan_ids_having_enough_executions AS plans
       )
    BEGIN
        SELECT
            table_name =
                '#plan_ids_having_enough_executions',
            plans.*
        FROM #plan_ids_having_enough_executions AS plans
        ORDER BY
            plans.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#plan_ids_having_enough_executions is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #plan_ids_with_query_hashes AS hashes
       )
    BEGIN
        SELECT
            table_name =
                '#plan_ids_with_query_hashes',
            hashes.*
        FROM #plan_ids_with_query_hashes AS hashes
        ORDER BY
            hashes.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#plan_ids_with_query_hashes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #plan_ids_with_total_waits AS waits
       )
    BEGIN
        SELECT
            table_name =
                '#plan_ids_with_total_waits',
            waits.*
        FROM #plan_ids_with_total_waits AS waits
        ORDER BY
            waits.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#plan_ids_with_total_waits is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #regression_baseline_runtime_stats AS runtime_stats_baseline
       )
    BEGIN
        SELECT
            table_name =
                '#regression_baseline_runtime_stats',
            runtime_stats_baseline.*
        FROM #regression_baseline_runtime_stats AS runtime_stats_baseline
        ORDER BY
           runtime_stats_baseline.query_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#regression_baseline_runtime_stats is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #regression_current_runtime_stats AS runtime_stats_current
       )
    BEGIN
        SELECT
            table_name =
                '#regression_current_runtime_stats',
            runtime_stats_current.*
        FROM #regression_current_runtime_stats AS runtime_stats_current
        ORDER BY
           runtime_stats_current.query_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#regression_current_runtime_stats is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #regression_changes AS changes
       )
    BEGIN
        SELECT
            table_name =
                '#regression_changes',
            changes.*
        FROM #regression_changes AS changes
        ORDER BY
           changes.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#regression_changes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_plan_hashes AS iph
       )
    BEGIN
        SELECT
            table_name =
                '#include_plan_hashes',
            iph.*
        FROM #include_plan_hashes AS iph
        ORDER BY
            iph.plan_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_plan_hashes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #include_sql_handles AS ish
       )
    BEGIN
        SELECT
            table_name =
                '#include_sql_handles',
            ish.*
        FROM #include_sql_handles AS ish
        ORDER BY
            ish.sql_handle
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#include_sql_handles is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #ignore_plan_ids AS ipi
       )
    BEGIN
        SELECT
            table_name =
                '#ignore_plan_ids',
            ipi.*
        FROM #ignore_plan_ids AS ipi
        ORDER BY
            ipi.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#ignore_plan_ids is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #ignore_query_ids AS iqi
       )
    BEGIN
        SELECT
            table_name =
                '#ignore_query_ids',
            iqi.*
        FROM #ignore_query_ids AS iqi
        ORDER BY
            iqi.query_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#ignore_query_ids is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #ignore_query_hashes AS iqh
       )
    BEGIN
        SELECT
            table_name =
                '#ignore_query_hashes',
            iqh.*
        FROM #ignore_query_hashes AS iqh
        ORDER BY
            iqh.query_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#ignore_query_hashes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #ignore_plan_hashes AS iph
       )
    BEGIN
        SELECT
            table_name =
                '#ignore_plan_hashes',
            iph.*
        FROM #ignore_plan_hashes AS iph
        ORDER BY
            iph.plan_hash
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#ignore_plan_hashes is empty';
    END;

    IF EXISTS
       (
           SELECT
               1/0
           FROM #ignore_sql_handles AS ish
       )
    BEGIN
        SELECT
            table_name =
                '#ignore_sql_handles',
            ish.*
        FROM #ignore_sql_handles AS ish
        ORDER BY
            ish.sql_handle
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#ignore_sql_handles is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_text_search AS qst
       )
    BEGIN
        SELECT
            table_name =
                '#query_text_search',
            qst.*
        FROM #query_text_search AS qst
        ORDER BY
            qst.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_text_search is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #wait_filter AS wf
       )
    BEGIN
        SELECT
            table_name =
                '#wait_filter',
            wf.*
        FROM #wait_filter AS wf
        ORDER BY
            wf.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#wait_filter is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #maintenance_plans AS mp
       )
    BEGIN
        SELECT
            table_name =
                '#maintenance_plans',
            mp.*
        FROM #maintenance_plans AS mp
        ORDER BY
            mp.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#maintenance_plans is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #database_query_store_options AS qst
       )
    BEGIN
        SELECT
            table_name =
                '#database_query_store_options',
            dqso.*
        FROM #database_query_store_options AS dqso
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#database_query_store_options is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_store_trouble AS qst
       )
    BEGIN
        SELECT
            table_name =
                '#query_store_trouble',
            qst.*
        FROM #query_store_trouble AS qst
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#database_query_store_options is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_store_plan AS qsp
       )
    BEGIN
        SELECT
            table_name =
                '#query_store_plan',
            qsp.*
        FROM #query_store_plan AS qsp
        ORDER BY
            qsp.plan_id, qsp.query_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_store_plan is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_store_query AS qsq
       )
    BEGIN
        SELECT
            table_name =
                '#query_store_query',
            qsq.*
        FROM #query_store_query AS qsq
        ORDER BY
            qsq.query_id,
            qsq.query_text_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_store_query is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_store_query_text AS qsqt
       )
    BEGIN
        SELECT
            table_name =
                '#query_store_query_text',
            qsqt.*
        FROM #query_store_query_text AS qsqt
        ORDER BY
            qsqt.query_text_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_store_query_text is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #dm_exec_query_stats AS deqs
       )
    BEGIN
        SELECT
            table_name =
                '#dm_exec_query_stats ',
            deqs.*
        FROM #dm_exec_query_stats AS deqs
        ORDER BY
            deqs.statement_sql_handle
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#dm_exec_query_stats is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_store_runtime_stats AS qsrs
       )
    BEGIN
        SELECT
            table_name =
                '#query_store_runtime_stats',
            qsrs.*
        FROM #query_store_runtime_stats AS qsrs
        ORDER BY
            qsrs.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_store_runtime_stats is empty';
    END;

    IF
      (
          @new = 1
          AND EXISTS
              (
                 SELECT
                     1/0
                 FROM #query_store_wait_stats AS qsws
              )
      )
    BEGIN
        SELECT
            table_name =
                '#query_store_wait_stats',
            qsws.*
        FROM #query_store_wait_stats AS qsws
        ORDER BY
            qsws.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_store_wait_stats is empty' +
                CASE
                    WHEN (
                              @product_version = 13
                          AND @azure = 0
                         )
                    THEN ' because it''s not available < 2017'
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM #database_query_store_options AS dqso
                             WHERE dqso.wait_stats_capture_mode_desc <> N'ON'
                         )
                    AND EXISTS
                        (
                            SELECT
                                1/0
                            FROM #database_query_store_options AS dqso
                            WHERE dqso.wait_stats_capture_mode_desc = N'ON'
                        )
                    THEN ' because we ignore wait stats if you have disabled capturing them in your Query Store options and everywhere that had it enabled had no data'
                    WHEN EXISTS
                         (
                             SELECT
                                 1/0
                             FROM #database_query_store_options AS dqso
                             WHERE dqso.wait_stats_capture_mode_desc <> N'ON'
                         )
                    THEN ' because we ignore wait stats if you have disabled capturing them in your Query Store options'
                    ELSE ' for the queries in the results'
                END;
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_context_settings AS qcs
       )
    BEGIN
        SELECT
            table_name =
                '#query_context_settings',
            qcs.*
        FROM #query_context_settings AS qcs
        ORDER BY
            qcs.context_settings_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_context_settings is empty';
    END;

    IF @sql_2022_views = 1
    BEGIN
        IF EXISTS
           (
              SELECT
                  1/0
              FROM #query_store_plan_feedback AS qspf
           )
        BEGIN
            SELECT
                table_name =
                    '#query_store_plan_feedback',
                qspf.*
            FROM #query_store_plan_feedback AS qspf
            ORDER BY
                qspf.plan_feedback_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#query_store_plan_feedback is empty';
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #query_store_query_hints AS qsqh
           )
        BEGIN
            SELECT
                table_name =
                    '#query_store_query_hints',
                qsqh.*
            FROM #query_store_query_hints AS qsqh
            ORDER BY
                qsqh.query_hint_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#query_store_query_hints is empty';
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #query_store_query_variant AS qsqv
           )
        BEGIN
            SELECT
                table_name =
                    '#query_store_query_variant',
                qsqv.*
            FROM #query_store_query_variant AS qsqv
            ORDER BY
                qsqv.query_variant_query_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#query_store_query_variant is empty';
        END;

        IF @ags_present = 1
        BEGIN
            IF EXISTS
               (
                  SELECT
                      1/0
                  FROM #query_store_replicas AS qsr
               )
            BEGIN
                SELECT
                    table_name =
                        '#query_store_replicas',
                    qsr.*
                FROM #query_store_replicas AS qsr
                ORDER BY
                    qsr.replica_group_id
                OPTION(RECOMPILE);
            END;
            ELSE
            BEGIN
                SELECT
                    result =
                        '#query_store_replicas is empty';
            END;

            IF EXISTS
               (
                  SELECT
                      1/0
                  FROM #query_store_plan_forcing_locations AS qspfl
               )
            BEGIN
                SELECT
                    table_name =
                        '#query_store_plan_forcing_locations',
                    qspfl.*
                FROM #query_store_plan_forcing_locations AS qspfl
                ORDER BY
                    qspfl.plan_forcing_location_id
                OPTION(RECOMPILE);
            END;
            ELSE
            BEGIN
                SELECT
                    result =
                        '#query_store_plan_forcing_locations is empty';
            END;
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #database_automatic_tuning_configurations AS datc
           )
        BEGIN
            SELECT
                table_name =
                    '#database_automatic_tuning_configurations',
                datc.*
            FROM #database_automatic_tuning_configurations AS datc
            ORDER BY
                datc.[option]
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#database_automatic_tuning_configurations is empty';
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #only_queries_with_hints AS oqwh
           )
        BEGIN
            SELECT
                table_name =
                    '#only_queries_with_hints',
                oqwh.*
            FROM #only_queries_with_hints AS oqwh
            ORDER BY
                oqwh.plan_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#only_queries_with_hints is empty';
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #only_queries_with_feedback AS oqwf
           )
        BEGIN
            SELECT
                table_name =
                    '#only_queries_with_feedback',
                oqwf.*
            FROM #only_queries_with_feedback AS oqwf
            ORDER BY
                oqwf.plan_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#only_queries_with_feedback is empty';
        END;

        IF EXISTS
           (
              SELECT
                  1/0
              FROM #only_queries_with_variants AS oqwv
           )
        BEGIN
            SELECT
                table_name =
                    '#only_queries_with_variants',
                oqwv.*
            FROM #only_queries_with_variants AS oqwv
            ORDER BY
                oqwv.plan_id
            OPTION(RECOMPILE);
        END;
        ELSE
        BEGIN
            SELECT
                result =
                    '#only_queries_with_variants is empty';
        END;
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #forced_plans_failures AS fpf
       )
    BEGIN
        SELECT
            table_name =
                '#forced_plans_failures',
            fpf.*
        FROM #forced_plans_failures AS fpf
        ORDER BY
            fpf.plan_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#forced_plans_failures is empty';
    END;

    IF EXISTS
       (
          SELECT
              1/0
          FROM #troubleshoot_performance AS tp
       )
    BEGIN
        SELECT
            table_name =
                '#troubleshoot_performance',
            tp.*
        FROM #troubleshoot_performance AS tp
        ORDER BY
            tp.id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#troubleshoot_performance is empty';
    END;
    IF EXISTS
       (
          SELECT
              1/0
          FROM #query_hash_totals AS qht
       )
    BEGIN
        SELECT
            table_name =
                '#query_hash_totals',
            qht.*
        FROM #query_hash_totals AS qht
        ORDER BY
            qht.database_id
        OPTION(RECOMPILE);
    END;
    ELSE
    BEGIN
        SELECT
            result =
                '#query_hash_totals is empty';
    END;
    RETURN; /*Stop doing anything, I guess*/
END; /*End debug*/
RETURN; /*Yeah sure why not?*/
END;/*Final End*/
GO

