-- 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'') ) 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') ) 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'' + REPLACE ( REPLACE ( @wait_type, N',', N'' ), N' ', N'' ) + N'' ).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'Skipped Plans' 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 + ' ', 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'' + REPLACE ( @include_databases, N',', N'' ) + N'' ) ) 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'' + REPLACE ( @exclude_databases, N',', N'' ) + N'' ) ) 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'' + REPLACE(superset.included_columns, N', ', N'') + N'' ) WHERE superset.included_columns IS NOT NULL UNION ALL /* ALL subsets' includes */ SELECT x = CONVERT ( xml, N'' + REPLACE(subset.included_columns, N', ', N'') + N'' ) 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'' + REPLACE(ISNULL(ia_winner.included_columns, N''), N', ', N'') + N'' ) WHERE ia_winner.included_columns IS NOT NULL UNION ALL /* Loser's includes */ SELECT x = CONVERT ( xml, N'' + REPLACE(ia_loser.included_columns, N', ', N'') + N'' ) 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'' + REPLACE(ISNULL(kdi.winner_includes, N''), N', ', N'') + N'' ) 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'' + REPLACE(kdi.loser_includes, N', ', N'') + N'' ) 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''??''), (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'' 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''??''), (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'' 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'??'), (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' 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, '''' + REPLACE ( @include_plan_ids, '','', '''' ) + '''' ) ) 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, '''' + REPLACE ( @include_query_ids, '','', '''' ) + '''' ) ) 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, '''' + REPLACE ( @ignore_plan_ids, '','', '''' ) + '''' ) ) 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, '''' + REPLACE ( @ignore_query_ids, '','', '''' ) + '''' ) ) 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'', qsp.query_plan) > 0 THEN TRY_CAST ( SUBSTRING ( qsp.query_plan, CHARINDEX(N'', qsp.query_plan), CHARINDEX ( N'', qsp.query_plan, CHARINDEX(N'', qsp.query_plan) ) + LEN(N'') - CHARINDEX(N'', qsp.query_plan) ) AS xml ) END ) AS x CROSS APPLY x.parameter_list_xml.nodes(N'//ParameterList/ColumnReference') AS cr(c) WHERE CHARINDEX(N'', 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'

' + REPLACE(prefix.param_prefix, N',', N'

') + N'

' 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' 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'' + REPLACE ( @include_databases, N',', N'' ) + N'' ) ) 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'' + REPLACE ( @exclude_databases, N',', N'' ) + N'' ) ) 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, '''' + REPLACE ( REPLACE ( @ids, '','', '''' ), '' '', '''' ) + '''' ).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, '''' + REPLACE ( REPLACE ( @ids, '','', '''' ), '' '', '''' ) + '''' ).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'' 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