diff --git a/Install-All/DarlingData.sql b/Install-All/DarlingData.sql
index bd42dcc3..081c1fec 100644
--- a/Install-All/DarlingData.sql
+++ b/Install-All/DarlingData.sql
@@ -1,4 +1,4 @@
--- Compile Date: 01/03/2026 20:00:05 UTC
+-- Compile Date: 1/24/2026 8:58:26 PM UTC
SET ANSI_NULLS ON;
SET ANSI_PADDING ON;
SET ANSI_WARNINGS ON;
@@ -71,8 +71,8 @@ BEGIN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '3.0',
- @version_date = '20260115';
+ @version = '3.1',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -143,7 +143,7 @@ BEGIN
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'0'
+ 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'@pending_task_threshold' THEN N'10'
@@ -235,15 +235,15 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@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'',
+ @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),
+ @collection_sql nvarchar(MAX),
/*Log to table stuff*/
@log_table_significant_waits sysname,
@log_table_waits_by_count sysname,
@@ -257,14 +257,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@log_table_scheduler_issues sysname,
@log_table_severe_errors sysname,
@cleanup_date datetime2(7),
- @check_sql nvarchar(max) = N'',
- @create_sql nvarchar(max) = N'',
- @insert_sql nvarchar(max) = N'',
+ @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'',
+ @dsql nvarchar(MAX) = N'',
+ @mdsql_template nvarchar(MAX) = N'',
+ @mdsql_execute nvarchar(MAX) = N'',
@start_date_debug nvarchar(50),
@end_date_debug nvarchar(50);
@@ -451,7 +451,7 @@ AND ca.utc_timestamp < @end_date';
SELECT
@what_to_check = LOWER(ISNULL(@what_to_check, 'all')),
@warnings_only = ISNULL(@warnings_only, 0),
- @wait_duration_ms = ISNULL(@wait_duration_ms, 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),
@pending_task_threshold = ISNULL(@pending_task_threshold, 10);
@@ -2048,7 +2048,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(CONVERT(date, @end_date)) +
'.'
ELSE 'no significant waits found!'
- END
+ END;
RAISERROR('No waits by count found', 0, 0) WITH NOWAIT;
END;
@@ -2308,7 +2308,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@wait_duration_ms) +
'ms.'
ELSE 'no significant waits found!'
- END
+ END;
RAISERROR('No waits by duration', 0, 0) WITH NOWAIT;
END;
@@ -2575,7 +2575,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no io issues found!'
- END
+ END;
RAISERROR('No io data found', 0, 0) WITH NOWAIT;
END;
END;
@@ -2732,7 +2732,7 @@ AND ca.utc_timestamp < @end_date';
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 IS NULL)
+ 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);
@@ -2780,7 +2780,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no cpu issues found!'
- END
+ END;
RAISERROR('No scheduler data found', 0, 0) WITH NOWAIT;
END;
@@ -2988,7 +2988,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no memory issues found!'
- END
+ END;
RAISERROR('No memory condition data found', 0, 0) WITH NOWAIT;
END;
@@ -3219,7 +3219,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no memory pressure events found!'
- END
+ END;
RAISERROR('No memory broker data found', 0, 0) WITH NOWAIT;
END;
@@ -3511,7 +3511,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(CONVERT(date, @end_date)) +
'.'
ELSE 'no memory node OOM events found!'
- END
+ END;
RAISERROR('No memory oom data found', 0, 0) WITH NOWAIT;
END;
@@ -3913,7 +3913,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no system health issues found!'
- END
+ END;
RAISERROR('No system health data found', 0, 0) WITH NOWAIT;
END;
@@ -4108,7 +4108,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no scheduler issues found!'
- END
+ END;
RAISERROR('No scheduler issues data found', 0, 0) WITH NOWAIT;
END;
@@ -4335,7 +4335,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no severe errors found!'
- END
+ END;
RAISERROR('No error data found', 0, 0) WITH NOWAIT;
END;
@@ -4503,7 +4503,7 @@ AND ca.utc_timestamp < @end_date';
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 IS NULL)
+ AND (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
OPTION(RECOMPILE);
IF @debug = 1
@@ -4534,6 +4534,14 @@ AND ca.utc_timestamp < @end_date';
AND @log_to_table = 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;
@@ -4586,20 +4594,20 @@ AND ca.utc_timestamp < @end_date';
SELECT
bx.event_time,
- currentdbname = bd.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
+ 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(100)'),
+ wait_resource = bd.value('(process/@waitresource)[1]', 'sysname'),
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)'),
+ 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'),
@@ -4611,6 +4619,7 @@ AND ca.utc_timestamp < @end_date';
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
@@ -4647,20 +4656,20 @@ AND ca.utc_timestamp < @end_date';
SELECT
bx.event_time,
- currentdbname = bg.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
+ 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(100)'),
+ wait_resource = bg.value('(process/@waitresource)[1]', 'sysname'),
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)'),
+ 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'),
@@ -4672,6 +4681,7 @@ AND ca.utc_timestamp < @end_date';
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
@@ -4928,7 +4938,7 @@ AND ca.utc_timestamp < @end_date';
'available plans for blocking',
b.currentdbname,
query_text =
- TRY_CAST(b.query_text AS nvarchar(max)),
+ TRY_CAST(b.query_text AS nvarchar(MAX)),
sql_handle =
CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
stmtstart =
@@ -4947,7 +4957,7 @@ AND ca.utc_timestamp < @end_date';
CONVERT(varchar(30), 'available plans for blocking'),
b.currentdbname,
query_text =
- TRY_CAST(b.query_text AS nvarchar(max)),
+ TRY_CAST(b.query_text AS nvarchar(MAX)),
sql_handle =
CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
stmtstart =
@@ -5078,23 +5088,23 @@ AND ca.utc_timestamp < @end_date';
),
d.victim_id,
d.deadlock_graph,
- id = e.x.value('@id', 'nvarchar(256)'),
+ id = e.x.value('@id', 'sysname'),
database_id = e.x.value('@currentdb', 'bigint'),
- current_database_name = e.x.value('@currentdbname', 'nvarchar(256)'),
+ 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', 'nvarchar(256)'),
- last_tran_started = e.x.value('@lasttranstarted', 'datetime'),
- last_batch_started = e.x.value('@lastbatchstarted', 'datetime'),
- last_batch_completed = e.x.value('@lastbatchcompleted', 'datetime'),
- lock_mode = e.x.value('@lockMode', 'nvarchar(256)'),
- status = e.x.value('@status', 'nvarchar(256)'),
+ 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', 'nvarchar(256)'),
- login_name = e.x.value('@loginname', 'nvarchar(256)'),
- isolation_level = e.x.value('@isolationlevel', 'nvarchar(256)'),
+ 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('(//process/inputbuf/text())[1]', 'nvarchar(max)'),
@@ -5306,11 +5316,11 @@ AND ca.utc_timestamp < @end_date';
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -5334,13 +5344,13 @@ AND ca.utc_timestamp < @end_date';
total_logical_reads_mb =
deqs.total_logical_reads * 8. / 1024.,
min_grant_mb =
- deqs.min_grant_kb * 8. / 1024.,
+ deqs.min_grant_kb / 1024.,
max_grant_mb =
- deqs.max_grant_kb * 8. / 1024.,
+ deqs.max_grant_kb / 1024.,
min_used_grant_mb =
- deqs.min_used_grant_kb * 8. / 1024.,
+ deqs.min_used_grant_kb / 1024.,
max_used_grant_mb =
- deqs.max_used_grant_kb * 8. / 1024.,
+ deqs.max_used_grant_kb / 1024.,
deqs.min_reserved_threads,
deqs.max_reserved_threads,
deqs.min_used_threads,
@@ -5404,7 +5414,7 @@ AND ca.utc_timestamp < @end_date';
ap.sql_handle,
ap.statement_start_offset,
ap.statement_end_offset
- INTO #all_avalable_plans
+ INTO #all_available_plans
FROM
(
SELECT
@@ -5458,13 +5468,13 @@ AND ca.utc_timestamp < @end_date';
(
SELECT
1/0
- FROM #all_avalable_plans AS ap
+ FROM #all_available_plans AS ap
WHERE ap.finding = 'available plans for blocking'
)
BEGIN
SELECT
aap.*
- FROM #all_avalable_plans AS aap
+ FROM #all_available_plans AS aap
WHERE aap.finding = 'available plans for blocking'
ORDER BY
aap.avg_worker_time_ms DESC
@@ -5489,13 +5499,13 @@ AND ca.utc_timestamp < @end_date';
(
SELECT
1/0
- FROM #all_avalable_plans AS ap
+ FROM #all_available_plans AS ap
WHERE ap.finding = 'available plans for deadlocks'
)
BEGIN
SELECT
aap.*
- FROM #all_avalable_plans AS aap
+ FROM #all_available_plans AS aap
WHERE aap.finding = 'available plans for deadlocks'
ORDER BY
aap.avg_worker_time_ms DESC
@@ -5608,8 +5618,8 @@ SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '7.0',
- @version_date = '20260115';
+ @version = '7.1',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -5674,7 +5684,7 @@ BEGIN
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 minimums for query, wait, and blocking durations. only use if you''re okay with potentially adding a lot of observer overhead on your system, or for testing purposes.'
+ 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'
@@ -5915,8 +5925,8 @@ CREATE TABLE
event_type_short sysname NOT NULL,
is_table_created bit NOT NULL DEFAULT 0,
is_view_created bit NOT NULL DEFAULT 0,
- last_checked datetime NOT NULL DEFAULT '19000101',
- last_updated datetime NOT NULL DEFAULT '19000101',
+ 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
@@ -6035,7 +6045,7 @@ DECLARE
@spe nvarchar(max) = N'.sys.sp_executesql ',
@view_sql nvarchar(max) = N'',
@view_database sysname = N'',
- @date_filter datetime,
+ @date_filter datetime2(7),
@Time time,
@delete_tracker integer,
@the_deleter_must_awaken nvarchar(max) = N'',
@@ -6514,7 +6524,7 @@ BEGIN
);
/* If we find any invalid waits, let people know */
- IF @@ROWCOUNT > 0
+ IF ROWCOUNT_BIG() > 0
BEGIN
SELECT
invalid_waits =
@@ -6756,9 +6766,9 @@ 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 < 0
+IF @delete_retention_days < 1
BEGIN
- SET @delete_retention_days *= -1;
+ 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;
@@ -6858,7 +6868,7 @@ END;
IF @requested_memory_mb > 0
BEGIN
- SET @requested_memory_kb = @requested_memory_mb / 1024.;
+ SET @requested_memory_kb = @requested_memory_mb * 1024.;
SET @requested_memory_mb_filter += N' AND requested_memory_kb >= ' + @requested_memory_kb + @nc10;
END;
@@ -8348,7 +8358,7 @@ BEGIN
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/@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,
@@ -8443,7 +8453,7 @@ BEGIN
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/@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,
@@ -8735,7 +8745,7 @@ BEGIN
)
FROM #blocking AS bg
WHERE (bg.database_name = @database_name
- OR @database_name IS NULL)
+ OR @database_name = N'')
UNION ALL
@@ -8749,7 +8759,7 @@ BEGIN
)
FROM #blocked AS bd
WHERE (bd.database_name = @database_name
- OR @database_name IS NULL)
+ OR @database_name = N'')
) AS kheb
OPTION(RECOMPILE);
@@ -8802,7 +8812,7 @@ BEGIN
) AS b
WHERE b.n = 1
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
ORDER BY
b.sort_order,
CASE
@@ -8836,9 +8846,9 @@ BEGIN
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 @database_name = N'')
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
UNION ALL
@@ -8859,11 +8869,11 @@ BEGIN
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)
+ 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 @database_name = N'')
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
) AS b
OPTION(RECOMPILE);
@@ -8880,11 +8890,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -9048,6 +9058,7 @@ IF @debug = 1 BEGIN RAISERROR(N'Starting data collection.', 0, 1) WITH NOWAIT; E
WHILE 1 = 1
BEGIN
+ SET @the_sleeper_must_awaken = N'';
IF @azure = 0
BEGIN
IF NOT EXISTS
@@ -9102,21 +9113,22 @@ BEGIN
N' ON DATABASE STATE = START;' +
@nc10
FROM sys.database_event_sessions AS ses
- JOIN sys.dm_xe_database_sessions AS dxs
+ LEFT JOIN sys.dm_xe_database_sessions AS dxs
ON dxs.name = ses.name
- WHERE ses.name LIKE N'keeper_HumanEvents_%';
+ 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;
+ 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
@@ -10222,7 +10234,7 @@ ORDER BY
/* this executes the insert */
EXECUTE sys.sp_executesql
@table_sql,
- N'@date_filter datetime',
+ N'@date_filter datetime2(7)',
@date_filter;
/*Update the worker table's last checked, and conditionally, updated dates*/
@@ -10233,7 +10245,7 @@ ORDER BY
SYSDATETIME(),
hew.last_updated =
CASE
- WHEN @@ROWCOUNT > 0
+ WHEN ROWCOUNT_BIG() > 0
THEN SYSDATETIME()
ELSE hew.last_updated
END
@@ -10293,6 +10305,7 @@ BEGIN
OR @delete_tracker <> DATEPART(HOUR, @Time)
)
BEGIN
+ SET @the_deleter_must_awaken = N'';
SELECT
@the_deleter_must_awaken +=
N' DELETE FROM ' +
@@ -10314,7 +10327,7 @@ BEGIN
/* execute the delete */
EXECUTE sys.sp_executesql
@the_deleter_must_awaken,
- N'@delete_retention_days INT',
+ N'@delete_retention_days integer',
@delete_retention_days;
/* set this to the hour it was last checked */
@@ -10333,17 +10346,33 @@ BEGIN
SET @executer = QUOTENAME(@output_database_name) + N'.sys.sp_executesql ';
- /*Clean up sessions, this isn't database-specific*/
- 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_%';
+ /*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;
@@ -10442,7 +10471,6 @@ BEGIN CATCH
THROW;
- RETURN -138;
END;
END CATCH;
END;
@@ -10542,8 +10570,8 @@ SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '5.0',
- @version_date = '20260115';
+ @version = '5.1',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -11336,7 +11364,6 @@ CREATE TABLE
IF LOWER(@target_type) = N'table'
BEGIN
GOTO TableMode;
- RETURN;
END;
/*Look to see if the session exists and is running*/
@@ -11527,13 +11554,13 @@ BEGIN
SELECT
@session_id =
- t.event_session_address,
+ t.event_session_id,
@target_session_id =
- t.target_name
- FROM sys.dm_xe_database_session_targets t
- JOIN sys.dm_xe_database_sessions s
- ON s.address = t.event_session_address
- WHERE t.target_name = @target_type
+ 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);
@@ -11553,7 +11580,7 @@ BEGIN
nvarchar(4000),
f.value
)
- FROM sys.server_event_session_fields AS f
+ 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'
@@ -11765,8 +11792,8 @@ BEGIN
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'),
+ 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'),
@@ -12050,6 +12077,7 @@ BEGIN
END
OPTION(RECOMPILE);
+ IF @debug = 1
BEGIN
RAISERROR('Inserting to #available_plans_sh', 0, 1) WITH NOWAIT;
END;
@@ -12119,11 +12147,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -12134,7 +12162,7 @@ BEGIN
(
SECOND,
deqs.creation_time,
- NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
+ NULLIF(deqs.last_execution_time, '19000101')
),
0
),
@@ -12309,10 +12337,8 @@ BEGIN
IF @timestamp_column IS NULL
BEGIN
- BEGIN
- SET @extract_sql = @extract_sql + N'
+ SET @extract_sql = @extract_sql + N'
AND e.x.exist(''@timestamp[. >= sql:variable("@start_date") and . < sql:variable("@end_date")]'') = 1';
- END;
END;
SET @extract_sql = @extract_sql + N'
@@ -12390,7 +12416,7 @@ SELECT
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/@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,
@@ -13130,7 +13156,7 @@ BEGIN
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)
+ 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
@@ -13165,11 +13191,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -13180,7 +13206,7 @@ BEGIN
(
SECOND,
deqs.creation_time,
- NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
+ NULLIF(deqs.last_execution_time, '19000101')
),
0
),
@@ -13675,7 +13701,7 @@ BEGIN
finding =
N'There have been ' +
CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
- N' background tasks involved in blocking sessions in ' +
+ N' done queries involved in blocking sessions in ' +
b.database_name +
N'.',
sort_order =
@@ -13717,7 +13743,7 @@ BEGIN
finding =
N'There have been ' +
CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
- N' background tasks involved in blocking sessions in ' +
+ N' done queries involved in blocking sessions in ' +
b.database_name +
N'.',
sort_order =
@@ -14298,8 +14324,8 @@ BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT
- @version = '2.0',
- @version_date = '20260115';
+ @version = '2.1',
+ @version_date = '20260201';
IF
/* Check SQL Server 2012+ for FORMAT and CONCAT functions */
@@ -14410,12 +14436,12 @@ BEGIN TRY
WHEN N'@min_writes' THEN '0'
WHEN N'@min_size_gb' THEN '0'
WHEN N'@min_rows' THEN '0'
- WHEN N'@dedupe_only' THEN '0'
- WHEN N'@get_all_databases' 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 'true'
+ WHEN N'@debug' THEN 'false'
WHEN N'@version' THEN 'NULL'
WHEN N'@version_date' THEN 'NULL'
ELSE NULL
@@ -14523,8 +14549,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/* Azure SQL DB or Managed Instance */
WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (5, 8)
THEN 1
- /* SQL Server 2019+ */
- WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) = 3
+ /* SQL Server 2019+ (Enterprise or Standard) */
+ WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (2, 3)
AND CONVERT
(
integer,
@@ -15287,55 +15313,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;
END;
- 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;
-
/* Parse @exclude_databases comma-separated list */
IF @get_all_databases = 1
AND @exclude_databases IS NOT NULL
@@ -15517,6 +15494,59 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 */
@@ -15653,9 +15683,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_usage_stats AS us
- ON i.object_id = us.object_id
- AND us.database_id = @database_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
@@ -16535,7 +16563,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@sql,
N'@database_id integer,
@object_id integer,
- @min_rows integer',
+ @min_rows bigint',
@current_database_id,
@object_id,
@min_rows;
@@ -17324,7 +17352,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]') + N', %') ESCAPE '~'
+ 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
@@ -17356,7 +17384,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]') + N', %') ESCAPE '~' /* ia2 has wider key that starts with ia1's key */
+ 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)
@@ -17408,7 +17436,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
OPTION(RECOMPILE);
- DECLARE @rule3_rowcount bigint = @@ROWCOUNT;
+ DECLARE @rule3_rowcount bigint = ROWCOUNT_BIG();
IF @debug = 1
BEGIN
@@ -17541,7 +17569,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
OPTION(RECOMPILE);
- DECLARE @rule5_rowcount bigint = @@ROWCOUNT;
+ DECLARE @rule5_rowcount bigint = ROWCOUNT_BIG();
IF @debug = 1
BEGIN
@@ -17555,92 +17583,114 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;
/* Rule 6: Merge includes from subset to superset indexes */
- WITH
- KeySubsetSuperset AS
- (
+ /* Pre-compute merged includes in a temp table to handle multiple subsets per superset */
+ IF @debug = 1
+ BEGIN
SELECT
- superset.database_id,
- superset.object_id,
- superset.index_id,
- superset.index_name,
- superset.index_hash,
- superset.included_columns AS superset_includes,
- subset.included_columns AS subset_includes
+ 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 superset.scope_hash = subset.scope_hash
+ 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 subset.action = N'DISABLE'
AND superset.consolidation_rule = N'Key Superset'
- AND subset.consolidation_rule = N'Key Subset'
+ OPTION(RECOMPILE);
+ END;
+
+ CREATE TABLE
+ #merged_includes
+ (
+ scope_hash bigint NOT NULL,
+ index_name sysname NOT NULL,
+ key_columns nvarchar(max) NOT NULL,
+ merged_includes nvarchar(max) NULL,
+ PRIMARY KEY (scope_hash, index_name)
+ );
+
+ /* Gather all supersets that need include merging */
+ INSERT INTO
+ #merged_includes
+ WITH
+ (TABLOCK)
+ (
+ scope_hash,
+ index_name,
+ key_columns,
+ merged_includes
)
- UPDATE
- ia
- SET
- ia.included_columns =
- CASE
- /* If both have includes, combine them without duplicates */
- WHEN kss.superset_includes IS NOT NULL
- AND kss.subset_includes IS NOT NULL
- THEN
- /* Create combined includes using XML method that works with all SQL Server versions */
+ SELECT
+ superset.scope_hash,
+ superset.index_name,
+ superset.key_columns,
+ merged_includes =
+ STUFF
+ (
(
- SELECT
- /* Combine both sets of includes */
- combined_cols =
- STUFF
+ SELECT DISTINCT
+ N', ' +
+ t.c.value('.', 'sysname')
+ FROM
+ (
+ /* Superset's own includes */
+ SELECT
+ x = CONVERT
(
- (
- SELECT DISTINCT
- N', ' +
- t.c.value('.', 'sysname')
- FROM
- (
- /* Create XML from superset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.superset_includes, N', ', N'') +
- N''
- )
+ xml,
+ N'' +
+ REPLACE(superset.included_columns, N', ', N'') +
+ N''
+ )
+ WHERE superset.included_columns IS NOT NULL
- UNION ALL
+ UNION ALL
- /* Create XML from subset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.subset_includes, N', ', N'') +
- N''
- )
- ) AS a
- /* Split XML into individual columns */
- CROSS APPLY a.x.nodes('/c') AS t(c)
- FOR
- XML
- PATH('')
- ),
- 1,
- 2,
- ''
+ /* ALL subsets' includes */
+ SELECT
+ x = CONVERT
+ (
+ xml,
+ N'' +
+ REPLACE(subset.included_columns, N', ', N'') +
+ N''
)
- )
- /* If only subset has includes, use those */
- WHEN kss.superset_includes IS NULL
- AND kss.subset_includes IS NOT NULL
- THEN kss.subset_includes
- /* If only superset has includes or neither has includes, keep superset's includes */
- ELSE kss.superset_includes
- END
+ 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 KeySubsetSuperset AS kss
- ON ia.index_hash = kss.index_hash
- WHERE ia.action = N'MERGE INCLUDES'
+ JOIN #merged_includes AS mi
+ ON mi.scope_hash = ia.scope_hash
+ AND mi.index_name = ia.index_name
OPTION(RECOMPILE);
IF @debug = 1
@@ -17663,7 +17713,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 (ia1.key_columns + N'%') /* ia2 has wider key that starts with ia1's key */
+ 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)
@@ -18342,20 +18392,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FROM #index_analysis AS ia
WHERE ia.action = N'MERGE INCLUDES'
AND ia.superseded_by IS NOT NULL
- /* This should indicate it already has all the needed includes */
+ /* Only change to KEEP if Rule 6 didn't compute merged includes for this index */
AND NOT EXISTS
(
- /* Find any indexes it supersedes that have includes not in this index */
SELECT
1/0
- FROM #index_analysis AS ia_subset
- WHERE ia_subset.scope_hash = ia.scope_hash
- AND ia_subset.key_columns = ia.key_columns
- AND ia_subset.action = N'DISABLE'
- AND ia_subset.target_index_name = ia.index_name
- /* This complex check handles cases where the superset doesn't contain all subset columns */
- AND CHARINDEX(ISNULL(ia_subset.included_columns, N''), ISNULL(ia.included_columns, N'')) = 0
- AND ISNULL(ia_subset.included_columns, N'') <> N''
+ FROM #merged_includes AS mi
+ WHERE mi.scope_hash = ia.scope_hash
+ AND mi.index_name = ia.index_name
)
OPTION(RECOMPILE);
@@ -18460,7 +18504,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CASE
WHEN ps.partition_function_name IS NOT NULL
THEN N' ON ' +
- QUOTENAME(ps.partition_function_name) +
+ QUOTENAME(ps.built_on) +
N'(' +
ISNULL(ps.partition_columns, N'') +
N')'
@@ -18848,7 +18892,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THEN N'UNIQUE '
ELSE N''
END +
- N'CLUSTERED INDEX' +
+ N'CLUSTERED INDEX ' +
QUOTENAME(fo.index_name) +
N' ON ' +
QUOTENAME(fo.database_name) +
@@ -19111,7 +19155,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
QUOTENAME(ia_uc.schema_name) +
N'.' +
QUOTENAME(ia_uc.table_name) +
- N' NOCHECK CONSTRAINT ' +
+ N' DROP CONSTRAINT ' +
QUOTENAME(ia_uc.index_name) +
N';',
/* Original index definition for validation */
@@ -20262,7 +20306,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
irs.index_name,
irs.script_type
ORDER BY
- irs.result_type DESC /* Prefer non-NULL result types */
+ irs.result_type DESC
) AS rn
FROM #index_cleanup_results AS irs
) AS ir
@@ -21071,8 +21115,8 @@ SET DATEFORMAT MDY;
BEGIN
SELECT
- @version = '3.0',
- @version_date = '20260115';
+ @version = '3.1',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -21106,7 +21150,7 @@ BEGIN
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 substitions.'
+ 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;'
@@ -21267,9 +21311,8 @@ BEGIN
@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 integer = 0 /*total number of searches to run*/,
+ @t_searches bigint = 0 /*total number of searches to run*/,
@l_count integer = 1 /*loop count*/,
- @stopper bit = 0, /*stop loop execution safety*/
@is_rds bit =
CASE
WHEN OBJECT_ID(N'rdsadmin.dbo.rds_read_error_log', N'P') IS NOT NULL
@@ -21546,7 +21589,6 @@ BEGIN
IF @debug = 1 BEGIN RAISERROR('Entering WHILE loop', 0, 1) WITH NOWAIT; END;
WHILE @@FETCH_STATUS = 0
- AND @stopper = 0
BEGIN
IF @debug = 1 BEGIN RAISERROR('Entering cursor', 0, 1) WITH NOWAIT; END;
@@ -21640,7 +21682,6 @@ BEGIN
IF @l_log IS NULL
BEGIN
IF @debug = 1 BEGIN RAISERROR('Breaking', 0, 1) WITH NOWAIT; END;
- SET @stopper = 1;
BREAK;
END;
IF @debug = 1 BEGIN RAISERROR('Ended WHILE loop', 0, 1) WITH NOWAIT; END;
@@ -21803,8 +21844,8 @@ BEGIN
Set version information
*/
SELECT
- @version = N'2.0',
- @version_date = N'20260115';
+ @version = N'2.1',
+ @version_date = N'20260201';
/*
Help section, for help.
@@ -21845,7 +21886,7 @@ BEGIN
valid_inputs =
CASE
ap.name
- WHEN N'@database_name' THEN 'the name of a database you care about indexes in'
+ 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'
@@ -21857,7 +21898,7 @@ BEGIN
ap.name
WHEN N'@database_name' THEN 'NULL'
WHEN N'@help' THEN 'false'
- WHEN N'@debug' THEN 'true'
+ WHEN N'@debug' THEN 'false'
WHEN N'@version' THEN 'NULL'
WHEN N'@version_date' THEN 'NULL'
ELSE NULL
@@ -22582,6 +22623,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
@@ -22855,6 +22901,16 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
AND domc.name = N'TokenAndPermUserStore'
AND domc.pages_kb >= 500000; /* Only if bigger than 500MB */
+ /* 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;
+
/* 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
@@ -23156,7 +23212,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
VALUES
(
- 5001,
+ 5000,
50,
N'Default Trace Permissions',
N'Inadequate permissions',
@@ -23227,6 +23283,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 */
@@ -23247,8 +23304,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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());
+ )
+ /* Look back at the past 7 days of events at most */
+ AND t.StartTime > DATEADD(DAY, -7, SYSDATETIME());
/* Update event names from map */
UPDATE
@@ -24642,7 +24700,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
IF @debug = 1
BEGIN
- PRINT @file_io_sql;
+ PRINT @db_size_sql;
END;
/* For non-Azure SQL DB, get size across all accessible databases */
@@ -26056,32 +26114,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
check_id = 7005,
priority = 50, /* Medium priority */
category = N'Database Configuration',
- finding = N'Non-Standard ANSI Settings',
+ finding = N'ANSI Settings Require Review',
database_name = d.name,
details =
- N'Database has non-standard ANSI settings: ' +
- CASE WHEN d.is_ansi_null_default_on = 1 THEN N'ANSI_NULL_DEFAULT ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_nulls_on = 1 THEN N'ANSI_NULLS ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_padding_on = 1 THEN N'ANSI_PADDING ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_warnings_on = 1 THEN N'ANSI_WARNINGS ON, ' ELSE N'' END +
- CASE WHEN d.is_arithabort_on = 1 THEN N'ARITHABORT ON, ' ELSE N'' END +
- CASE WHEN d.is_concat_null_yields_null_on = 1 THEN N'CONCAT_NULL_YIELDS_NULL ON, ' ELSE N'' END +
- CASE WHEN d.is_numeric_roundabort_on = 1 THEN N'NUMERIC_ROUNDABORT ON, ' ELSE N'' END +
- CASE WHEN d.is_quoted_identifier_on = 1 THEN N'QUOTED_IDENTIFIER ON, ' ELSE N'' END +
- N'which can cause unexpected application behavior and compatibility issues.',
+ 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 = 1
- OR d.is_ansi_nulls_on = 1
- OR d.is_ansi_padding_on = 1
- OR d.is_ansi_warnings_on = 1
- OR d.is_arithabort_on = 1
- OR d.is_concat_null_yields_null_on = 1
+ 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 = 1
+ OR d.is_quoted_identifier_on = 0
);
/* Check Query Store Status */
@@ -26910,6 +26969,7 @@ ALTER PROCEDURE
@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*/
@@ -26924,8 +26984,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET LANGUAGE us_english;
SELECT
- @version = '6.0',
- @version_date = '20260115';
+ @version = '6.1',
+ @version_date = '20260201';
IF @help = 1
@@ -26969,6 +27029,7 @@ BEGIN
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'
@@ -26990,6 +27051,7 @@ BEGIN
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'
@@ -27011,6 +27073,7 @@ BEGIN
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'
@@ -27057,6 +27120,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.
*/
@@ -27067,7 +27138,10 @@ END; /*End help section*/
@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);
@@ -27679,7 +27753,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
plan_handle varbinary(64) NULL,
sql_text xml NULL,
query_plan_xml xml NULL,
- live_query_plan 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;
@@ -27748,14 +27822,6 @@ OPTION(MAXDOP 1, RECOMPILE);',
@log_table_name_prefix,
@debug;
- 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'
@@ -28106,8 +28172,6 @@ OPTION(MAXDOP 1, RECOMPILE);',
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'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'
@@ -28945,11 +29009,15 @@ OPTION(MAXDOP 1, RECOMPILE);',
FORMAT
(
dopc.cntr_value /
- DATEDIFF
+ ISNULL
(
- SECOND,
- dopc.sample_time,
- SYSDATETIME()
+ DATEDIFF
+ (
+ SECOND,
+ dopc.sample_time,
+ SYSDATETIME()
+ ),
+ 1
),
'N0'
)
@@ -28992,7 +29060,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
FORMAT((dopc2.cntr_value - dopc.cntr_value), 'N0'),
total_difference_per_second =
FORMAT((dopc2.cntr_value - dopc.cntr_value) /
- DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time), 'N0'),
+ ISNULL(DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time), 1), 'N0'),
sample_seconds =
DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time),
first_sample_time =
@@ -29440,7 +29508,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
SELECT
@total_physical_memory_gb =
SUM(osi.committed_target_kb / 1024. / 1024.)
- FROM sys.dm_os_sys_info osi
+ FROM sys.dm_os_sys_info AS osi
OPTION(MAXDOP 1, RECOMPILE);
END;
@@ -30052,7 +30120,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
CASE
WHEN @live_plans = 1
THEN N'
- OUTER APPLY sys.dm_exec_query_statistics_xml(deqmg.plan_handle) AS deqs'
+ OUTER APPLY sys.dm_exec_query_statistics_xml(deqmg.session_id) AS deqs'
ELSE N''
END +
N'
@@ -30530,7 +30598,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
dowt.wait_duration_ms DESC
OPTION(MAXDOP 1, RECOMPILE);
- IF @@ROWCOUNT = 0
+ IF ROWCOUNT_BIG() = 0
BEGIN
SELECT
THREADPOOL = N'No current THREADPOOL waits';
@@ -30756,7 +30824,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
CASE
WHEN @live_plans = 1
THEN N'
- OUTER APPLY sys.dm_exec_query_statistics_xml(der.plan_handle) AS deqs'
+ OUTER APPLY sys.dm_exec_query_statistics_xml(der.session_id) AS deqs'
ELSE N''
END +
N'
@@ -30872,6 +30940,309 @@ OPTION(MAXDOP 1, RECOMPILE);',
GOTO DO_OVER;
END;
+ troubleshoot_blocking:
+ IF @troubleshoot_blocking = 1
+ BEGIN
+ /*
+ Blocking chain analysis - mimics sp_WhoIsActive @find_block_leaders = 1
+ Uses sys.sysprocesses to find both active and idle blockers
+ Uses recursive CTE to walk blocking chains
+ */
+
+ /*
+ Table variable to hold lead blockers (anchor rows)
+ This improves performance by materializing the anchor set once
+ */
+ DECLARE
+ @lead_blockers table
+ (
+ session_id smallint NOT NULL
+ PRIMARY KEY WITH (IGNORE_DUP_KEY = ON),
+ blocking_session_id smallint NOT NULL,
+ blocking_chain nvarchar(4000) NOT NULL
+ );
+
+ /*
+ Find lead blockers: sessions that are blocking others
+ but not blocked themselves (includes idle sessions with open transactions)
+ */
+ INSERT
+ @lead_blockers
+ (
+ session_id,
+ blocking_session_id,
+ blocking_chain
+ )
+ SELECT
+ session_id = sp.spid,
+ blocking_session_id = sp.blocked,
+ blocking_chain =
+ CONVERT
+ (
+ nvarchar(4000),
+ CONVERT(nvarchar(20), sp.spid) + N' lead blocker'
+ )
+ FROM sys.sysprocesses AS sp
+ WHERE sp.blocked = 0
+ AND EXISTS
+ (
+ SELECT
+ 1/0
+ FROM sys.sysprocesses AS sp2
+ WHERE sp2.blocked = sp.spid
+ );
+
+ /*
+ Recursive CTE to walk blocking chains
+ Anchor: lead blockers from table variable
+ Recursive: sessions blocked by current level
+ */
+ WITH
+ blockers
+ (
+ session_id,
+ blocking_session_id,
+ blocking_level,
+ top_level_blocker,
+ blocking_chain,
+ visited_path
+ ) AS
+ (
+ /*
+ Anchor: Lead blockers from table variable
+ */
+ SELECT
+ session_id = lb.session_id,
+ blocking_session_id = lb.blocking_session_id,
+ blocking_level = 0,
+ top_level_blocker = lb.session_id,
+ blocking_chain = lb.blocking_chain,
+ visited_path =
+ CONVERT
+ (
+ nvarchar(4000),
+ N'.' + CONVERT(nvarchar(20), lb.session_id) + N'.'
+ )
+ FROM @lead_blockers AS lb
+
+ UNION ALL
+
+ /*
+ Recursive: Walk down the blocking chain
+ */
+ SELECT
+ session_id = sp.spid,
+ blocking_session_id = sp.blocked,
+ blocking_level = b.blocking_level + 1,
+ top_level_blocker = b.top_level_blocker,
+ blocking_chain =
+ CONVERT
+ (
+ nvarchar(4000),
+ REPLICATE(N' > ', b.blocking_level + 1) +
+ CONVERT(nvarchar(20), sp.blocked) +
+ N' blocking ' +
+ CONVERT(nvarchar(20), sp.spid)
+ ),
+ visited_path =
+ CONVERT
+ (
+ nvarchar(4000),
+ b.visited_path + CONVERT(nvarchar(20), sp.spid) + N'.'
+ )
+ FROM blockers AS b
+ JOIN sys.sysprocesses AS sp
+ ON sp.blocked = b.session_id
+ WHERE b.visited_path NOT LIKE
+ N'%.' + CONVERT(nvarchar(20), sp.spid) + N'.%'
+ ),
+ blocking_info
+ (
+ session_id,
+ blocking_session_id,
+ blocking_level,
+ top_level_blocker,
+ blocking_chain,
+ blocked_session_count,
+ last_batch,
+ status,
+ wait_type,
+ wait_time,
+ wait_resource,
+ cpu_time,
+ physical_io,
+ memusage,
+ open_transaction_count,
+ database_name,
+ command,
+ sql_handle,
+ statement_start_offset,
+ statement_end_offset,
+ login_name,
+ host_name,
+ program_name,
+ login_time
+ ) AS
+ (
+ /*
+ Join blocking chain results to sysprocesses for session details
+ SQL text and query plans are NOT retrieved here - done in final SELECT
+ */
+ SELECT
+ b.session_id,
+ b.blocking_session_id,
+ b.blocking_level,
+ b.top_level_blocker,
+ b.blocking_chain,
+ blocked_session_count =
+ (
+ SELECT
+ COUNT_BIG(*)
+ FROM blockers AS b2
+ WHERE b2.visited_path LIKE
+ N'%.' + CONVERT(nvarchar(20), b.session_id) + N'.%'
+ AND b2.session_id <> b.session_id
+ ),
+ sp.last_batch,
+ sp.status,
+ wait_type = sp.lastwaittype,
+ wait_time = sp.waittime,
+ wait_resource = sp.waitresource,
+ cpu_time = sp.cpu,
+ physical_io = sp.physical_io,
+ sp.memusage,
+ open_transaction_count = sp.open_tran,
+ database_name = DB_NAME(sp.dbid),
+ command = sp.cmd,
+ sp.sql_handle,
+ statement_start_offset = sp.stmt_start,
+ statement_end_offset = sp.stmt_end,
+ login_name = sp.loginame,
+ host_name = sp.hostname,
+ program_name = sp.program_name,
+ sp.login_time
+ FROM blockers AS b
+ JOIN sys.sysprocesses AS sp
+ ON sp.spid = b.session_id
+ )
+ /*
+ Final SELECT: retrieve SQL text and query plans last for performance
+ Column order matches sp_WhoIsActive where possible
+ */
+ SELECT
+ [dd hh:mm:ss.mss] =
+ CASE
+ WHEN e.elapsed_time_ms < 0
+ THEN RIGHT(REPLICATE('0', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
+ ' ' +
+ RIGHT(CONVERT(varchar(30), DATEADD(SECOND, (-1 * e.elapsed_time_ms), 0), 120), 9) +
+ '.000'
+ ELSE RIGHT(REPLICATE('0', 2) +
+ CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
+ ' ' +
+ RIGHT(CONVERT(varchar(30), DATEADD(SECOND, e.elapsed_time_ms / 1000, 0), 120), 9) +
+ '.' +
+ RIGHT('000' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
+ END,
+ bi.session_id,
+ blocking_session_id = NULLIF(bi.blocking_session_id, 0),
+ bi.blocking_level,
+ bi.top_level_blocker,
+ bi.blocking_chain,
+ bi.blocked_session_count,
+ query_text =
+ (
+ SELECT
+ [processing-instruction(query)] =
+ SUBSTRING
+ (
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(
+ dest.text COLLATE Latin1_General_BIN2,
+ NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
+ NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'),
+ NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N''),
+ N'', N'??'), N'?>', N'??'),
+ (bi.statement_start_offset / 2) + 1,
+ (
+ (
+ CASE bi.statement_end_offset
+ WHEN -1
+ THEN DATALENGTH(dest.text)
+ ELSE bi.statement_end_offset
+ END - bi.statement_start_offset
+ ) / 2
+ ) + 1
+ )
+ FOR XML PATH(''),
+ TYPE
+ ),
+ query_plan =
+ CASE
+ WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
+ THEN TRY_CAST(deqp.query_plan AS xml)
+ WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
+ THEN
+ (
+ SELECT
+ [processing-instruction(query_plan)] =
+ N'-- ' + NCHAR(13) + NCHAR(10) +
+ N'-- This is a huge query plan.' + NCHAR(13) + NCHAR(10) +
+ N'-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR(13) + NCHAR(10) +
+ NCHAR(13) + NCHAR(10) +
+ REPLACE(deqp.query_plan, N' 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
@@ -31083,8 +31454,8 @@ ALTER PROCEDURE
@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 query ids to search for*/
- @include_query_ids nvarchar(4000) = NULL, /*a list of plan ids to search 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*/
@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*/
@@ -31216,12 +31587,12 @@ DECLARE
N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' +
NCHAR(10),
@nc10 nchar(1) = NCHAR(10),
- @where_clause nvarchar(MAX) = N'',
@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)
@@ -31316,6 +31687,17 @@ SELECT
)
);
+/*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
(
@@ -31577,28 +31959,81 @@ BEGIN
N'.' +
QUOTENAME(@procedure_name);
- /*Check if procedure exists in Query Store - single procedure (no wildcards)*/
- IF CHARINDEX(N'%', @procedure_name) = 0
+ /*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
- @procedure_exists =
- CASE
- WHEN EXISTS
+ @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 ' + @database_name_quoted + N'.sys.query_store_query AS qsq
- WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
+ FROM #procedure_object_ids AS p
+ WHERE qsq.object_id = p.object_id
)
- THEN 1
- ELSE 0
- END
- OPTION(RECOMPILE);' + @nc10;
+ )
+ THEN 1
+ ELSE 0
+ END
+) AS x
+OPTION(RECOMPILE);' + @nc10;
IF @debug = 1
BEGIN
@@ -31608,7 +32043,50 @@ BEGIN
EXECUTE sys.sp_executesql
@sql,
- N'@procedure_exists bit OUTPUT, @procedure_name_quoted nvarchar(1024)',
+ 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;
@@ -31669,6 +32147,13 @@ CREATE TABLE
INDEX plan_id CLUSTERED (plan_id)
);
+CREATE TABLE
+ #procedure_object_ids
+(
+ object_id bigint NOT NULL,
+ INDEX object_id CLUSTERED (object_id)
+);
+
/*
Create Query Store temp tables
*/
@@ -31971,14 +32456,6 @@ CREATE TABLE
parameter_compiled_value nvarchar(max) NULL
);
-CREATE TABLE
- #query_text_parameters
-(
- plan_id bigint NOT NULL,
- INDEX plan_id CLUSTERED (plan_id),
- parameter_declaration nvarchar(max) NULL
-);
-
CREATE TABLE
#reproduction_warnings
(
@@ -32380,7 +32857,7 @@ BEGIN
)
)';
- /*Add procedure filter if specified*/
+ /*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -32399,6 +32876,31 @@ BEGIN
)';
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;
@@ -32485,7 +32987,7 @@ BEGIN
)
)';
- /*Add procedure filter if specified*/
+ /*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -32504,6 +33006,31 @@ BEGIN
)';
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;
@@ -32906,7 +33433,7 @@ BEGIN
)';
END;
-/*Add procedure filter if specified*/
+/*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -32931,6 +33458,37 @@ BEGIN
)';
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
@@ -33144,7 +33702,7 @@ AND NOT EXISTS
)';
END;
-/*Add procedure filter if specified*/
+/*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -33163,6 +33721,31 @@ AND qsp.query_id IN
)';
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'
@@ -33192,7 +33775,7 @@ EXECUTE sys.sp_executesql
Populate the #query_store_query table with query metadata
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query';
SELECT
@@ -33250,7 +33833,7 @@ EXECUTE sys.sp_executesql
Populate the #query_store_query_text table with query text
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_text';
SELECT
@@ -33316,7 +33899,7 @@ OPTION(RECOMPILE);
Populate the #query_context_settings table with context settings
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_context_settings';
SELECT
@@ -33475,12 +34058,14 @@ OPTION(RECOMPILE);
/*
Populate the #query_store_wait_stats table with wait statistics (SQL 2017+)
*/
-SELECT
- @sql = N'',
- @current_table = N'inserting #query_store_wait_stats';
+IF @sql_2017 = 1
+BEGIN
+ SELECT
+ @sql = @isolation_level,
+ @current_table = N'inserting #query_store_wait_stats';
-SELECT
- @sql += N'
+ SELECT
+ @sql += N'
SELECT
@database_id,
qsws_with_lasts.plan_id,
@@ -33532,30 +34117,31 @@ HAVING
SUM(qsws_with_lasts.min_query_wait_time_ms) > 0.
OPTION(RECOMPILE);' + @nc10;
-IF @debug = 1
-BEGIN
- PRINT LEN(@sql);
- PRINT @sql;
-END;
+ 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;
+ 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
@@ -33566,7 +34152,7 @@ BEGIN
Populate the #query_store_plan_feedback table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_plan_feedback';
SELECT
@@ -33619,7 +34205,7 @@ OPTION(RECOMPILE);' + @nc10;
Populate the #query_store_query_variant table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_variant';
SELECT
@@ -33664,7 +34250,7 @@ OPTION(RECOMPILE);' + @nc10;
Populate the #query_store_query_hints table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_hints';
SELECT
@@ -34784,7 +35370,7 @@ ALTER PROCEDURE
@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, want do you want the results sorted by ('magnitude', 'improved', or 'regressed')?*/
+ @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.*/
@@ -34806,8 +35392,8 @@ BEGIN TRY
These are for your outputs.
*/
SELECT
- @version = '6.0',
- @version_date = '20260115';
+ @version = '6.1',
+ @version_date = '20260201';
/*
Helpful section! For help.
@@ -37465,8 +38051,14 @@ SELECT
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 =
@@ -37906,7 +38498,7 @@ OPTION(RECOMPILE);' + @nc10;
IF ROWCOUNT_BIG() = 0
BEGIN
- RAISERROR('No object_ids were found for %s in schema %s', 11, 1, @procedure_schema, @procedure_name) WITH NOWAIT;
+ RAISERROR('No object_ids were found for %s in schema %s', 11, 1, @procedure_name, @procedure_schema) WITH NOWAIT;
RETURN;
END;
@@ -38873,7 +39465,7 @@ BEGIN
PRINT @dynamic_sql;
END;
- EXEC sys.sp_executesql
+ EXECUTE sys.sp_executesql
@dynamic_sql,
N'@split_sql nvarchar(max),
@param_value nvarchar(4000)',
@@ -40199,7 +40791,7 @@ BEGIN
SELECT
qsq.query_hash,
plan_hash_count_for_query_hash =
- COUNT(DISTINCT qsp.query_plan_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
@@ -41856,7 +42448,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -41965,7 +42557,7 @@ BEGIN
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42059,7 +42651,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42429,7 +43021,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42564,7 +43156,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42655,7 +43247,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42832,7 +43424,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42902,7 +43494,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -42978,7 +43570,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -43053,7 +43645,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -43123,7 +43715,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -43555,34 +44147,34 @@ ORDER BY
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 money) ASC,
+ 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 money) DESC,
+ 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 money)) DESC,
+ 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 money)'
- WHEN 'logical reads' THEN N'TRY_PARSE(x.avg_logical_io_reads_mb AS money)'
- WHEN 'physical reads' THEN N'TRY_PARSE(x.avg_physical_io_reads_mb AS money)'
- WHEN 'writes' THEN N'TRY_PARSE(x.avg_logical_io_writes_mb AS money)'
- WHEN 'duration' THEN N'TRY_PARSE(x.avg_duration_ms AS money)'
- WHEN 'memory' THEN N'TRY_PARSE(x.avg_query_max_used_memory_mb AS money)'
- WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.avg_tempdb_space_used_mb AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END
- WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)'
+ 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 money)'
- WHEN 'plan count by hashes' THEN N'TRY_PARSE(x.plan_hash_count_for_query_hash AS money) DESC,
+ WHEN 'rows' THEN N'TRY_PARSE(x.avg_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 money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END
+ 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
@@ -44118,12 +44710,11 @@ BEGIN
IF @rc > 0
BEGIN
SELECT
- @current_table = 'selecting resource stats';
-
- SET @sql = N'';
+ @current_table = 'selecting resource stats',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -44344,12 +44935,11 @@ BEGIN
Wait stats by query
*/
SELECT
- @current_table = 'selecting wait stats by query';
-
- SET @sql = N'';
+ @current_table = 'selecting wait stats by query',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -44484,12 +45074,11 @@ BEGIN
Wait stats in total
*/
SELECT
- @current_table = 'selecting wait stats in total';
-
- SET @sql = N'';
+ @current_table = 'selecting wait stats in total',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -44664,7 +45253,7 @@ BEGIN
BEGIN
SELECT
@current_table = 'selecting query store options',
- @sql = N'';
+ @sql = @isolation_level;
SELECT
@sql +=
@@ -45037,7 +45626,7 @@ BEGIN
@only_queries_with_hints,
only_query_with_feedback =
@only_queries_with_feedback,
- only_query_with_hints =
+ only_query_with_variants =
@only_queries_with_variants,
only_queries_with_forced_plans =
@only_queries_with_forced_plans,
diff --git a/sp_HealthParser/sp_HealthParser.sql b/sp_HealthParser/sp_HealthParser.sql
index 806a87e8..b7e8209d 100644
--- a/sp_HealthParser/sp_HealthParser.sql
+++ b/sp_HealthParser/sp_HealthParser.sql
@@ -70,8 +70,8 @@ BEGIN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '3.0',
- @version_date = '20260115';
+ @version = '3.2',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -142,7 +142,7 @@ BEGIN
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'0'
+ 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'@pending_task_threshold' THEN N'10'
@@ -234,15 +234,15 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@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'',
+ @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),
+ @collection_sql nvarchar(MAX),
/*Log to table stuff*/
@log_table_significant_waits sysname,
@log_table_waits_by_count sysname,
@@ -256,14 +256,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@log_table_scheduler_issues sysname,
@log_table_severe_errors sysname,
@cleanup_date datetime2(7),
- @check_sql nvarchar(max) = N'',
- @create_sql nvarchar(max) = N'',
- @insert_sql nvarchar(max) = N'',
+ @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'',
+ @dsql nvarchar(MAX) = N'',
+ @mdsql_template nvarchar(MAX) = N'',
+ @mdsql_execute nvarchar(MAX) = N'',
@start_date_debug nvarchar(50),
@end_date_debug nvarchar(50);
@@ -450,7 +450,7 @@ AND ca.utc_timestamp < @end_date';
SELECT
@what_to_check = LOWER(ISNULL(@what_to_check, 'all')),
@warnings_only = ISNULL(@warnings_only, 0),
- @wait_duration_ms = ISNULL(@wait_duration_ms, 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),
@pending_task_threshold = ISNULL(@pending_task_threshold, 10);
@@ -2047,7 +2047,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(CONVERT(date, @end_date)) +
'.'
ELSE 'no significant waits found!'
- END
+ END;
RAISERROR('No waits by count found', 0, 0) WITH NOWAIT;
END;
@@ -2307,7 +2307,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@wait_duration_ms) +
'ms.'
ELSE 'no significant waits found!'
- END
+ END;
RAISERROR('No waits by duration', 0, 0) WITH NOWAIT;
END;
@@ -2574,7 +2574,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no io issues found!'
- END
+ END;
RAISERROR('No io data found', 0, 0) WITH NOWAIT;
END;
END;
@@ -2731,7 +2731,7 @@ AND ca.utc_timestamp < @end_date';
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 IS NULL)
+ 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);
@@ -2779,7 +2779,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no cpu issues found!'
- END
+ END;
RAISERROR('No scheduler data found', 0, 0) WITH NOWAIT;
END;
@@ -2987,7 +2987,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no memory issues found!'
- END
+ END;
RAISERROR('No memory condition data found', 0, 0) WITH NOWAIT;
END;
@@ -3218,7 +3218,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no memory pressure events found!'
- END
+ END;
RAISERROR('No memory broker data found', 0, 0) WITH NOWAIT;
END;
@@ -3510,7 +3510,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(CONVERT(date, @end_date)) +
'.'
ELSE 'no memory node OOM events found!'
- END
+ END;
RAISERROR('No memory oom data found', 0, 0) WITH NOWAIT;
END;
@@ -3912,7 +3912,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no system health issues found!'
- END
+ END;
RAISERROR('No system health data found', 0, 0) WITH NOWAIT;
END;
@@ -4107,7 +4107,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no scheduler issues found!'
- END
+ END;
RAISERROR('No scheduler issues data found', 0, 0) WITH NOWAIT;
END;
@@ -4334,7 +4334,7 @@ AND ca.utc_timestamp < @end_date';
RTRIM(@warnings_only) +
'.'
ELSE 'no severe errors found!'
- END
+ END;
RAISERROR('No error data found', 0, 0) WITH NOWAIT;
END;
@@ -4502,7 +4502,7 @@ AND ca.utc_timestamp < @end_date';
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 IS NULL)
+ AND (w.x.exist('(data[@name="state"]/text[.= "WARNING"])') = @warnings_only OR @warnings_only = 0)
OPTION(RECOMPILE);
IF @debug = 1
@@ -4533,6 +4533,14 @@ AND ca.utc_timestamp < @end_date';
AND @log_to_table = 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;
@@ -4585,20 +4593,20 @@ AND ca.utc_timestamp < @end_date';
SELECT
bx.event_time,
- currentdbname = bd.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
+ 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(100)'),
+ wait_resource = bd.value('(process/@waitresource)[1]', 'sysname'),
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)'),
+ 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'),
@@ -4610,6 +4618,7 @@ AND ca.utc_timestamp < @end_date';
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
@@ -4646,20 +4655,20 @@ AND ca.utc_timestamp < @end_date';
SELECT
bx.event_time,
- currentdbname = bg.value('(process/@currentdbname)[1]', 'nvarchar(128)'),
+ 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(100)'),
+ wait_resource = bg.value('(process/@waitresource)[1]', 'sysname'),
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)'),
+ 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'),
@@ -4671,6 +4680,7 @@ AND ca.utc_timestamp < @end_date';
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
@@ -4927,7 +4937,7 @@ AND ca.utc_timestamp < @end_date';
'available plans for blocking',
b.currentdbname,
query_text =
- TRY_CAST(b.query_text AS nvarchar(max)),
+ TRY_CAST(b.query_text AS nvarchar(MAX)),
sql_handle =
CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
stmtstart =
@@ -4946,7 +4956,7 @@ AND ca.utc_timestamp < @end_date';
CONVERT(varchar(30), 'available plans for blocking'),
b.currentdbname,
query_text =
- TRY_CAST(b.query_text AS nvarchar(max)),
+ TRY_CAST(b.query_text AS nvarchar(MAX)),
sql_handle =
CONVERT(varbinary(64), n.c.value('@sqlhandle', 'varchar(130)'), 1),
stmtstart =
@@ -5077,23 +5087,23 @@ AND ca.utc_timestamp < @end_date';
),
d.victim_id,
d.deadlock_graph,
- id = e.x.value('@id', 'nvarchar(256)'),
+ id = e.x.value('@id', 'sysname'),
database_id = e.x.value('@currentdb', 'bigint'),
- current_database_name = e.x.value('@currentdbname', 'nvarchar(256)'),
+ 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', 'nvarchar(256)'),
- last_tran_started = e.x.value('@lasttranstarted', 'datetime'),
- last_batch_started = e.x.value('@lastbatchstarted', 'datetime'),
- last_batch_completed = e.x.value('@lastbatchcompleted', 'datetime'),
- lock_mode = e.x.value('@lockMode', 'nvarchar(256)'),
- status = e.x.value('@status', 'nvarchar(256)'),
+ 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', 'nvarchar(256)'),
- login_name = e.x.value('@loginname', 'nvarchar(256)'),
- isolation_level = e.x.value('@isolationlevel', 'nvarchar(256)'),
+ 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('(//process/inputbuf/text())[1]', 'nvarchar(max)'),
@@ -5305,11 +5315,11 @@ AND ca.utc_timestamp < @end_date';
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -5333,13 +5343,13 @@ AND ca.utc_timestamp < @end_date';
total_logical_reads_mb =
deqs.total_logical_reads * 8. / 1024.,
min_grant_mb =
- deqs.min_grant_kb * 8. / 1024.,
+ deqs.min_grant_kb / 1024.,
max_grant_mb =
- deqs.max_grant_kb * 8. / 1024.,
+ deqs.max_grant_kb / 1024.,
min_used_grant_mb =
- deqs.min_used_grant_kb * 8. / 1024.,
+ deqs.min_used_grant_kb / 1024.,
max_used_grant_mb =
- deqs.max_used_grant_kb * 8. / 1024.,
+ deqs.max_used_grant_kb / 1024.,
deqs.min_reserved_threads,
deqs.max_reserved_threads,
deqs.min_used_threads,
@@ -5403,7 +5413,7 @@ AND ca.utc_timestamp < @end_date';
ap.sql_handle,
ap.statement_start_offset,
ap.statement_end_offset
- INTO #all_avalable_plans
+ INTO #all_available_plans
FROM
(
SELECT
@@ -5457,13 +5467,13 @@ AND ca.utc_timestamp < @end_date';
(
SELECT
1/0
- FROM #all_avalable_plans AS ap
+ FROM #all_available_plans AS ap
WHERE ap.finding = 'available plans for blocking'
)
BEGIN
SELECT
aap.*
- FROM #all_avalable_plans AS aap
+ FROM #all_available_plans AS aap
WHERE aap.finding = 'available plans for blocking'
ORDER BY
aap.avg_worker_time_ms DESC
@@ -5488,13 +5498,13 @@ AND ca.utc_timestamp < @end_date';
(
SELECT
1/0
- FROM #all_avalable_plans AS ap
+ FROM #all_available_plans AS ap
WHERE ap.finding = 'available plans for deadlocks'
)
BEGIN
SELECT
aap.*
- FROM #all_avalable_plans AS aap
+ FROM #all_available_plans AS aap
WHERE aap.finding = 'available plans for deadlocks'
ORDER BY
aap.avg_worker_time_ms DESC
diff --git a/sp_HumanEvents/sp_HumanEvents.sql b/sp_HumanEvents/sp_HumanEvents.sql
index 5c900996..707b3213 100644
--- a/sp_HumanEvents/sp_HumanEvents.sql
+++ b/sp_HumanEvents/sp_HumanEvents.sql
@@ -88,8 +88,8 @@ SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '7.0',
- @version_date = '20260115';
+ @version = '7.2',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -154,7 +154,7 @@ BEGIN
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 minimums for query, wait, and blocking durations. only use if you''re okay with potentially adding a lot of observer overhead on your system, or for testing purposes.'
+ 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'
@@ -395,8 +395,8 @@ CREATE TABLE
event_type_short sysname NOT NULL,
is_table_created bit NOT NULL DEFAULT 0,
is_view_created bit NOT NULL DEFAULT 0,
- last_checked datetime NOT NULL DEFAULT '19000101',
- last_updated datetime NOT NULL DEFAULT '19000101',
+ 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
@@ -515,7 +515,7 @@ DECLARE
@spe nvarchar(max) = N'.sys.sp_executesql ',
@view_sql nvarchar(max) = N'',
@view_database sysname = N'',
- @date_filter datetime,
+ @date_filter datetime2(7),
@Time time,
@delete_tracker integer,
@the_deleter_must_awaken nvarchar(max) = N'',
@@ -994,7 +994,7 @@ BEGIN
);
/* If we find any invalid waits, let people know */
- IF @@ROWCOUNT > 0
+ IF ROWCOUNT_BIG() > 0
BEGIN
SELECT
invalid_waits =
@@ -1236,9 +1236,9 @@ 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 < 0
+IF @delete_retention_days < 1
BEGIN
- SET @delete_retention_days *= -1;
+ 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;
@@ -1338,7 +1338,7 @@ END;
IF @requested_memory_mb > 0
BEGIN
- SET @requested_memory_kb = @requested_memory_mb / 1024.;
+ SET @requested_memory_kb = @requested_memory_mb * 1024.;
SET @requested_memory_mb_filter += N' AND requested_memory_kb >= ' + @requested_memory_kb + @nc10;
END;
@@ -2828,7 +2828,7 @@ BEGIN
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/@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,
@@ -2923,7 +2923,7 @@ BEGIN
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/@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,
@@ -3215,7 +3215,7 @@ BEGIN
)
FROM #blocking AS bg
WHERE (bg.database_name = @database_name
- OR @database_name IS NULL)
+ OR @database_name = N'')
UNION ALL
@@ -3229,7 +3229,7 @@ BEGIN
)
FROM #blocked AS bd
WHERE (bd.database_name = @database_name
- OR @database_name IS NULL)
+ OR @database_name = N'')
) AS kheb
OPTION(RECOMPILE);
@@ -3282,7 +3282,7 @@ BEGIN
) AS b
WHERE b.n = 1
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
ORDER BY
b.sort_order,
CASE
@@ -3316,9 +3316,9 @@ BEGIN
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 @database_name = N'')
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
UNION ALL
@@ -3339,11 +3339,11 @@ BEGIN
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)
+ 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 @database_name = N'')
AND (b.contentious_object = @object_name
- OR @object_name IS NULL)
+ OR @object_name = N'')
) AS b
OPTION(RECOMPILE);
@@ -3360,11 +3360,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -3528,6 +3528,7 @@ IF @debug = 1 BEGIN RAISERROR(N'Starting data collection.', 0, 1) WITH NOWAIT; E
WHILE 1 = 1
BEGIN
+ SET @the_sleeper_must_awaken = N'';
IF @azure = 0
BEGIN
IF NOT EXISTS
@@ -3582,21 +3583,22 @@ BEGIN
N' ON DATABASE STATE = START;' +
@nc10
FROM sys.database_event_sessions AS ses
- JOIN sys.dm_xe_database_sessions AS dxs
+ LEFT JOIN sys.dm_xe_database_sessions AS dxs
ON dxs.name = ses.name
- WHERE ses.name LIKE N'keeper_HumanEvents_%';
+ 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;
+ 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
@@ -4702,7 +4704,7 @@ ORDER BY
/* this executes the insert */
EXECUTE sys.sp_executesql
@table_sql,
- N'@date_filter datetime',
+ N'@date_filter datetime2(7)',
@date_filter;
/*Update the worker table's last checked, and conditionally, updated dates*/
@@ -4713,7 +4715,7 @@ ORDER BY
SYSDATETIME(),
hew.last_updated =
CASE
- WHEN @@ROWCOUNT > 0
+ WHEN ROWCOUNT_BIG() > 0
THEN SYSDATETIME()
ELSE hew.last_updated
END
@@ -4773,6 +4775,7 @@ BEGIN
OR @delete_tracker <> DATEPART(HOUR, @Time)
)
BEGIN
+ SET @the_deleter_must_awaken = N'';
SELECT
@the_deleter_must_awaken +=
N' DELETE FROM ' +
@@ -4794,7 +4797,7 @@ BEGIN
/* execute the delete */
EXECUTE sys.sp_executesql
@the_deleter_must_awaken,
- N'@delete_retention_days INT',
+ N'@delete_retention_days integer',
@delete_retention_days;
/* set this to the hour it was last checked */
@@ -4813,17 +4816,33 @@ BEGIN
SET @executer = QUOTENAME(@output_database_name) + N'.sys.sp_executesql ';
- /*Clean up sessions, this isn't database-specific*/
- 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_%';
+ /*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;
@@ -4922,7 +4941,6 @@ BEGIN CATCH
THROW;
- RETURN -138;
END;
END CATCH;
END;
diff --git a/sp_HumanEvents/sp_HumanEventsBlockViewer.sql b/sp_HumanEvents/sp_HumanEventsBlockViewer.sql
index 7dc00c85..f00225ff 100644
--- a/sp_HumanEvents/sp_HumanEventsBlockViewer.sql
+++ b/sp_HumanEvents/sp_HumanEventsBlockViewer.sql
@@ -93,8 +93,8 @@ SET XACT_ABORT OFF;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT
- @version = '5.0',
- @version_date = '20260115';
+ @version = '5.2',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -887,7 +887,6 @@ CREATE TABLE
IF LOWER(@target_type) = N'table'
BEGIN
GOTO TableMode;
- RETURN;
END;
/*Look to see if the session exists and is running*/
@@ -1078,13 +1077,13 @@ BEGIN
SELECT
@session_id =
- t.event_session_address,
+ t.event_session_id,
@target_session_id =
- t.target_name
- FROM sys.dm_xe_database_session_targets t
- JOIN sys.dm_xe_database_sessions s
- ON s.address = t.event_session_address
- WHERE t.target_name = @target_type
+ 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);
@@ -1104,7 +1103,7 @@ BEGIN
nvarchar(4000),
f.value
)
- FROM sys.server_event_session_fields AS f
+ 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'
@@ -1316,8 +1315,8 @@ BEGIN
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'),
+ 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'),
@@ -1601,6 +1600,7 @@ BEGIN
END
OPTION(RECOMPILE);
+ IF @debug = 1
BEGIN
RAISERROR('Inserting to #available_plans_sh', 0, 1) WITH NOWAIT;
END;
@@ -1670,11 +1670,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -1685,7 +1685,7 @@ BEGIN
(
SECOND,
deqs.creation_time,
- NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
+ NULLIF(deqs.last_execution_time, '19000101')
),
0
),
@@ -1860,10 +1860,8 @@ BEGIN
IF @timestamp_column IS NULL
BEGIN
- BEGIN
- SET @extract_sql = @extract_sql + N'
+ SET @extract_sql = @extract_sql + N'
AND e.x.exist(''@timestamp[. >= sql:variable("@start_date") and . < sql:variable("@end_date")]'') = 1';
- END;
END;
SET @extract_sql = @extract_sql + N'
@@ -1941,7 +1939,7 @@ SELECT
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/@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,
@@ -2681,7 +2679,7 @@ BEGIN
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)
+ 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
@@ -2716,11 +2714,11 @@ BEGIN
total_worker_time_ms =
deqs.total_worker_time / 1000.,
avg_worker_time_ms =
- CONVERT(decimal(38, 6), deqs.total_worker_time / 1000. / deqs.execution_count),
+ 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. / deqs.execution_count),
+ CONVERT(decimal(38, 6), deqs.total_elapsed_time / 1000. / NULLIF(deqs.execution_count, 0)),
executions_per_second =
ISNULL
(
@@ -2731,7 +2729,7 @@ BEGIN
(
SECOND,
deqs.creation_time,
- NULLIF(deqs.last_execution_time, '1900-01-01 00:00:00.000')
+ NULLIF(deqs.last_execution_time, '19000101')
),
0
),
@@ -3226,7 +3224,7 @@ BEGIN
finding =
N'There have been ' +
CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
- N' background tasks involved in blocking sessions in ' +
+ N' done queries involved in blocking sessions in ' +
b.database_name +
N'.',
sort_order =
@@ -3268,7 +3266,7 @@ BEGIN
finding =
N'There have been ' +
CONVERT(nvarchar(20), COUNT_BIG(DISTINCT b.transaction_id)) +
- N' background tasks involved in blocking sessions in ' +
+ N' done queries involved in blocking sessions in ' +
b.database_name +
N'.',
sort_order =
diff --git a/sp_IndexCleanup/sp_IndexCleanup.sql b/sp_IndexCleanup/sp_IndexCleanup.sql
index ab8ecc4b..a2c781ef 100644
--- a/sp_IndexCleanup/sp_IndexCleanup.sql
+++ b/sp_IndexCleanup/sp_IndexCleanup.sql
@@ -72,8 +72,8 @@ BEGIN
SET NOCOUNT ON;
BEGIN TRY
SELECT
- @version = '2.0',
- @version_date = '20260115';
+ @version = '2.2',
+ @version_date = '20260201';
IF
/* Check SQL Server 2012+ for FORMAT and CONCAT functions */
@@ -184,12 +184,12 @@ BEGIN TRY
WHEN N'@min_writes' THEN '0'
WHEN N'@min_size_gb' THEN '0'
WHEN N'@min_rows' THEN '0'
- WHEN N'@dedupe_only' THEN '0'
- WHEN N'@get_all_databases' 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 'true'
+ WHEN N'@debug' THEN 'false'
WHEN N'@version' THEN 'NULL'
WHEN N'@version_date' THEN 'NULL'
ELSE NULL
@@ -297,8 +297,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/* Azure SQL DB or Managed Instance */
WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (5, 8)
THEN 1
- /* SQL Server 2019+ */
- WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) = 3
+ /* SQL Server 2019+ (Enterprise or Standard) */
+ WHEN CONVERT(integer, SERVERPROPERTY('EngineEdition')) IN (2, 3)
AND CONVERT
(
integer,
@@ -1061,55 +1061,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;
END;
- 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;
-
/* Parse @exclude_databases comma-separated list */
IF @get_all_databases = 1
AND @exclude_databases IS NOT NULL
@@ -1291,6 +1242,59 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 */
@@ -1427,9 +1431,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 ' + QUOTENAME(@current_database_name) + N'.sys.dm_db_index_usage_stats AS us
- ON i.object_id = us.object_id
- AND us.database_id = @database_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
@@ -2309,7 +2311,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@sql,
N'@database_id integer,
@object_id integer,
- @min_rows integer',
+ @min_rows bigint',
@current_database_id,
@object_id,
@min_rows;
@@ -3098,7 +3100,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]') + N', %') ESCAPE '~'
+ 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
@@ -3130,7 +3132,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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(ia1.key_columns, '~', '~~'), '[', '~['), ']', '~]') + N', %') ESCAPE '~' /* ia2 has wider key that starts with ia1's key */
+ 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)
@@ -3182,7 +3184,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
OPTION(RECOMPILE);
- DECLARE @rule3_rowcount bigint = @@ROWCOUNT;
+ DECLARE @rule3_rowcount bigint = ROWCOUNT_BIG();
IF @debug = 1
BEGIN
@@ -3315,7 +3317,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
OPTION(RECOMPILE);
- DECLARE @rule5_rowcount bigint = @@ROWCOUNT;
+ DECLARE @rule5_rowcount bigint = ROWCOUNT_BIG();
IF @debug = 1
BEGIN
@@ -3329,92 +3331,114 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END;
/* Rule 6: Merge includes from subset to superset indexes */
- WITH
- KeySubsetSuperset AS
- (
+ /* Pre-compute merged includes in a temp table to handle multiple subsets per superset */
+ IF @debug = 1
+ BEGIN
SELECT
- superset.database_id,
- superset.object_id,
- superset.index_id,
- superset.index_name,
- superset.index_hash,
- superset.included_columns AS superset_includes,
- subset.included_columns AS subset_includes
+ 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 superset.scope_hash = subset.scope_hash
+ 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 subset.action = N'DISABLE'
AND superset.consolidation_rule = N'Key Superset'
- AND subset.consolidation_rule = N'Key Subset'
+ OPTION(RECOMPILE);
+ END;
+
+ CREATE TABLE
+ #merged_includes
+ (
+ scope_hash bigint NOT NULL,
+ index_name sysname NOT NULL,
+ key_columns nvarchar(max) NOT NULL,
+ merged_includes nvarchar(max) NULL,
+ PRIMARY KEY (scope_hash, index_name)
+ );
+
+ /* Gather all supersets that need include merging */
+ INSERT INTO
+ #merged_includes
+ WITH
+ (TABLOCK)
+ (
+ scope_hash,
+ index_name,
+ key_columns,
+ merged_includes
)
- UPDATE
- ia
- SET
- ia.included_columns =
- CASE
- /* If both have includes, combine them without duplicates */
- WHEN kss.superset_includes IS NOT NULL
- AND kss.subset_includes IS NOT NULL
- THEN
- /* Create combined includes using XML method that works with all SQL Server versions */
+ SELECT
+ superset.scope_hash,
+ superset.index_name,
+ superset.key_columns,
+ merged_includes =
+ STUFF
+ (
(
- SELECT
- /* Combine both sets of includes */
- combined_cols =
- STUFF
+ SELECT DISTINCT
+ N', ' +
+ t.c.value('.', 'sysname')
+ FROM
+ (
+ /* Superset's own includes */
+ SELECT
+ x = CONVERT
(
- (
- SELECT DISTINCT
- N', ' +
- t.c.value('.', 'sysname')
- FROM
- (
- /* Create XML from superset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.superset_includes, N', ', N'') +
- N''
- )
-
- UNION ALL
-
- /* Create XML from subset includes */
- SELECT
- x = CONVERT
- (
- xml,
- N'' +
- REPLACE(kss.subset_includes, N', ', N'') +
- N''
- )
- ) AS a
- /* Split XML into individual columns */
- CROSS APPLY a.x.nodes('/c') AS t(c)
- FOR
- XML
- PATH('')
- ),
- 1,
- 2,
- ''
+ xml,
+ N'' +
+ REPLACE(superset.included_columns, N', ', N'') +
+ N''
)
- )
- /* If only subset has includes, use those */
- WHEN kss.superset_includes IS NULL
- AND kss.subset_includes IS NOT NULL
- THEN kss.subset_includes
- /* If only superset has includes or neither has includes, keep superset's includes */
- ELSE kss.superset_includes
- END
+ 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 KeySubsetSuperset AS kss
- ON ia.index_hash = kss.index_hash
- WHERE ia.action = N'MERGE INCLUDES'
+ JOIN #merged_includes AS mi
+ ON mi.scope_hash = ia.scope_hash
+ AND mi.index_name = ia.index_name
OPTION(RECOMPILE);
IF @debug = 1
@@ -3437,7 +3461,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 (ia1.key_columns + N'%') /* ia2 has wider key that starts with ia1's key */
+ 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)
@@ -4116,20 +4140,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FROM #index_analysis AS ia
WHERE ia.action = N'MERGE INCLUDES'
AND ia.superseded_by IS NOT NULL
- /* This should indicate it already has all the needed includes */
+ /* Only change to KEEP if Rule 6 didn't compute merged includes for this index */
AND NOT EXISTS
(
- /* Find any indexes it supersedes that have includes not in this index */
SELECT
1/0
- FROM #index_analysis AS ia_subset
- WHERE ia_subset.scope_hash = ia.scope_hash
- AND ia_subset.key_columns = ia.key_columns
- AND ia_subset.action = N'DISABLE'
- AND ia_subset.target_index_name = ia.index_name
- /* This complex check handles cases where the superset doesn't contain all subset columns */
- AND CHARINDEX(ISNULL(ia_subset.included_columns, N''), ISNULL(ia.included_columns, N'')) = 0
- AND ISNULL(ia_subset.included_columns, N'') <> N''
+ FROM #merged_includes AS mi
+ WHERE mi.scope_hash = ia.scope_hash
+ AND mi.index_name = ia.index_name
)
OPTION(RECOMPILE);
@@ -4234,7 +4252,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CASE
WHEN ps.partition_function_name IS NOT NULL
THEN N' ON ' +
- QUOTENAME(ps.partition_function_name) +
+ QUOTENAME(ps.built_on) +
N'(' +
ISNULL(ps.partition_columns, N'') +
N')'
@@ -4622,7 +4640,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THEN N'UNIQUE '
ELSE N''
END +
- N'CLUSTERED INDEX' +
+ N'CLUSTERED INDEX ' +
QUOTENAME(fo.index_name) +
N' ON ' +
QUOTENAME(fo.database_name) +
@@ -4885,7 +4903,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
QUOTENAME(ia_uc.schema_name) +
N'.' +
QUOTENAME(ia_uc.table_name) +
- N' NOCHECK CONSTRAINT ' +
+ N' DROP CONSTRAINT ' +
QUOTENAME(ia_uc.index_name) +
N';',
/* Original index definition for validation */
@@ -6036,7 +6054,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
irs.index_name,
irs.script_type
ORDER BY
- irs.result_type DESC /* Prefer non-NULL result types */
+ irs.result_type DESC
) AS rn
FROM #index_cleanup_results AS irs
) AS ir
diff --git a/sp_LogHunter/sp_LogHunter.sql b/sp_LogHunter/sp_LogHunter.sql
index 5a6d2e57..d0f0bbec 100644
--- a/sp_LogHunter/sp_LogHunter.sql
+++ b/sp_LogHunter/sp_LogHunter.sql
@@ -73,8 +73,8 @@ SET DATEFORMAT MDY;
BEGIN
SELECT
- @version = '3.0',
- @version_date = '20260115';
+ @version = '3.2',
+ @version_date = '20260201';
IF @help = 1
BEGIN
@@ -108,7 +108,7 @@ BEGIN
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 substitions.'
+ 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;'
@@ -269,9 +269,8 @@ BEGIN
@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 integer = 0 /*total number of searches to run*/,
+ @t_searches bigint = 0 /*total number of searches to run*/,
@l_count integer = 1 /*loop count*/,
- @stopper bit = 0, /*stop loop execution safety*/
@is_rds bit =
CASE
WHEN OBJECT_ID(N'rdsadmin.dbo.rds_read_error_log', N'P') IS NOT NULL
@@ -548,7 +547,6 @@ BEGIN
IF @debug = 1 BEGIN RAISERROR('Entering WHILE loop', 0, 1) WITH NOWAIT; END;
WHILE @@FETCH_STATUS = 0
- AND @stopper = 0
BEGIN
IF @debug = 1 BEGIN RAISERROR('Entering cursor', 0, 1) WITH NOWAIT; END;
@@ -642,7 +640,6 @@ BEGIN
IF @l_log IS NULL
BEGIN
IF @debug = 1 BEGIN RAISERROR('Breaking', 0, 1) WITH NOWAIT; END;
- SET @stopper = 1;
BREAK;
END;
IF @debug = 1 BEGIN RAISERROR('Ended WHILE loop', 0, 1) WITH NOWAIT; END;
diff --git a/sp_PerfCheck/sp_PerfCheck.sql b/sp_PerfCheck/sp_PerfCheck.sql
index 7d9519bb..f24ab47f 100644
--- a/sp_PerfCheck/sp_PerfCheck.sql
+++ b/sp_PerfCheck/sp_PerfCheck.sql
@@ -64,8 +64,8 @@ BEGIN
Set version information
*/
SELECT
- @version = N'2.0',
- @version_date = N'20260115';
+ @version = N'2.2',
+ @version_date = N'20260201';
/*
Help section, for help.
@@ -106,7 +106,7 @@ BEGIN
valid_inputs =
CASE
ap.name
- WHEN N'@database_name' THEN 'the name of a database you care about indexes in'
+ 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'
@@ -118,7 +118,7 @@ BEGIN
ap.name
WHEN N'@database_name' THEN 'NULL'
WHEN N'@help' THEN 'false'
- WHEN N'@debug' THEN 'true'
+ WHEN N'@debug' THEN 'false'
WHEN N'@version' THEN 'NULL'
WHEN N'@version_date' THEN 'NULL'
ELSE NULL
@@ -843,6 +843,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
@@ -1116,6 +1121,16 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
AND domc.name = N'TokenAndPermUserStore'
AND domc.pages_kb >= 500000; /* Only if bigger than 500MB */
+ /* 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;
+
/* 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
@@ -1417,7 +1432,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
)
VALUES
(
- 5001,
+ 5000,
50,
N'Default Trace Permissions',
N'Inadequate permissions',
@@ -1488,6 +1503,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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 */
@@ -1508,8 +1524,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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());
+ )
+ /* Look back at the past 7 days of events at most */
+ AND t.StartTime > DATEADD(DAY, -7, SYSDATETIME());
/* Update event names from map */
UPDATE
@@ -2903,7 +2920,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
IF @debug = 1
BEGIN
- PRINT @file_io_sql;
+ PRINT @db_size_sql;
END;
/* For non-Azure SQL DB, get size across all accessible databases */
@@ -4317,32 +4334,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
check_id = 7005,
priority = 50, /* Medium priority */
category = N'Database Configuration',
- finding = N'Non-Standard ANSI Settings',
+ finding = N'ANSI Settings Require Review',
database_name = d.name,
details =
- N'Database has non-standard ANSI settings: ' +
- CASE WHEN d.is_ansi_null_default_on = 1 THEN N'ANSI_NULL_DEFAULT ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_nulls_on = 1 THEN N'ANSI_NULLS ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_padding_on = 1 THEN N'ANSI_PADDING ON, ' ELSE N'' END +
- CASE WHEN d.is_ansi_warnings_on = 1 THEN N'ANSI_WARNINGS ON, ' ELSE N'' END +
- CASE WHEN d.is_arithabort_on = 1 THEN N'ARITHABORT ON, ' ELSE N'' END +
- CASE WHEN d.is_concat_null_yields_null_on = 1 THEN N'CONCAT_NULL_YIELDS_NULL ON, ' ELSE N'' END +
- CASE WHEN d.is_numeric_roundabort_on = 1 THEN N'NUMERIC_ROUNDABORT ON, ' ELSE N'' END +
- CASE WHEN d.is_quoted_identifier_on = 1 THEN N'QUOTED_IDENTIFIER ON, ' ELSE N'' END +
- N'which can cause unexpected application behavior and compatibility issues.',
+ 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 = 1
- OR d.is_ansi_nulls_on = 1
- OR d.is_ansi_padding_on = 1
- OR d.is_ansi_warnings_on = 1
- OR d.is_arithabort_on = 1
- OR d.is_concat_null_yields_null_on = 1
+ 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 = 1
+ OR d.is_quoted_identifier_on = 0
);
/* Check Query Store Status */
diff --git a/sp_PressureDetector/sp_PressureDetector.sql b/sp_PressureDetector/sp_PressureDetector.sql
index 652ba6c5..fe502473 100644
--- a/sp_PressureDetector/sp_PressureDetector.sql
+++ b/sp_PressureDetector/sp_PressureDetector.sql
@@ -63,6 +63,7 @@ ALTER PROCEDURE
@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*/
@@ -77,8 +78,8 @@ SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET LANGUAGE us_english;
SELECT
- @version = '6.0',
- @version_date = '20260115';
+ @version = '6.2',
+ @version_date = '20260201';
IF @help = 1
@@ -122,6 +123,7 @@ BEGIN
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'
@@ -143,6 +145,7 @@ BEGIN
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'
@@ -164,6 +167,7 @@ BEGIN
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'
@@ -210,6 +214,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.
*/
@@ -220,7 +232,10 @@ END; /*End help section*/
@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);
@@ -832,7 +847,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
plan_handle varbinary(64) NULL,
sql_text xml NULL,
query_plan_xml xml NULL,
- live_query_plan 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;
@@ -901,14 +916,6 @@ OPTION(MAXDOP 1, RECOMPILE);',
@log_table_name_prefix,
@debug;
- 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'
@@ -1259,8 +1266,6 @@ OPTION(MAXDOP 1, RECOMPILE);',
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'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'
@@ -2098,11 +2103,15 @@ OPTION(MAXDOP 1, RECOMPILE);',
FORMAT
(
dopc.cntr_value /
- DATEDIFF
+ ISNULL
(
- SECOND,
- dopc.sample_time,
- SYSDATETIME()
+ DATEDIFF
+ (
+ SECOND,
+ dopc.sample_time,
+ SYSDATETIME()
+ ),
+ 1
),
'N0'
)
@@ -2145,7 +2154,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
FORMAT((dopc2.cntr_value - dopc.cntr_value), 'N0'),
total_difference_per_second =
FORMAT((dopc2.cntr_value - dopc.cntr_value) /
- DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time), 'N0'),
+ ISNULL(DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time), 1), 'N0'),
sample_seconds =
DATEDIFF(SECOND, dopc.sample_time, dopc2.sample_time),
first_sample_time =
@@ -2593,7 +2602,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
SELECT
@total_physical_memory_gb =
SUM(osi.committed_target_kb / 1024. / 1024.)
- FROM sys.dm_os_sys_info osi
+ FROM sys.dm_os_sys_info AS osi
OPTION(MAXDOP 1, RECOMPILE);
END;
@@ -3205,7 +3214,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
CASE
WHEN @live_plans = 1
THEN N'
- OUTER APPLY sys.dm_exec_query_statistics_xml(deqmg.plan_handle) AS deqs'
+ OUTER APPLY sys.dm_exec_query_statistics_xml(deqmg.session_id) AS deqs'
ELSE N''
END +
N'
@@ -3683,7 +3692,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
dowt.wait_duration_ms DESC
OPTION(MAXDOP 1, RECOMPILE);
- IF @@ROWCOUNT = 0
+ IF ROWCOUNT_BIG() = 0
BEGIN
SELECT
THREADPOOL = N'No current THREADPOOL waits';
@@ -3909,7 +3918,7 @@ OPTION(MAXDOP 1, RECOMPILE);',
CASE
WHEN @live_plans = 1
THEN N'
- OUTER APPLY sys.dm_exec_query_statistics_xml(der.plan_handle) AS deqs'
+ OUTER APPLY sys.dm_exec_query_statistics_xml(der.session_id) AS deqs'
ELSE N''
END +
N'
@@ -4025,6 +4034,309 @@ OPTION(MAXDOP 1, RECOMPILE);',
GOTO DO_OVER;
END;
+ troubleshoot_blocking:
+ IF @troubleshoot_blocking = 1
+ BEGIN
+ /*
+ Blocking chain analysis - mimics sp_WhoIsActive @find_block_leaders = 1
+ Uses sys.sysprocesses to find both active and idle blockers
+ Uses recursive CTE to walk blocking chains
+ */
+
+ /*
+ Table variable to hold lead blockers (anchor rows)
+ This improves performance by materializing the anchor set once
+ */
+ DECLARE
+ @lead_blockers table
+ (
+ session_id smallint NOT NULL
+ PRIMARY KEY WITH (IGNORE_DUP_KEY = ON),
+ blocking_session_id smallint NOT NULL,
+ blocking_chain nvarchar(4000) NOT NULL
+ );
+
+ /*
+ Find lead blockers: sessions that are blocking others
+ but not blocked themselves (includes idle sessions with open transactions)
+ */
+ INSERT
+ @lead_blockers
+ (
+ session_id,
+ blocking_session_id,
+ blocking_chain
+ )
+ SELECT
+ session_id = sp.spid,
+ blocking_session_id = sp.blocked,
+ blocking_chain =
+ CONVERT
+ (
+ nvarchar(4000),
+ CONVERT(nvarchar(20), sp.spid) + N' lead blocker'
+ )
+ FROM sys.sysprocesses AS sp
+ WHERE sp.blocked = 0
+ AND EXISTS
+ (
+ SELECT
+ 1/0
+ FROM sys.sysprocesses AS sp2
+ WHERE sp2.blocked = sp.spid
+ );
+
+ /*
+ Recursive CTE to walk blocking chains
+ Anchor: lead blockers from table variable
+ Recursive: sessions blocked by current level
+ */
+ WITH
+ blockers
+ (
+ session_id,
+ blocking_session_id,
+ blocking_level,
+ top_level_blocker,
+ blocking_chain,
+ visited_path
+ ) AS
+ (
+ /*
+ Anchor: Lead blockers from table variable
+ */
+ SELECT
+ session_id = lb.session_id,
+ blocking_session_id = lb.blocking_session_id,
+ blocking_level = 0,
+ top_level_blocker = lb.session_id,
+ blocking_chain = lb.blocking_chain,
+ visited_path =
+ CONVERT
+ (
+ nvarchar(4000),
+ N'.' + CONVERT(nvarchar(20), lb.session_id) + N'.'
+ )
+ FROM @lead_blockers AS lb
+
+ UNION ALL
+
+ /*
+ Recursive: Walk down the blocking chain
+ */
+ SELECT
+ session_id = sp.spid,
+ blocking_session_id = sp.blocked,
+ blocking_level = b.blocking_level + 1,
+ top_level_blocker = b.top_level_blocker,
+ blocking_chain =
+ CONVERT
+ (
+ nvarchar(4000),
+ REPLICATE(N' > ', b.blocking_level + 1) +
+ CONVERT(nvarchar(20), sp.blocked) +
+ N' blocking ' +
+ CONVERT(nvarchar(20), sp.spid)
+ ),
+ visited_path =
+ CONVERT
+ (
+ nvarchar(4000),
+ b.visited_path + CONVERT(nvarchar(20), sp.spid) + N'.'
+ )
+ FROM blockers AS b
+ JOIN sys.sysprocesses AS sp
+ ON sp.blocked = b.session_id
+ WHERE b.visited_path NOT LIKE
+ N'%.' + CONVERT(nvarchar(20), sp.spid) + N'.%'
+ ),
+ blocking_info
+ (
+ session_id,
+ blocking_session_id,
+ blocking_level,
+ top_level_blocker,
+ blocking_chain,
+ blocked_session_count,
+ last_batch,
+ status,
+ wait_type,
+ wait_time,
+ wait_resource,
+ cpu_time,
+ physical_io,
+ memusage,
+ open_transaction_count,
+ database_name,
+ command,
+ sql_handle,
+ statement_start_offset,
+ statement_end_offset,
+ login_name,
+ host_name,
+ program_name,
+ login_time
+ ) AS
+ (
+ /*
+ Join blocking chain results to sysprocesses for session details
+ SQL text and query plans are NOT retrieved here - done in final SELECT
+ */
+ SELECT
+ b.session_id,
+ b.blocking_session_id,
+ b.blocking_level,
+ b.top_level_blocker,
+ b.blocking_chain,
+ blocked_session_count =
+ (
+ SELECT
+ COUNT_BIG(*)
+ FROM blockers AS b2
+ WHERE b2.visited_path LIKE
+ N'%.' + CONVERT(nvarchar(20), b.session_id) + N'.%'
+ AND b2.session_id <> b.session_id
+ ),
+ sp.last_batch,
+ sp.status,
+ wait_type = sp.lastwaittype,
+ wait_time = sp.waittime,
+ wait_resource = sp.waitresource,
+ cpu_time = sp.cpu,
+ physical_io = sp.physical_io,
+ sp.memusage,
+ open_transaction_count = sp.open_tran,
+ database_name = DB_NAME(sp.dbid),
+ command = sp.cmd,
+ sp.sql_handle,
+ statement_start_offset = sp.stmt_start,
+ statement_end_offset = sp.stmt_end,
+ login_name = sp.loginame,
+ host_name = sp.hostname,
+ program_name = sp.program_name,
+ sp.login_time
+ FROM blockers AS b
+ JOIN sys.sysprocesses AS sp
+ ON sp.spid = b.session_id
+ )
+ /*
+ Final SELECT: retrieve SQL text and query plans last for performance
+ Column order matches sp_WhoIsActive where possible
+ */
+ SELECT
+ [dd hh:mm:ss.mss] =
+ CASE
+ WHEN e.elapsed_time_ms < 0
+ THEN RIGHT(REPLICATE('0', 2) + CONVERT(varchar(10), (-1 * e.elapsed_time_ms) / 86400), 2) +
+ ' ' +
+ RIGHT(CONVERT(varchar(30), DATEADD(SECOND, (-1 * e.elapsed_time_ms), 0), 120), 9) +
+ '.000'
+ ELSE RIGHT(REPLICATE('0', 2) +
+ CONVERT(varchar(10), e.elapsed_time_ms / 86400000), 2) +
+ ' ' +
+ RIGHT(CONVERT(varchar(30), DATEADD(SECOND, e.elapsed_time_ms / 1000, 0), 120), 9) +
+ '.' +
+ RIGHT('000' + CONVERT(varchar(3), e.elapsed_time_ms % 1000), 3)
+ END,
+ bi.session_id,
+ blocking_session_id = NULLIF(bi.blocking_session_id, 0),
+ bi.blocking_level,
+ bi.top_level_blocker,
+ bi.blocking_chain,
+ bi.blocked_session_count,
+ query_text =
+ (
+ SELECT
+ [processing-instruction(query)] =
+ SUBSTRING
+ (
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
+ REPLACE(
+ dest.text COLLATE Latin1_General_BIN2,
+ NCHAR(31), N'?'), NCHAR(30), N'?'), NCHAR(29), N'?'), NCHAR(28), N'?'), NCHAR(27), N'?'), NCHAR(26), N'?'), NCHAR(25), N'?'), NCHAR(24), N'?'), NCHAR(23), N'?'), NCHAR(22), N'?'),
+ NCHAR(21), N'?'), NCHAR(20), N'?'), NCHAR(19), N'?'), NCHAR(18), N'?'), NCHAR(17), N'?'), NCHAR(16), N'?'), NCHAR(15), N'?'), NCHAR(14), N'?'), NCHAR(12), N'?'),
+ NCHAR(11), N'?'), NCHAR(8), N'?'), NCHAR(7), N'?'), NCHAR(6), N'?'), NCHAR(5), N'?'), NCHAR(4), N'?'), NCHAR(3), N'?'), NCHAR(2), N'?'), NCHAR(1), N'?'), NCHAR(0), N''),
+ N'', N'??'), N'?>', N'??'),
+ (bi.statement_start_offset / 2) + 1,
+ (
+ (
+ CASE bi.statement_end_offset
+ WHEN -1
+ THEN DATALENGTH(dest.text)
+ ELSE bi.statement_end_offset
+ END - bi.statement_start_offset
+ ) / 2
+ ) + 1
+ )
+ FOR XML PATH(''),
+ TYPE
+ ),
+ query_plan =
+ CASE
+ WHEN TRY_CAST(deqp.query_plan AS xml) IS NOT NULL
+ THEN TRY_CAST(deqp.query_plan AS xml)
+ WHEN TRY_CAST(deqp.query_plan AS xml) IS NULL
+ THEN
+ (
+ SELECT
+ [processing-instruction(query_plan)] =
+ N'-- ' + NCHAR(13) + NCHAR(10) +
+ N'-- This is a huge query plan.' + NCHAR(13) + NCHAR(10) +
+ N'-- Remove the headers and footers, save it as a .sqlplan file, and re-open it.' + NCHAR(13) + NCHAR(10) +
+ NCHAR(13) + NCHAR(10) +
+ REPLACE(deqp.query_plan, N' 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
diff --git a/sp_QueryReproBuilder/sp_QueryReproBuilder.sql b/sp_QueryReproBuilder/sp_QueryReproBuilder.sql
index 9fe078f0..533fe3aa 100644
--- a/sp_QueryReproBuilder/sp_QueryReproBuilder.sql
+++ b/sp_QueryReproBuilder/sp_QueryReproBuilder.sql
@@ -57,8 +57,8 @@ ALTER PROCEDURE
@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 query ids to search for*/
- @include_query_ids nvarchar(4000) = NULL, /*a list of plan ids to search 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*/
@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*/
@@ -83,8 +83,8 @@ BEGIN TRY
/*Version*/
SELECT
- @version = '1.0',
- @version_date = '20260115';
+ @version = '1.2',
+ @version_date = '20260201';
/*Help*/
IF @help = 1
@@ -190,12 +190,12 @@ DECLARE
N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' +
NCHAR(10),
@nc10 nchar(1) = NCHAR(10),
- @where_clause nvarchar(MAX) = N'',
@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)
@@ -290,6 +290,17 @@ SELECT
)
);
+/*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
(
@@ -551,28 +562,81 @@ BEGIN
N'.' +
QUOTENAME(@procedure_name);
- /*Check if procedure exists in Query Store - single procedure (no wildcards)*/
- IF CHARINDEX(N'%', @procedure_name) = 0
+ /*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
- @procedure_exists =
- CASE
- WHEN EXISTS
+ @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 ' + @database_name_quoted + N'.sys.query_store_query AS qsq
- WHERE qsq.object_id = OBJECT_ID(@procedure_name_quoted)
+ FROM #procedure_object_ids AS p
+ WHERE qsq.object_id = p.object_id
)
- THEN 1
- ELSE 0
- END
- OPTION(RECOMPILE);' + @nc10;
+ )
+ THEN 1
+ ELSE 0
+ END
+) AS x
+OPTION(RECOMPILE);' + @nc10;
IF @debug = 1
BEGIN
@@ -582,7 +646,50 @@ BEGIN
EXECUTE sys.sp_executesql
@sql,
- N'@procedure_exists bit OUTPUT, @procedure_name_quoted nvarchar(1024)',
+ 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;
@@ -643,6 +750,13 @@ CREATE TABLE
INDEX plan_id CLUSTERED (plan_id)
);
+CREATE TABLE
+ #procedure_object_ids
+(
+ object_id bigint NOT NULL,
+ INDEX object_id CLUSTERED (object_id)
+);
+
/*
Create Query Store temp tables
*/
@@ -945,14 +1059,6 @@ CREATE TABLE
parameter_compiled_value nvarchar(max) NULL
);
-CREATE TABLE
- #query_text_parameters
-(
- plan_id bigint NOT NULL,
- INDEX plan_id CLUSTERED (plan_id),
- parameter_declaration nvarchar(max) NULL
-);
-
CREATE TABLE
#reproduction_warnings
(
@@ -1354,7 +1460,7 @@ BEGIN
)
)';
- /*Add procedure filter if specified*/
+ /*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -1373,6 +1479,31 @@ BEGIN
)';
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;
@@ -1459,7 +1590,7 @@ BEGIN
)
)';
- /*Add procedure filter if specified*/
+ /*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -1478,6 +1609,31 @@ BEGIN
)';
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;
@@ -1880,7 +2036,7 @@ BEGIN
)';
END;
-/*Add procedure filter if specified*/
+/*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -1905,6 +2061,37 @@ BEGIN
)';
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
@@ -2118,7 +2305,7 @@ AND NOT EXISTS
)';
END;
-/*Add procedure filter if specified*/
+/*Add procedure filter if specified - single procedure*/
IF
(
@procedure_name IS NOT NULL
@@ -2137,6 +2324,31 @@ AND qsp.query_id IN
)';
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'
@@ -2166,7 +2378,7 @@ EXECUTE sys.sp_executesql
Populate the #query_store_query table with query metadata
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query';
SELECT
@@ -2224,7 +2436,7 @@ EXECUTE sys.sp_executesql
Populate the #query_store_query_text table with query text
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_text';
SELECT
@@ -2290,7 +2502,7 @@ OPTION(RECOMPILE);
Populate the #query_context_settings table with context settings
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_context_settings';
SELECT
@@ -2449,12 +2661,14 @@ OPTION(RECOMPILE);
/*
Populate the #query_store_wait_stats table with wait statistics (SQL 2017+)
*/
-SELECT
- @sql = N'',
- @current_table = N'inserting #query_store_wait_stats';
+IF @sql_2017 = 1
+BEGIN
+ SELECT
+ @sql = @isolation_level,
+ @current_table = N'inserting #query_store_wait_stats';
-SELECT
- @sql += N'
+ SELECT
+ @sql += N'
SELECT
@database_id,
qsws_with_lasts.plan_id,
@@ -2506,30 +2720,31 @@ HAVING
SUM(qsws_with_lasts.min_query_wait_time_ms) > 0.
OPTION(RECOMPILE);' + @nc10;
-IF @debug = 1
-BEGIN
- PRINT LEN(@sql);
- PRINT @sql;
-END;
+ 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;
+ 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
@@ -2540,7 +2755,7 @@ BEGIN
Populate the #query_store_plan_feedback table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_plan_feedback';
SELECT
@@ -2593,7 +2808,7 @@ OPTION(RECOMPILE);' + @nc10;
Populate the #query_store_query_variant table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_variant';
SELECT
@@ -2638,7 +2853,7 @@ OPTION(RECOMPILE);' + @nc10;
Populate the #query_store_query_hints table
*/
SELECT
- @sql = N'',
+ @sql = @isolation_level,
@current_table = N'inserting #query_store_query_hints';
SELECT
diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql
index fb96bae7..3e4909e1 100644
--- a/sp_QuickieStore/sp_QuickieStore.sql
+++ b/sp_QuickieStore/sp_QuickieStore.sql
@@ -98,7 +98,7 @@ ALTER PROCEDURE
@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, want do you want the results sorted by ('magnitude', 'improved', or 'regressed')?*/
+ @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.*/
@@ -120,8 +120,8 @@ BEGIN TRY
These are for your outputs.
*/
SELECT
- @version = '6.0',
- @version_date = '20260115';
+ @version = '6.2',
+ @version_date = '20260201';
/*
Helpful section! For help.
@@ -2779,8 +2779,14 @@ SELECT
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 =
@@ -3220,7 +3226,7 @@ OPTION(RECOMPILE);' + @nc10;
IF ROWCOUNT_BIG() = 0
BEGIN
- RAISERROR('No object_ids were found for %s in schema %s', 11, 1, @procedure_schema, @procedure_name) WITH NOWAIT;
+ RAISERROR('No object_ids were found for %s in schema %s', 11, 1, @procedure_name, @procedure_schema) WITH NOWAIT;
RETURN;
END;
@@ -4187,7 +4193,7 @@ BEGIN
PRINT @dynamic_sql;
END;
- EXEC sys.sp_executesql
+ EXECUTE sys.sp_executesql
@dynamic_sql,
N'@split_sql nvarchar(max),
@param_value nvarchar(4000)',
@@ -5513,7 +5519,7 @@ BEGIN
SELECT
qsq.query_hash,
plan_hash_count_for_query_hash =
- COUNT(DISTINCT qsp.query_plan_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
@@ -7170,7 +7176,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -7279,7 +7285,7 @@ BEGIN
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -7373,7 +7379,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -7743,7 +7749,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -7878,7 +7884,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -7969,7 +7975,7 @@ WITH
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8146,7 +8152,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8216,7 +8222,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8292,7 +8298,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8367,7 +8373,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8437,7 +8443,7 @@ OPTION(RECOMPILE);' + @nc10;
)
EXECUTE sys.sp_executesql
@sql,
- N'@database_id int',
+ N'@database_id integer',
@database_id;
IF @troubleshoot_performance = 1
@@ -8869,34 +8875,34 @@ ORDER BY
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 money) ASC,
+ 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 money) DESC,
+ 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 money)) DESC,
+ 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 money)'
- WHEN 'logical reads' THEN N'TRY_PARSE(x.avg_logical_io_reads_mb AS money)'
- WHEN 'physical reads' THEN N'TRY_PARSE(x.avg_physical_io_reads_mb AS money)'
- WHEN 'writes' THEN N'TRY_PARSE(x.avg_logical_io_writes_mb AS money)'
- WHEN 'duration' THEN N'TRY_PARSE(x.avg_duration_ms AS money)'
- WHEN 'memory' THEN N'TRY_PARSE(x.avg_query_max_used_memory_mb AS money)'
- WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'TRY_PARSE(x.avg_tempdb_space_used_mb AS money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END
- WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)'
+ 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 money)'
- WHEN 'plan count by hashes' THEN N'TRY_PARSE(x.plan_hash_count_for_query_hash AS money) DESC,
+ WHEN 'rows' THEN N'TRY_PARSE(x.avg_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 money)' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END
+ 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
@@ -9432,12 +9438,11 @@ BEGIN
IF @rc > 0
BEGIN
SELECT
- @current_table = 'selecting resource stats';
-
- SET @sql = N'';
+ @current_table = 'selecting resource stats',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -9658,12 +9663,11 @@ BEGIN
Wait stats by query
*/
SELECT
- @current_table = 'selecting wait stats by query';
-
- SET @sql = N'';
+ @current_table = 'selecting wait stats by query',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -9798,12 +9802,11 @@ BEGIN
Wait stats in total
*/
SELECT
- @current_table = 'selecting wait stats in total';
-
- SET @sql = N'';
+ @current_table = 'selecting wait stats in total',
+ @sql = @isolation_level;
SELECT
- @sql =
+ @sql +=
CONVERT
(
nvarchar(max),
@@ -9978,7 +9981,7 @@ BEGIN
BEGIN
SELECT
@current_table = 'selecting query store options',
- @sql = N'';
+ @sql = @isolation_level;
SELECT
@sql +=
@@ -10351,7 +10354,7 @@ BEGIN
@only_queries_with_hints,
only_query_with_feedback =
@only_queries_with_feedback,
- only_query_with_hints =
+ only_query_with_variants =
@only_queries_with_variants,
only_queries_with_forced_plans =
@only_queries_with_forced_plans,
diff --git a/todo.md b/todo.md
new file mode 100644
index 00000000..25b8f71a
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,233 @@
+# DarlingData Code Review TODO
+
+## sp_HealthParser.sql
+
+### High Priority (Bugs)
+
+- [x] **Memory Grant Conversion Error (Lines 5345-5352)** - FIXED. Removed `* 8` from KB columns.
+
+- [x] **@wait_duration_ms ISNULL Mismatch (Line 453)** - FIXED. Changed to `ISNULL(@wait_duration_ms, 500)`.
+
+- [x] **Deadlock Database Filter Logic** - NOT A BUG. Upstream validation added earlier handles invalid database names.
+
+### Low Priority (Code Quality)
+
+- [ ] **Unused Variables (Lines 221, 233)** - `@azure_msg` and `@mi_msg` declared and assigned but never used. SKIPPED - may be for future use.
+
+- [ ] **Unused Columns in #scheduler_details (Lines 2717-2718)** - `name` and `component` columns extracted from XML but never referenced. SKIPPED - useful for debugging.
+
+- [ ] **Commented-Out Dead Code (Lines 4454-4526)** - ~70 lines of `#useless` table code commented out. SKIPPED - intentionally preserved for future use.
+
+- [x] **datetime vs datetime2 inconsistency (Lines 5097-5099)** - FIXED. Changed to `datetime2` for consistency.
+
+---
+
+## sp_HumanEvents.sql
+
+### High Priority (Bugs)
+
+- [x] **Azure Stopped Sessions Restart Logic Bug (Lines 3578-3588)** - FIXED. Changed to `LEFT JOIN` and added `AND dxs.create_time IS NULL`.
+
+- [x] **Cleanup Section Missing Azure Support (Lines 4818-4828)** - FIXED. Added `IF @azure = 0 / ELSE` branch for Azure support.
+
+- [x] **Division by Zero in Query Stats (Lines 3363, 3367)** - FIXED. Added `NULLIF(deqs.execution_count, 0)`.
+
+### Low Priority
+
+- [x] **datetime vs datetime2 inconsistencies (Lines 398-399, 518, 4707)** - FIXED. Changed to `datetime2(7)`. Line 79 (@version_date OUTPUT) left as datetime for backwards compatibility.
+
+- [x] **Abbreviated data type INT (Line 4800)** - FIXED. Changed to `integer`.
+
+---
+
+## sp_HumanEventsBlockViewer.sql
+
+### High Priority (Bugs)
+
+- [x] **Azure Event File - Wrong DMV and Type Mismatch (Lines 1079-1111)** - FIXED. Changed to use `sys.database_event_session_targets`, `sys.database_event_sessions`, and `sys.database_event_session_fields` (catalog views with integer IDs).
+
+- [x] **System Health - Column Name Mismatch (Lines 1318-1319 vs 1375-1376)** - FIXED. Changed `#blocked_sh` to use `last_transaction_started`/`last_transaction_completed` for consistency.
+
+### Low Priority
+
+- [ ] **datetime vs datetime2 (Line 85)** - `@version_date` OUTPUT parameter uses datetime. SKIPPED - backwards compatibility.
+
+- [x] **Inconsistent Date Literal Formats (Lines 1688, 2734)** - FIXED. Changed to `'19000101'`.
+
+- [x] **Redundant Nested BEGIN/END (Lines 1861-1867)** - FIXED. Removed unnecessary inner BEGIN/END.
+
+---
+
+## sp_IndexCleanup.sql
+
+### Fixed Earlier (needs re-review like other procedures)
+
+- [x] **Help @debug default (Line 192)** - FIXED. Changed from 'true' to 'false'.
+
+- [x] **Missing space in CREATE INDEX (Line 4625)** - FIXED. Added space.
+
+- [x] **@@ROWCOUNT usage (Lines 3185, 3318)** - FIXED. Changed to ROWCOUNT_BIG().
+
+- [x] **@min_rows type mismatch (Line 2312)** - FIXED. Changed from integer to bigint.
+
+- [x] **#requested_but_skipped_databases logic order** - FIXED. Moved to after #databases is populated.
+
+### New Issues Found (Multi-Agent Review)
+
+#### High Priority (Bugs)
+
+- [x] **Missing ELSE Clause in CASE Expression (Lines 2179-2189)** - NOT A BUG. WHERE clause at line 2212 filters to `i.type IN (1, 2)`, so CASE always matches.
+
+- [x] **Missing index_id in JOIN Condition (Lines 1434-1436)** - FIXED. Removed dead LEFT JOIN to `dm_db_index_usage_stats` entirely - no columns were selected from it. Left comment for future reference.
+
+- [x] **@supports_optimize_for_sequential_key Missing Standard Edition (Lines 294-318)** - FIXED. Changed `= 3` to `IN (2, 3)` to include Standard Edition.
+
+#### Medium Priority
+
+- [x] **Help Section Default Value Inconsistencies (Lines 60,61 vs 187,188)** - FIXED. Changed help section to show `'false'` for `@dedupe_only` and `@get_all_databases` to match declarations.
+
+#### Low Priority (Code Quality)
+
+- [x] **datetime vs datetime2 (Lines 67, 598-601)** - NOT AN ISSUE. The temp table columns match the source DMV `sys.dm_db_index_usage_stats` which uses `datetime`. `@version_date` OUTPUT parameter stays `datetime` for backwards compatibility.
+
+- [x] **ROW_NUMBER Deduplication Ordering (Lines 6029-6052)** - FIXED. Removed stale comment about "non-NULL result types" - column is NOT NULL so comment was misleading.
+
+---
+
+## sp_LogHunter.sql
+
+### High Priority (Bugs)
+
+- [x] **Date Filtering May Delete Current Log (Lines 364-374)** - NOT A BUG. Intentional - explicit date ranges should filter the current log too since it can contain weeks/months of data.
+
+- [x] **@days_back NULL in DATEADD (Lines 465, 493)** - NOT A BUG. The ISNULL at line 323 handles this - when start_date is provided, it's used; otherwise falls back to days_back. The NULL days_back is intentional when explicit dates are given.
+
+- [x] **@t_searches Type Mismatch (Lines 272, 510)** - FIXED. Changed `@t_searches` from `integer` to `bigint`.
+
+### Medium Priority
+
+- [x] **Typo in Help Section (Line 111)** - FIXED. "substitions" → "substitutions".
+
+### Low Priority (Code Quality)
+
+- [x] **datetime vs datetime2 (Lines 55, 56, 64, 286)** - NOT AN ISSUE. xp_readerrorlog returns datetime, so keeping datetime for consistency.
+
+- [x] **Redundant @stopper and BREAK (Lines 644-646)** - FIXED. Removed @stopper variable entirely - it was only ever set right before BREAK, making it pointless.
+
+- [x] **Confusing CTE Alias (Lines 708-711)** - SKIPPED. Not worth changing.
+
+---
+
+## sp_PerfCheck.sql
+
+### High Priority (Bugs)
+
+- [x] **@processors Never Initialized (Line 257)** - FIXED. Added `SELECT @processors = osi.cpu_count FROM sys.dm_os_sys_info` after line 845.
+
+- [x] **@physical_memory_gb Used Before Assignment (Line 1145 vs 2950)** - FIXED. Added assignment from sys.dm_os_sys_info before the LPIM check.
+
+- [x] **Operator Precedence Bug in Trace Events (Lines 1490-1512)** - FIXED. Wrapped event conditions in parentheses so date filter applies to all events.
+
+- [x] **Wrong Variable in Debug Print (Line 2906)** - FIXED. Changed `PRINT @file_io_sql` to `PRINT @db_size_sql`.
+
+- [x] **Duplicate check_id 5001 (Lines 1420, 1538)** - FIXED. Changed "Default Trace Permissions" to check_id 5000.
+
+### Medium Priority
+
+- [x] **@debug Default Mismatch in Help (Lines 52, 121)** - FIXED. Changed help section from 'true' to 'false'.
+
+- [x] **@database_name valid_inputs Copy-Paste Error (Line 109)** - FIXED. Changed "indexes in" to "check".
+
+### Low Priority (Code Quality)
+
+- [x] **datetime vs datetime2 (Lines 54, 522, 541)** - NOT AN ISSUE. Source DMVs (sys.databases.create_date, sys.fn_trace_gettable.StartTime) use datetime. @version_date OUTPUT parameter stays datetime for backwards compatibility.
+
+---
+
+## sp_PressureDetector.sql
+
+### High Priority (Bugs)
+
+- [x] **Missing Comma in Table Definition (Lines 847-848)** - FIXED. Added comma after `live_query_plan xml NULL` before PRIMARY KEY.
+
+- [x] **Duplicated sp_executesql Call (Lines 907-923)** - FIXED BY USER. Removed duplicate execution block.
+
+- [x] **Duplicate CASE Expression for HTREINIT (Lines 1272-1275)** - FIXED. Removed duplicate WHEN clause. Verified all HT* wait types are covered.
+
+- [x] **Wrong Parameter for sys.dm_exec_query_statistics_xml (Lines 3210, 3914)** - FIXED. Changed from plan_handle to session_id (function takes session_id per MS docs).
+
+- [x] **Missing ISNULL for Some Parameters (Lines 228-237)** - FIXED. Added @skip_perfmon, @log_to_table, @troubleshoot_blocking to ISNULL block.
+
+- [x] **Division by Zero in Perfmon Counters (Lines 2105-2115, 2156-2157)** - FIXED. Wrapped DATEDIFF with ISNULL(..., 1) to avoid divide by zero.
+
+### Low Priority (Code Quality)
+
+- [x] **@@ROWCOUNT instead of ROWCOUNT_BIG() (Line 3695)** - FIXED. Changed to ROWCOUNT_BIG().
+
+- [x] **Missing AS keyword on table alias (Line 2605)** - FIXED. Changed to `FROM sys.dm_os_sys_info AS osi`.
+
+### Not Issues
+
+- datetime types are justified (match source DMVs like sys.dm_exec_query_memory_grants)
+- Parameter/help section defaults all match correctly
+- No abbreviated data types, no COUNT() issues
+
+---
+
+## sp_QueryReproBuilder.sql
+
+### High Priority (Bugs)
+
+- [x] **Query Store Wait Stats Version Check Missing (Lines 2452-2532)** - FIXED. Added @sql_2017 variable (set for version >= 14 or Azure) and wrapped wait stats section with IF @sql_2017 = 1.
+
+- [x] **Wildcard Procedure Filtering Broken (Lines 554-597)** - FIXED. Ported wildcard handling from sp_QuickieStore: added #procedure_object_ids table, wildcard procedure existence check, and wildcard filter branches in all 4 procedure filter sections.
+
+- [x] **Missing @isolation_level in Dynamic SQL (Lines 2168, 2226, 2292, 2452, 2542, 2595, 2640)** - FIXED. Changed all 7 occurrences from `@sql = N''` to `@sql = @isolation_level`.
+
+### Low Priority (Code Quality)
+
+- [x] **Swapped Parameter Comments (Lines 60-61)** - FIXED. Swapped comments so @include_plan_ids says "plan ids" and @include_query_ids says "query ids".
+
+- [x] **Unused Variable @where_clause (Line 193)** - FIXED. Removed unused variable.
+
+- [x] **Unused Temp Table #query_text_parameters (Lines 948-954)** - FIXED. Removed unused temp table.
+
+### Not Issues
+
+- datetime on @version_date (Line 71) - backwards compatibility
+- No @@ROWCOUNT, no COUNT(), no abbreviated types
+
+---
+
+## sp_QuickieStore.sql
+
+### High Priority (Bugs)
+
+- [x] **RAISERROR Parameter Order Reversed (Line 3223)** - FIXED. Swapped to `@procedure_name, @procedure_schema`.
+
+- [x] **Debug Output Duplicate Column Name (Line 10354)** - FIXED. Changed to `only_query_with_variants`.
+
+- [x] **Missing @isolation_level in Expert Mode Dynamic SQL (Lines 9435, 9660, 9799, 9977)** - FIXED. Changed all four to use `@sql = @isolation_level`.
+
+### Medium Priority
+
+- [x] **Typo in @regression_direction Comment (Line 101)** - FIXED. "want do you want" → "what do you want".
+
+- [x] **Missing ISNULL Handling for Parameters** - FIXED. Added `@escape_brackets`, `@query_type`, `@execution_type_desc` to ISNULL/NULLIF block.
+
+- [x] **COUNT() instead of COUNT_BIG() (Line 5522)** - FIXED. Changed to COUNT_BIG().
+
+- [x] **Abbreviated Data Type 'int' in Dynamic SQL (12 occurrences)** - FIXED. Changed all `N'@database_id int'` to `N'@database_id integer'`.
+
+### Low Priority (Code Quality)
+
+- [ ] **Missing ELSE Branch in @wait_filter CASE (Lines 5116-5132)** - SKIPPED. Validation exists earlier, defensive coding not needed.
+
+- [ ] **datetime Instead of datetime2 in #troubleshoot_performance (Lines 1463-1464)** - SKIPPED. Internal temp table, no external impact.
+
+- [x] **EXEC Instead of EXECUTE (Line 4196)** - FIXED. Changed to EXECUTE.
+
+### Not Issues
+
+- datetime on @version_date (Line 108) - backwards compatibility with OUTPUT parameter
+- Azure SQL MI @ags_present logic - appears intentional, Azure SQL MI AG support is complex