From e9f12d474449b3fbfe5fdfed5dfa413897e2cd36 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 20:20:03 +0100 Subject: [PATCH 01/24] First attempt at adding the ability to sort by the number of plan hashes per query hash --- sp_QuickieStore/sp_QuickieStore.sql | 218 ++++++++++++++++++++++++---- 1 file changed, 188 insertions(+), 30 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 6f35aad..7cabc76 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -212,7 +212,7 @@ BEGIN CASE ap.name WHEN N'@database_name' THEN 'a database name with query store enabled' - WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent' + WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan hashes' WHEN N'@top' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807' WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999' WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999' @@ -496,6 +496,20 @@ CREATE TABLE ) PERSISTED NOT NULL PRIMARY KEY ); +IF @sort_order = 'plan hashes' +BEGIN + /* + Holds plan_id with the count of the number of query hashes they have. + Only uses for when we're sorting by plan hashes per query has. + */ + CREATE TABLE + #plan_ids_with_query_hashes + ( + plan_id bigint PRIMARY KEY WITH (IGNORE_DUP_KEY = ON), + PlanHashesPerQueryHash INT NOT NULL + ); +END; + /* Hold plan hashes for plans we want */ @@ -2148,7 +2162,8 @@ IF @sort_order NOT IN 'memory', 'tempdb', 'executions', - 'recent' + 'recent', + 'plan hashes' ) BEGIN RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; @@ -4356,6 +4371,108 @@ OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; )' + @nc10; END; +/* +Populate sort-helping tables, if needed +*/ +IF @sort_order = 'plan hashes' +BEGIN + SELECT + @current_table = 'inserting #plan_ids_with_query_hashes', + @sql = @isolation_level; + + IF @troubleshoot_performance = 1 + BEGIN + EXEC sys.sp_executesql + @troubleshoot_insert, + N'@current_table nvarchar(100)', + @current_table; + + SET STATISTICS XML ON; + END; + + SELECT + @sql += N' + SELECT TOP (@top) + QueryHashesWithIds.plan_id, + PlanHashesPerQueryHash + FROM + ( + SELECT + qsq.query_hash, + COUNT(DISTINCT qsp.query_plan_hash) AS PlanHashesPerQueryHash + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + GROUP + BY qsrs.plan_id + ) AS QueryHashesWithCounts + JOIN + ( + SELECT + qsq.query_hash, + qsp.plan_id + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + ) AS QueryHashesWithIds + ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash + ORDER BY + QueryHashesWithCounts.PlanHashesPerQueryHash DESC + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; + + IF @debug = 1 + BEGIN + PRINT LEN(@sql); + PRINT @sql; + END; + + INSERT + #plan_ids_with_query_hashes WITH(TABLOCK) + ( + plan_id, + PlanHashesPerQueryHash + ) + EXEC sys.sp_executesql + @sql, + @parameters, + @top, + @start_date, + @end_date, + @execution_count, + @duration_ms, + @execution_type_desc, + @database_id, + @queries_top, + @work_start_utc, + @work_end_utc; + + IF @troubleshoot_performance = 1 + BEGIN + SET STATISTICS XML OFF; + + EXEC sys.sp_executesql + @troubleshoot_update, + N'@current_table nvarchar(100)', + @current_table; + + EXEC sys.sp_executesql + @troubleshoot_info, + N'@sql nvarchar(max), + @current_table nvarchar(100)', + @sql, + @current_table; + END; +END; /*End populating sort-helping tables*/ /* This section screens out index create and alter statements because who cares @@ -4468,32 +4585,46 @@ BEGIN SET STATISTICS XML ON; END; -SELECT - @sql += N' -SELECT TOP (@top) - qsrs.plan_id -FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs -WHERE 1 = 1 -' + @where_clause - + N' -GROUP - BY qsrs.plan_id -ORDER BY - MAX(' + -CASE @sort_order - WHEN 'cpu' THEN N'qsrs.avg_cpu_time' - WHEN 'logical reads' THEN N'qsrs.avg_logical_io_reads' - WHEN 'physical reads' THEN N'qsrs.avg_physical_io_reads' - WHEN 'writes' THEN N'qsrs.avg_logical_io_writes' - WHEN 'duration' THEN N'qsrs.avg_duration' - WHEN 'memory' THEN N'qsrs.avg_query_max_used_memory' - WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END - WHEN 'executions' THEN N'qsrs.count_executions' - WHEN 'recent' THEN N'qsrs.last_execution_time' - ELSE N'qsrs.avg_cpu_time' -END + -N') DESC -OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; +IF @sort_order = 'plan hashes' +BEGIN + SELECT + @sql += N' + SELECT TOP (@top) + hashes.plan_id + FROM #plan_ids_with_query_hashes AS hashes + ORDER BY + hashes.PlanHashesPerQueryHash DESC + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; +END +ELSE +BEGIN + SELECT + @sql += N' + SELECT TOP (@top) + qsrs.plan_id + FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + WHERE 1 = 1 + ' + @where_clause + + N' + GROUP + BY qsrs.plan_id + ORDER BY + MAX(' + + CASE @sort_order + WHEN 'cpu' THEN N'qsrs.avg_cpu_time' + WHEN 'logical reads' THEN N'qsrs.avg_logical_io_reads' + WHEN 'physical reads' THEN N'qsrs.avg_physical_io_reads' + WHEN 'writes' THEN N'qsrs.avg_logical_io_writes' + WHEN 'duration' THEN N'qsrs.avg_duration' + WHEN 'memory' THEN N'qsrs.avg_query_max_used_memory' + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END + WHEN 'executions' THEN N'qsrs.count_executions' + WHEN 'recent' THEN N'qsrs.last_execution_time' + ELSE N'qsrs.avg_cpu_time' + END + + N') DESC + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; +END; IF @debug = 1 BEGIN @@ -4645,7 +4776,17 @@ CROSS APPLY ( SELECT TOP (@queries_top) qsrs.* - FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs' + IF @sort_order = 'plan hashes' + BEGIN + SELECT + @sql += N' + JOIN #plan_ids_with_query_hashes AS hashes + ON qsrs.plan_id = hashes.plan_id' + END; + +SELECT + @sql += N' WHERE qsrs.plan_id = dp.plan_id AND 1 = 1 ' + @where_clause @@ -4661,6 +4802,7 @@ CASE @sort_order WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'qsrs.avg_cpu_time' END + N' DESC ) AS qsrs @@ -6350,6 +6492,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -6579,6 +6722,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -6782,6 +6926,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -6986,6 +7131,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -7002,7 +7148,17 @@ FROM ( nvarchar(MAX), N' - FROM #query_store_runtime_stats AS qsrs + FROM #query_store_runtime_stats AS qsrs' + IF @sort_order = 'plan hashes' + BEGIN + SELECT + @sql += N' + JOIN #plan_ids_with_query_hashes AS hashes + ON qsrs.plan_id = hashes.plan_id' + END; + +SELECT + @sql += N' CROSS APPLY ( SELECT @@ -7180,6 +7336,7 @@ ORDER BY ' + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time' END WHEN 'executions' THEN N'x.count_executions' WHEN 'recent' THEN N'x.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'x.avg_cpu_time_ms' END WHEN 1 @@ -7194,6 +7351,7 @@ ORDER BY ' + 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 AS money)' END WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' + WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END END From 9710fe93b32c0c54dbc0e8a559830e16a4f3e67f Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 20:34:24 +0100 Subject: [PATCH 02/24] Removed some syntax errors. --- sp_QuickieStore/sp_QuickieStore.sql | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 7cabc76..9dabd80 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4409,7 +4409,7 @@ BEGIN ' + @where_clause + N' GROUP - BY qsrs.plan_id + BY qsq.query_hash ) AS QueryHashesWithCounts JOIN ( @@ -7148,7 +7148,8 @@ FROM ( nvarchar(MAX), N' - FROM #query_store_runtime_stats AS qsrs' + FROM #query_store_runtime_stats AS qsrs' + ) IF @sort_order = 'plan hashes' BEGIN SELECT @@ -7158,7 +7159,11 @@ FROM END; SELECT - @sql += N' + @sql += + CONVERT + ( + NVARCHAR(MAX), + N' CROSS APPLY ( SELECT From 7d7c6b3832aaa00230d7913346519a74334800b4 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:24:20 +0100 Subject: [PATCH 03/24] Fixed scoping of sorting column. --- sp_QuickieStore/sp_QuickieStore.sql | 31 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 9dabd80..72084c9 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -506,7 +506,7 @@ BEGIN #plan_ids_with_query_hashes ( plan_id bigint PRIMARY KEY WITH (IGNORE_DUP_KEY = ON), - PlanHashesPerQueryHash INT NOT NULL + plan_hash_count_for_query_hash INT NOT NULL ); END; @@ -4394,12 +4394,12 @@ BEGIN @sql += N' SELECT TOP (@top) QueryHashesWithIds.plan_id, - PlanHashesPerQueryHash + plan_hash_count_for_query_hash FROM ( SELECT qsq.query_hash, - COUNT(DISTINCT qsp.query_plan_hash) AS PlanHashesPerQueryHash + COUNT(DISTINCT qsp.query_plan_hash) AS plan_hash_count_for_query_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 @@ -4427,7 +4427,7 @@ BEGIN ) AS QueryHashesWithIds ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash ORDER BY - QueryHashesWithCounts.PlanHashesPerQueryHash DESC + QueryHashesWithCounts.plan_hash_count_for_query_hash DESC OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; IF @debug = 1 @@ -4440,7 +4440,7 @@ BEGIN #plan_ids_with_query_hashes WITH(TABLOCK) ( plan_id, - PlanHashesPerQueryHash + plan_hash_count_for_query_hash ) EXEC sys.sp_executesql @sql, @@ -4593,7 +4593,7 @@ BEGIN hashes.plan_id FROM #plan_ids_with_query_hashes AS hashes ORDER BY - hashes.PlanHashesPerQueryHash DESC + hashes.plan_hash_count_for_query_hash DESC OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; END ELSE @@ -4802,7 +4802,7 @@ CASE @sort_order WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' ELSE N'qsrs.avg_cpu_time' END + N' DESC ) AS qsrs @@ -6492,7 +6492,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -6722,7 +6722,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -6926,7 +6926,7 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' @@ -7131,10 +7131,15 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' + + CASE WHEN @sort_order = 'plan hashes' + THEN N' + , hashes.plan_hash_count_for_query_hash' + ELSE N'' + END ) ); END; /*End expert mode = 0, format output = 1*/ @@ -7341,7 +7346,7 @@ ORDER BY ' + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time' END WHEN 'executions' THEN N'x.count_executions' WHEN 'recent' THEN N'x.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'x.plan_hash_count_for_query_hash' ELSE N'x.avg_cpu_time_ms' END WHEN 1 @@ -7356,7 +7361,7 @@ ORDER BY ' + 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 AS money)' END WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.PlanHashesPerQueryHash' + WHEN 'plan hashes' THEN N'x.plan_hash_count_for_query_hash' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END END From 5f39baaa9a890ce911f8ed9b54b8fcc33de816ed Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:27:59 +0100 Subject: [PATCH 04/24] Adding sorting column to all relevant outputs. --- sp_QuickieStore/sp_QuickieStore.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 72084c9..78b792b 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -6496,6 +6496,11 @@ FROM ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' + + CASE WHEN @sort_order = 'plan hashes' + THEN N' + , hashes.plan_hash_count_for_query_hash' + ELSE N'' + END ) ); END; /*End expert mode 1, format output 0 columns*/ @@ -6726,6 +6731,11 @@ FROM ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' + + CASE WHEN @sort_order = 'plan hashes' + THEN N' + , hashes.plan_hash_count_for_query_hash' + ELSE N'' + END ) ); END; /*End expert mode = 1, format output = 1*/ @@ -6930,6 +6940,11 @@ FROM ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' + + CASE WHEN @sort_order = 'plan hashes' + THEN N' + , hashes.plan_hash_count_for_query_hash' + ELSE N'' + END ) ); END; /*End expert mode = 0, format output = 0*/ From 007db70ff6e3ef177bd8dce3770a6b378c30c3b7 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:42:24 +0100 Subject: [PATCH 05/24] Added #plan_ids_with_query_hashes to debug output. --- sp_QuickieStore/sp_QuickieStore.sql | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 78b792b..fb3fc07 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -9127,6 +9127,29 @@ BEGIN '#include_query_hashes is empty'; END; + IF EXISTS + ( + SELECT + 1/0 + FROM #plan_ids_with_query_hashes AS hashes + ) + BEGIN + SELECT + table_name = + '#plan_ids_with_query_hashes', + hashes.* + FROM #plan_ids_with_query_hashes AS hashes + ORDER BY + hashes.plan_id + OPTION(RECOMPILE); + END; + ELSE + BEGIN + SELECT + result = + '#plan_ids_with_query_hashes is empty'; + END; + IF EXISTS ( SELECT From f81fabe3552dc5795f343db00d7cf164321f3e80 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 7 Jul 2024 23:09:19 +0100 Subject: [PATCH 06/24] Fixed QueryHashesWithCounts Join --- sp_QuickieStore/sp_QuickieStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index fb3fc07..ad292eb 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4413,7 +4413,7 @@ BEGIN ) AS QueryHashesWithCounts JOIN ( - SELECT + SELECT DISTINCT qsq.query_hash, qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq From 38e56860743b09b97f88b488cbdfb6fb0a45c49f Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:48:14 +0100 Subject: [PATCH 07/24] Renamed the new sort order, made debug output work when the new order is not used, and broke ties in new sort order. --- sp_QuickieStore/sp_QuickieStore.sql | 138 +++++++++++++++++++--------- 1 file changed, 95 insertions(+), 43 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index ad292eb..b65ab5b 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -212,7 +212,7 @@ BEGIN CASE ap.name WHEN N'@database_name' THEN 'a database name with query store enabled' - WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan hashes' + WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes' WHEN N'@top' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807' WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999' WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999' @@ -496,19 +496,22 @@ CREATE TABLE ) PERSISTED NOT NULL PRIMARY KEY ); -IF @sort_order = 'plan hashes' -BEGIN - /* - Holds plan_id with the count of the number of query hashes they have. - Only uses for when we're sorting by plan hashes per query has. - */ - CREATE TABLE - #plan_ids_with_query_hashes - ( - plan_id bigint PRIMARY KEY WITH (IGNORE_DUP_KEY = ON), - plan_hash_count_for_query_hash INT NOT NULL - ); -END; + +/* +Holds plan_id with the count of the number of query hashes they have. +Only used when we're sorting by how many plan hashes each +query hash has. + +We still have to declare this table even when it's not used, +because the debug output breaks if we don't. +*/ +CREATE TABLE + #plan_ids_with_query_hashes +( + plan_id bigint, + query_hash binary(8), + plan_hash_count_for_query_hash INT NOT NULL +); /* Hold plan hashes for plans we want @@ -2163,7 +2166,7 @@ IF @sort_order NOT IN 'tempdb', 'executions', 'recent', - 'plan hashes' + 'plan count by hashes' ) BEGIN RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; @@ -4374,7 +4377,7 @@ END; /* Populate sort-helping tables, if needed */ -IF @sort_order = 'plan hashes' +IF @sort_order = 'plan count by hashes' BEGIN SELECT @current_table = 'inserting #plan_ids_with_query_hashes', @@ -4393,8 +4396,9 @@ BEGIN SELECT @sql += N' SELECT TOP (@top) - QueryHashesWithIds.plan_id, - plan_hash_count_for_query_hash + QueryHashesWithIds.plan_id, + QueryHashesWithCounts.query_hash, + QueryHashesWithCounts.plan_hash_count_for_query_hash FROM ( SELECT @@ -4413,7 +4417,7 @@ BEGIN ) AS QueryHashesWithCounts JOIN ( - SELECT DISTINCT + SELECT qsq.query_hash, qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq @@ -4427,7 +4431,7 @@ BEGIN ) AS QueryHashesWithIds ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash ORDER BY - QueryHashesWithCounts.plan_hash_count_for_query_hash DESC + QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; IF @debug = 1 @@ -4440,6 +4444,7 @@ BEGIN #plan_ids_with_query_hashes WITH(TABLOCK) ( plan_id, + query_hash, plan_hash_count_for_query_hash ) EXEC sys.sp_executesql @@ -4585,15 +4590,30 @@ BEGIN SET STATISTICS XML ON; END; -IF @sort_order = 'plan hashes' +IF @sort_order = 'plan count by hashes' BEGIN SELECT @sql += N' - SELECT TOP (@top) - hashes.plan_id - FROM #plan_ids_with_query_hashes AS hashes - ORDER BY - hashes.plan_hash_count_for_query_hash DESC + SELECT + qsrs.plan_id + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + AND qsq.query_hash IN + ( + SELECT TOP (@top) + hashes.query_hash + FROM #plan_ids_with_query_hashes AS hashes + ORDER BY + hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash DESC + ) + GROUP + BY qsrs.plan_id OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; END ELSE @@ -4777,7 +4797,7 @@ CROSS APPLY SELECT TOP (@queries_top) qsrs.* FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs' - IF @sort_order = 'plan hashes' + IF @sort_order = 'plan count by hashes' BEGIN SELECT @sql += N' @@ -4802,7 +4822,7 @@ CASE @sort_order WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' ELSE N'qsrs.avg_cpu_time' END + N' DESC ) AS qsrs @@ -6492,13 +6512,21 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' - + CASE WHEN @sort_order = 'plan hashes' + /* + Bolt the extra columns on, because we need them to be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having this column visible in the output a bad thing? + Probably not. + */ + + CASE WHEN @sort_order = 'plan count by hashes' THEN N' - , hashes.plan_hash_count_for_query_hash' + , hashes.plan_hash_count_for_query_hash, hashes.query_hash' ELSE N'' END ) @@ -6727,13 +6755,21 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' - + CASE WHEN @sort_order = 'plan hashes' + /* + Bolt the extra columns on, because we need them to be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having this column visible in the output a bad thing? + Probably not. + */ + + CASE WHEN @sort_order = 'plan count by hashes' THEN N' - , hashes.plan_hash_count_for_query_hash' + , hashes.plan_hash_count_for_query_hash, hashes.query_hash' ELSE N'' END ) @@ -6936,13 +6972,21 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' - + CASE WHEN @sort_order = 'plan hashes' + /* + Bolt the extra columns on, because we need them to be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having this column visible in the output a bad thing? + Probably not. + */ + + CASE WHEN @sort_order = 'plan count by hashes' THEN N' - , hashes.plan_hash_count_for_query_hash' + , hashes.plan_hash_count_for_query_hash, hashes.query_hash' ELSE N'' END ) @@ -7146,13 +7190,21 @@ FROM WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'qsrs.avg_tempdb_space_used_mb' ELSE N'qsrs.avg_cpu_time' END WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' - WHEN 'plan hashes' THEN N'hashes.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' ELSE N'qsrs.avg_cpu_time_ms' END + N' DESC )' - + CASE WHEN @sort_order = 'plan hashes' + /* + Bolt the extra columns on, because we need them to be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having this column visible in the output a bad thing? + Probably not. + */ + + CASE WHEN @sort_order = 'plan count by hashes' THEN N' - , hashes.plan_hash_count_for_query_hash' + , hashes.plan_hash_count_for_query_hash, hashes.query_hash' ELSE N'' END ) @@ -7170,7 +7222,7 @@ FROM N' FROM #query_store_runtime_stats AS qsrs' ) - IF @sort_order = 'plan hashes' + IF @sort_order = 'plan count by hashes' BEGIN SELECT @sql += N' @@ -7361,7 +7413,7 @@ ORDER BY ' + WHEN 'tempdb' THEN CASE WHEN @new = 1 THEN N'x.avg_tempdb_space_used_mb' ELSE N'x.avg_cpu_time' END WHEN 'executions' THEN N'x.count_executions' WHEN 'recent' THEN N'x.last_execution_time' - WHEN 'plan hashes' THEN N'x.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' ELSE N'x.avg_cpu_time_ms' END WHEN 1 @@ -7376,7 +7428,7 @@ ORDER BY ' + 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 AS money)' END WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' - WHEN 'plan hashes' THEN N'x.plan_hash_count_for_query_hash' + WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' END END From 7093fabb4a61eaf0e28ad06ebc78ed4360d84780 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:51:00 +0100 Subject: [PATCH 08/24] Fixed plurals. --- sp_QuickieStore/sp_QuickieStore.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index b65ab5b..a1abcdd 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -6521,7 +6521,7 @@ FROM Has the side-effect of making them visible in the final output, because our SELECT is just x.*. - But, really, is having this column visible in the output a bad thing? + But, really, is having the columns visible in the output a bad thing? Probably not. */ + CASE WHEN @sort_order = 'plan count by hashes' @@ -6764,7 +6764,7 @@ FROM Has the side-effect of making them visible in the final output, because our SELECT is just x.*. - But, really, is having this column visible in the output a bad thing? + But, really, is having the columns visible in the output a bad thing? Probably not. */ + CASE WHEN @sort_order = 'plan count by hashes' @@ -6981,7 +6981,7 @@ FROM Has the side-effect of making them visible in the final output, because our SELECT is just x.*. - But, really, is having this column visible in the output a bad thing? + But, really, is having the columns visible in the output a bad thing? Probably not. */ + CASE WHEN @sort_order = 'plan count by hashes' @@ -7199,7 +7199,7 @@ FROM Has the side-effect of making them visible in the final output, because our SELECT is just x.*. - But, really, is having this column visible in the output a bad thing? + But, really, is having the columns visible in the output a bad thing? Probably not. */ + CASE WHEN @sort_order = 'plan count by hashes' From 2217a25b4284fb1156fa57fb0038899aba415192 Mon Sep 17 00:00:00 2001 From: Reece Goding <67124261+ReeceGoding@users.noreply.github.com> Date: Sat, 13 Jul 2024 16:36:52 +0100 Subject: [PATCH 09/24] Made new sort order fit better with @Top --- sp_QuickieStore/sp_QuickieStore.sql | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index a1abcdd..d37d2fb 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4394,8 +4394,13 @@ BEGIN END; SELECT + /* + The lack of @top is deliberate. + You get so many results per hash from this query that + adding in @top cuts off most of your results. + */ @sql += N' - SELECT TOP (@top) + SELECT QueryHashesWithIds.plan_id, QueryHashesWithCounts.query_hash, QueryHashesWithCounts.plan_hash_count_for_query_hash @@ -4432,7 +4437,7 @@ BEGIN ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash ORDER BY QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash - OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; + OPTION(RECOMPILE);' + @nc10; IF @debug = 1 BEGIN @@ -4601,17 +4606,18 @@ BEGIN ON qsq.query_id = qsp.query_id JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs ON qsp.plan_id = qsrs.plan_id + JOIN + ( + SELECT + hashes.query_hash, + DENSE_RANK() OVER (ORDER BY hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash DESC) AS ranking + FROM #plan_ids_with_query_hashes AS hashes + ) AS ranked_hashes + ON qsq.query_hash = ranked_hashes.query_hash WHERE 1 = 1 ' + @where_clause + N' - AND qsq.query_hash IN - ( - SELECT TOP (@top) - hashes.query_hash - FROM #plan_ids_with_query_hashes AS hashes - ORDER BY - hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash DESC - ) + AND ranked_hashes.ranking <= @TOP GROUP BY qsrs.plan_id OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; From 131d7ada57f60dd805279ff366d60d1286174917 Mon Sep 17 00:00:00 2001 From: RG Date: Thu, 18 Jul 2024 19:10:02 +0100 Subject: [PATCH 10/24] Truncated #plan_ids_with_query_hashes --- sp_QuickieStore/sp_QuickieStore.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index d37d2fb..1cca14f 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -6256,6 +6256,8 @@ BEGIN #query_types; TRUNCATE TABLE #wait_filter; + TRUNCATE TABLE + #plan_ids_with_query_hashes; TRUNCATE TABLE #only_queries_with_hints; TRUNCATE TABLE From 6f766abee235a9768143c810f9489f7328fbc657 Mon Sep 17 00:00:00 2001 From: RG Date: Sun, 21 Jul 2024 22:11:37 +0100 Subject: [PATCH 11/24] Added sort order for many wait stats. Minor edits to previous sort order. --- sp_QuickieStore/sp_QuickieStore.sql | 271 +++++++++++++++++++++++++--- 1 file changed, 250 insertions(+), 21 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 1cca14f..3f5e5a1 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -212,7 +212,7 @@ BEGIN CASE ap.name WHEN N'@database_name' THEN 'a database name with query store enabled' - WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes' + WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes, cpu waits, lock waits, locks waits, latch waits, latches waits, buffer latch waits, buffer latches waits, buffer io waits, log waits, log io waits, network waits, network io waits, parallel waits, parallelism waits, memory waits' WHEN N'@top' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807' WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999' WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999' @@ -496,23 +496,42 @@ CREATE TABLE ) PERSISTED NOT NULL PRIMARY KEY ); +/* +The following two tables are for adding extra columns +on to our output. We need these for sorting by anything +that isn't in query_store_runtime_stats. + +We still have to declare these tables even when they're +not used because the debug output breaks if we don't. + +They are database dependent, so remember to truncate +if @GetAllDatabases = 1. +*/ /* Holds plan_id with the count of the number of query hashes they have. Only used when we're sorting by how many plan hashes each query hash has. - -We still have to declare this table even when it's not used, -because the debug output breaks if we don't. */ CREATE TABLE #plan_ids_with_query_hashes ( - plan_id bigint, - query_hash binary(8), + plan_id bigint NOT NULL, + query_hash binary(8) NOT NULL, plan_hash_count_for_query_hash INT NOT NULL ); +/* +Largely just exists because total_query_wait_time_ms +isn't in our normal output. +*/ +CREATE TABLE + #plan_ids_with_total_waits +( + plan_id bigint NOT NULL, + total_query_wait_time_ms bigint NOT NULL +); + /* Hold plan hashes for plans we want */ @@ -1188,7 +1207,8 @@ DECLARE @utc_minutes_original bigint, @df integer, @work_start_utc time(0), - @work_end_utc time(0); + @work_end_utc time(0), + @sort_order_is_a_wait bit; /* In cases where we are escaping @query_text_search and @@ -2166,7 +2186,22 @@ IF @sort_order NOT IN 'tempdb', 'executions', 'recent', - 'plan count by hashes' + 'plan count by hashes', + 'cpu waits', + 'lock waits', + 'locks waits', + 'latch waits', + 'latches waits', + 'buffer latch waits', + 'buffer latches waits', + 'buffer io waits', + 'log waits', + 'log io waits', + 'network waits', + 'network io waits', + 'parallel waits', + 'parallelism waits', + 'memory waits' ) BEGIN RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; @@ -2175,12 +2210,40 @@ BEGIN @sort_order = 'cpu'; END; +/* +Checks if the sort order is for waits. +Cuts out a lot of repetition. +*/ +IF @sort_order IN + ( + 'cpu waits', + 'lock waits', + 'locks waits', + 'latch waits', + 'latches waits', + 'buffer latch waits', + 'buffer latches waits', + 'buffer io waits', + 'log waits', + 'log io waits', + 'network waits', + 'network io waits', + 'parallel waits', + 'parallelism waits', + 'memory waits' + ) +BEGIN + + SELECT + @sort_order_is_a_wait = 1; +END; + /* These columns are only available in 2017+ */ IF ( - @sort_order = 'tempdb' + (@sort_order = 'tempdb' OR @sort_order_is_a_wait = 1) AND @new = 0 ) BEGIN @@ -4375,7 +4438,10 @@ OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; END; /* -Populate sort-helping tables, if needed +Populate sort-helping tables, if needed. + +Again, these exist just to put in scope +columns that wouldn't normally be in scope. */ IF @sort_order = 'plan count by hashes' BEGIN @@ -4482,7 +4548,102 @@ BEGIN @sql, @current_table; END; -END; /*End populating sort-helping tables*/ +END; +IF @sort_order_is_a_wait = 1 +BEGIN + SELECT + @current_table = 'inserting #plan_ids_with_total_waits', + @sql = @isolation_level; + + IF @troubleshoot_performance = 1 + BEGIN + EXEC sys.sp_executesql + @troubleshoot_insert, + N'@current_table nvarchar(100)', + @current_table; + + SET STATISTICS XML ON; + END; + + SELECT + @sql += N' + SELECT TOP (@top) + qsrs.plan_id, + MAX(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms + FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws + ON qsrs.plan_id = qsws.plan_id + WHERE 1 = 1 + AND qsws.wait_category = ' + + CASE @sort_order + WHEN 'cpu waits' THEN N'1' + WHEN 'lock waits' THEN N'3' + WHEN 'locks waits' THEN N'3' + WHEN 'latch waits' THEN N'4' + WHEN 'latches waits' THEN N'4' + WHEN 'buffer latch waits' THEN N'5' + WHEN 'buffer latches waits' THEN N'5' + WHEN 'buffer io waits' THEN N'6' + WHEN 'log waits' THEN N'14' + WHEN 'log io waits' THEN N'14' + WHEN 'network waits' THEN N'15' + WHEN 'network io waits' THEN N'15' + WHEN 'parallel waits' THEN N'16' + WHEN 'parallelism waits' THEN N'16' + WHEN 'memory waits' THEN N'17' + END + + @where_clause + + N' + GROUP + BY qsrs.plan_id + ORDER BY + MAX(qsws.total_query_wait_time_ms) DESC + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; + + IF @debug = 1 + BEGIN + PRINT LEN(@sql); + PRINT @sql; + END; + + INSERT + #plan_ids_with_total_waits WITH(TABLOCK) + ( + plan_id, + total_query_wait_time_ms + ) + EXEC sys.sp_executesql + @sql, + @parameters, + @top, + @start_date, + @end_date, + @execution_count, + @duration_ms, + @execution_type_desc, + @database_id, + @queries_top, + @work_start_utc, + @work_end_utc; + + IF @troubleshoot_performance = 1 + BEGIN + SET STATISTICS XML OFF; + + EXEC sys.sp_executesql + @troubleshoot_update, + N'@current_table nvarchar(100)', + @current_table; + + EXEC sys.sp_executesql + @troubleshoot_info, + N'@sql nvarchar(max), + @current_table nvarchar(100)', + @sql, + @current_table; + END; +END; +/*End populating sort-helping tables*/ /* This section screens out index create and alter statements because who cares @@ -4579,7 +4740,12 @@ SELECT ); /* -This gets the plan_ids we care about +This gets the plan_ids we care about. + +We unfortunately need an ELSE IF chain here +because the final branch contains defaults +that we only want to hit if we did not hit +any others. */ SELECT @current_table = 'inserting #distinct_plans', @@ -4622,6 +4788,14 @@ BEGIN BY qsrs.plan_id OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; END +ELSE IF @sort_order_is_a_wait = 1 +BEGIN + SELECT + @sql += N' + SELECT + plan_id + FROM #plan_ids_with_total_waits' + @nc10; +END ELSE BEGIN SELECT @@ -4810,6 +4984,13 @@ CROSS APPLY JOIN #plan_ids_with_query_hashes AS hashes ON qsrs.plan_id = hashes.plan_id' END; + IF @sort_order_is_a_wait = 1 + BEGIN + SELECT + @sql += N' + JOIN #plan_ids_with_total_waits AS waits + ON qsrs.plan_id = waits.plan_id' + END; SELECT @sql += N' @@ -4829,7 +5010,7 @@ CASE @sort_order WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE N'qsrs.avg_cpu_time' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC ) AS qsrs GROUP BY @@ -6258,6 +6439,8 @@ BEGIN #wait_filter; TRUNCATE TABLE #plan_ids_with_query_hashes; + TRUNCATE TABLE + #plan_ids_with_total_waits; TRUNCATE TABLE #only_queries_with_hints; TRUNCATE TABLE @@ -6525,16 +6708,20 @@ FROM END + N' DESC )' /* - Bolt the extra columns on, because we need them to be in scope for sorting. + Bolt any special sorting columns on, because we need them to + be in scope for sorting. Has the side-effect of making them visible in the final output, because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - Probably not. + I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' + , waits.total_query_wait_time_ms' ELSE N'' END ) @@ -6768,16 +6955,20 @@ FROM END + N' DESC )' /* - Bolt the extra columns on, because we need them to be in scope for sorting. + Bolt any special sorting columns on, because we need them to + be in scope for sorting. Has the side-effect of making them visible in the final output, because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - Probably not. + I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' + , waits.total_query_wait_time_ms' ELSE N'' END ) @@ -6985,16 +7176,20 @@ FROM END + N' DESC )' /* - Bolt the extra columns on, because we need them to be in scope for sorting. + Bolt any special sorting columns on, because we need them to + be in scope for sorting. Has the side-effect of making them visible in the final output, because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - Probably not. + I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' + , waits.total_query_wait_time_ms' ELSE N'' END ) @@ -7203,16 +7398,20 @@ FROM END + N' DESC )' /* - Bolt the extra columns on, because we need them to be in scope for sorting. + Bolt any special sorting columns on, because we need them to + be in scope for sorting. Has the side-effect of making them visible in the final output, because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - Probably not. + I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' + , waits.total_query_wait_time_ms' ELSE N'' END ) @@ -7237,6 +7436,13 @@ FROM JOIN #plan_ids_with_query_hashes AS hashes ON qsrs.plan_id = hashes.plan_id' END; + IF @sort_order_is_a_wait = 1 + BEGIN + SELECT + @sql += N' + JOIN #plan_ids_with_total_waits AS waits + ON qsrs.plan_id = waits.plan_id' + END; SELECT @sql += @@ -9210,6 +9416,29 @@ BEGIN '#plan_ids_with_query_hashes is empty'; END; + IF EXISTS + ( + SELECT + 1/0 + FROM #plan_ids_with_query_hashes AS hashes + ) + BEGIN + SELECT + table_name = + '#plan_ids_with_total_waits', + waits.* + FROM #plan_ids_with_total_waits AS waits + ORDER BY + hashes.plan_id + OPTION(RECOMPILE); + END; + ELSE + BEGIN + SELECT + result = + '#plan_ids_with_total_waits is empty'; + END; + IF EXISTS ( SELECT From 3e00059d9c362f7e7102499b15f2a58e94b809d5 Mon Sep 17 00:00:00 2001 From: RG Date: Sun, 21 Jul 2024 22:26:33 +0100 Subject: [PATCH 12/24] Fixed syntax erorrs in debug. --- sp_QuickieStore/sp_QuickieStore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 3f5e5a1..7bbbaf2 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -9420,7 +9420,7 @@ BEGIN ( SELECT 1/0 - FROM #plan_ids_with_query_hashes AS hashes + FROM #plan_ids_with_total_waits AS waits ) BEGIN SELECT @@ -9429,7 +9429,7 @@ BEGIN waits.* FROM #plan_ids_with_total_waits AS waits ORDER BY - hashes.plan_id + waits.plan_id OPTION(RECOMPILE); END; ELSE From c424a2ec66508abe52a2775ffe79d03fd821c0e4 Mon Sep 17 00:00:00 2001 From: RG Date: Sun, 21 Jul 2024 23:08:57 +0100 Subject: [PATCH 13/24] Alaised new sort column. Fixed n column --- sp_QuickieStore/sp_QuickieStore.sql | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 7bbbaf2..1f4006b 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -6704,7 +6704,7 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE N'qsrs.avg_cpu_time_ms' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' /* @@ -6721,7 +6721,7 @@ FROM , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 THEN N' - , waits.total_query_wait_time_ms' + , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -6951,7 +6951,7 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE N'qsrs.avg_cpu_time_ms' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' /* @@ -6968,7 +6968,7 @@ FROM , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 THEN N' - , waits.total_query_wait_time_ms' + , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7172,7 +7172,7 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE N'qsrs.avg_cpu_time_ms' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' /* @@ -7189,7 +7189,7 @@ FROM , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 THEN N' - , waits.total_query_wait_time_ms' + , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7394,7 +7394,7 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE N'qsrs.avg_cpu_time_ms' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' /* @@ -7411,7 +7411,7 @@ FROM , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 THEN N' - , waits.total_query_wait_time_ms' + , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) From d6f26a11be98068329218b43dcfdf27b352d598c Mon Sep 17 00:00:00 2001 From: RG Date: Mon, 22 Jul 2024 22:17:01 +0100 Subject: [PATCH 14/24] Added sort_order for total waits. Improve debug output. Added missing line in qsrs output's ORDER BY. --- sp_QuickieStore/sp_QuickieStore.sql | 100 +++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 1f4006b..4cf6187 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -212,7 +212,7 @@ BEGIN CASE ap.name WHEN N'@database_name' THEN 'a database name with query store enabled' - WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes, cpu waits, lock waits, locks waits, latch waits, latches waits, buffer latch waits, buffer latches waits, buffer io waits, log waits, log io waits, network waits, network io waits, parallel waits, parallelism waits, memory waits' + WHEN N'@sort_order' THEN 'cpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes, cpu waits, lock waits, locks waits, latch waits, latches waits, buffer latch waits, buffer latches waits, buffer io waits, log waits, log io waits, network waits, network io waits, parallel waits, parallelism waits, memory waits, total waits' WHEN N'@top' THEN 'a positive integer between 1 and 9,223,372,036,854,775,807' WHEN N'@start_date' THEN 'January 1, 1753, through December 31, 9999' WHEN N'@end_date' THEN 'January 1, 1753, through December 31, 9999' @@ -528,7 +528,7 @@ isn't in our normal output. CREATE TABLE #plan_ids_with_total_waits ( - plan_id bigint NOT NULL, + plan_id bigint NOT NULL PRIMARY KEY, total_query_wait_time_ms bigint NOT NULL ); @@ -2201,7 +2201,8 @@ IF @sort_order NOT IN 'network io waits', 'parallel waits', 'parallelism waits', - 'memory waits' + 'memory waits', + 'total waits' ) BEGIN RAISERROR('The sort order (%s) you chose is so out of this world that I''m using cpu instead', 10, 1, @sort_order) WITH NOWAIT; @@ -2211,7 +2212,7 @@ BEGIN END; /* -Checks if the sort order is for waits. +Checks if the sort order is for a wait. Cuts out a lot of repetition. */ IF @sort_order IN @@ -2230,7 +2231,8 @@ IF @sort_order IN 'network io waits', 'parallel waits', 'parallelism waits', - 'memory waits' + 'memory waits', + 'total waits' ) BEGIN @@ -4549,7 +4551,87 @@ BEGIN @current_table; END; END; -IF @sort_order_is_a_wait = 1 +IF @sort_order = 'total waits' +BEGIN + SELECT + @current_table = 'inserting #plan_ids_with_total_waits', + @sql = @isolation_level; + + IF @troubleshoot_performance = 1 + BEGIN + EXEC sys.sp_executesql + @troubleshoot_insert, + N'@current_table nvarchar(100)', + @current_table; + + SET STATISTICS XML ON; + END; + + SELECT + @sql += N' + SELECT TOP (@top) + qsrs.plan_id, + SUM(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms + FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws + ON qsrs.plan_id = qsws.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + GROUP + BY qsrs.plan_id + ORDER BY + SUM(qsws.total_query_wait_time_ms) DESC + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; + + IF @debug = 1 + BEGIN + PRINT LEN(@sql); + PRINT @sql; + END; + + INSERT + #plan_ids_with_total_waits WITH(TABLOCK) + ( + plan_id, + total_query_wait_time_ms + ) + EXEC sys.sp_executesql + @sql, + @parameters, + @top, + @start_date, + @end_date, + @execution_count, + @duration_ms, + @execution_type_desc, + @database_id, + @queries_top, + @work_start_utc, + @work_end_utc; + + IF @troubleshoot_performance = 1 + BEGIN + SET STATISTICS XML OFF; + + EXEC sys.sp_executesql + @troubleshoot_update, + N'@current_table nvarchar(100)', + @current_table; + + EXEC sys.sp_executesql + @troubleshoot_info, + N'@sql nvarchar(max), + @current_table nvarchar(100)', + @sql, + @current_table; + END; +END; +/* + 'total waits' is special. It's a sum, not a max, so + we cover is above rather than here. +*/ +IF @sort_order_is_a_wait = 1 AND @sort_order <> 'total waits' BEGIN SELECT @current_table = 'inserting #plan_ids_with_total_waits', @@ -7628,7 +7710,7 @@ ORDER BY ' + WHEN 'executions' THEN N'x.count_executions' WHEN 'recent' THEN N'x.last_execution_time' WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' - ELSE N'x.avg_cpu_time_ms' + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time' END END WHEN 1 THEN @@ -7643,7 +7725,7 @@ ORDER BY ' + WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' - ELSE N'TRY_PARSE(x.avg_cpu_time_ms AS money)' + 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 AS money)' END END END + N' DESC @@ -9065,6 +9147,8 @@ BEGIN @database_name, sort_order = @sort_order, + sort_order_is_a_wait = + @sort_order_is_a_wait, [top] = @top, start_date = From 236737a8af90b218ea5cfafc0cb9a4cc3afa276d Mon Sep 17 00:00:00 2001 From: RG Date: Wed, 24 Jul 2024 22:28:47 +0100 Subject: [PATCH 15/24] Formatted and sorted new columns correctly. Added comment to explain why this is needed. --- sp_QuickieStore/sp_QuickieStore.sql | 36 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 4cf6187..f14efc2 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -6799,11 +6799,9 @@ FROM I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' - , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 - THEN N' - , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7043,14 +7041,13 @@ FROM because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - I find it's helpful. + I find it's helpful, but it does mean that we have to format them + when applicable. */ + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' - , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 - THEN N' - , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7267,11 +7264,9 @@ FROM I find it's helpful. */ + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' - , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 - THEN N' - , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7486,14 +7481,13 @@ FROM because our SELECT is just x.*. But, really, is having the columns visible in the output a bad thing? - I find it's helpful. + I find it's helpful, but it does mean that we have to format them + when applicable. */ + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' - , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' WHEN @sort_order_is_a_wait = 1 - THEN N' - , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' ELSE N'' END ) @@ -7712,6 +7706,10 @@ ORDER BY ' + WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time' END END + /* + The ORDER BY is on the same level as the topmost SELECT, which is just SELECT x.*. + This means that to sort formatted output, we have to un-format it. + */ WHEN 1 THEN CASE @sort_order @@ -7724,7 +7722,7 @@ ORDER BY ' + 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 AS money)' END WHEN 'executions' THEN N'TRY_PARSE(x.count_executions AS money)' WHEN 'recent' THEN N'x.last_execution_time' - WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' + WHEN 'plan count by hashes' THEN N'TRY_PARSE(x.plan_hash_count_for_query_hash AS money) DESC, x.query_hash' 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 AS money)' END END END From e0e70962ee3b52cf820dbf6c2cff1f9781d321a6 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 01:14:38 +0100 Subject: [PATCH 16/24] Made 'plan count by hashes' code much shorter and faster, and made it correctly count plans. --- sp_QuickieStore/sp_QuickieStore.sql | 111 +++++++++++++--------------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index f14efc2..fcaa49f 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4463,49 +4463,56 @@ BEGIN SELECT /* - The lack of @top is deliberate. - You get so many results per hash from this query that - adding in @top cuts off most of your results. + This sort order is useless if we don't show the + ties, so only DENSE_RANK() makes sense to use. + This is why this is not SELECT TOP. */ @sql += N' SELECT - QueryHashesWithIds.plan_id, - QueryHashesWithCounts.query_hash, - QueryHashesWithCounts.plan_hash_count_for_query_hash - FROM - ( - SELECT - qsq.query_hash, - COUNT(DISTINCT qsp.query_plan_hash) AS plan_hash_count_for_query_hash - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp - ON qsq.query_id = qsp.query_id - JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs - ON qsp.plan_id = qsrs.plan_id - WHERE 1 = 1 - ' + @where_clause - + N' - GROUP - BY qsq.query_hash - ) AS QueryHashesWithCounts - JOIN + ranked_plans.plan_id, + ranked_plans.query_hash, + ranked_plans.plan_hash_count_for_query_hash + FROM ( - SELECT - qsq.query_hash, - qsp.plan_id - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp - ON qsq.query_id = qsp.query_id - JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs - ON qsp.plan_id = qsrs.plan_id - WHERE 1 = 1 - ' + @where_clause - + N' - ) AS QueryHashesWithIds - ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash - ORDER BY - QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash - OPTION(RECOMPILE);' + @nc10; + SELECT + QueryHashesWithIds.plan_id, + QueryHashesWithCounts.query_hash, + QueryHashesWithCounts.plan_hash_count_for_query_hash, + DENSE_RANK() OVER (ORDER BY QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash DESC) AS ranking + FROM + ( + SELECT + qsq.query_hash, + COUNT(DISTINCT qsp.query_plan_hash) AS plan_hash_count_for_query_hash + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + GROUP + BY qsq.query_hash + ) AS QueryHashesWithCounts + JOIN + ( + SELECT + qsq.query_hash, + qsp.plan_id + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + ) AS QueryHashesWithIds + ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash + ) AS ranked_plans + WHERE ranked_plans.ranking <= @TOP + OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; IF @debug = 1 BEGIN @@ -4847,28 +4854,10 @@ IF @sort_order = 'plan count by hashes' BEGIN SELECT @sql += N' - SELECT - qsrs.plan_id - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp - ON qsq.query_id = qsp.query_id - JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs - ON qsp.plan_id = qsrs.plan_id - JOIN - ( - SELECT - hashes.query_hash, - DENSE_RANK() OVER (ORDER BY hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash DESC) AS ranking - FROM #plan_ids_with_query_hashes AS hashes - ) AS ranked_hashes - ON qsq.query_hash = ranked_hashes.query_hash - WHERE 1 = 1 - ' + @where_clause - + N' - AND ranked_hashes.ranking <= @TOP - GROUP - BY qsrs.plan_id - OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; + SELECT DISTINCT + plan_id + FROM #plan_ids_with_query_hashes + OPTION(RECOMPILE);' + @nc10; END ELSE IF @sort_order_is_a_wait = 1 BEGIN From 150d3e20592b5a787354b453582293b371164b3a Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 01:19:19 +0100 Subject: [PATCH 17/24] Typo fix. --- sp_QuickieStore/sp_QuickieStore.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index fcaa49f..27933df 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4636,7 +4636,7 @@ BEGIN END; /* 'total waits' is special. It's a sum, not a max, so - we cover is above rather than here. + we cover it above rather than here. */ IF @sort_order_is_a_wait = 1 AND @sort_order <> 'total waits' BEGIN From b4f71d1f80f2be5296a649b6ff1c756ec78b993c Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 02:12:15 +0100 Subject: [PATCH 18/24] Made new tables include database_ids to make @Get_All_Databases=1 work. --- sp_QuickieStore/sp_QuickieStore.sql | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 27933df..e8b85f6 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -499,13 +499,19 @@ CREATE TABLE /* The following two tables are for adding extra columns on to our output. We need these for sorting by anything -that isn't in query_store_runtime_stats. +that isn't in #query_store_runtime_stats. We still have to declare these tables even when they're not used because the debug output breaks if we don't. -They are database dependent, so remember to truncate -if @GetAllDatabases = 1. +They are database dependent but not truncated at +the end of each loop, so we need a database_id +column. + +We do not truncate these because we need them to still +be in scope and fully populated when we return our +final results from #query_store_runtime_stats, which +is done after the point where we would truncate. */ /* @@ -516,6 +522,7 @@ query hash has. CREATE TABLE #plan_ids_with_query_hashes ( + database_id int NOT NULL, plan_id bigint NOT NULL, query_hash binary(8) NOT NULL, plan_hash_count_for_query_hash INT NOT NULL @@ -528,8 +535,10 @@ isn't in our normal output. CREATE TABLE #plan_ids_with_total_waits ( - plan_id bigint NOT NULL PRIMARY KEY, - total_query_wait_time_ms bigint NOT NULL + database_id int NOT NULL, + plan_id bigint NOT NULL, + total_query_wait_time_ms bigint NOT NULL, + PRIMARY KEY (database_id, plan_id) ); /* @@ -4469,6 +4478,7 @@ BEGIN */ @sql += N' SELECT + @database_id, ranked_plans.plan_id, ranked_plans.query_hash, ranked_plans.plan_hash_count_for_query_hash @@ -4523,6 +4533,7 @@ BEGIN INSERT #plan_ids_with_query_hashes WITH(TABLOCK) ( + database_id, plan_id, query_hash, plan_hash_count_for_query_hash @@ -4577,6 +4588,7 @@ BEGIN SELECT @sql += N' SELECT TOP (@top) + @database_id, qsrs.plan_id, SUM(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs @@ -4600,6 +4612,7 @@ BEGIN INSERT #plan_ids_with_total_waits WITH(TABLOCK) ( + database_id, plan_id, total_query_wait_time_ms ) @@ -4657,6 +4670,7 @@ BEGIN SELECT @sql += N' SELECT TOP (@top) + @database_id, qsrs.plan_id, MAX(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs @@ -4698,7 +4712,8 @@ BEGIN INSERT #plan_ids_with_total_waits WITH(TABLOCK) ( - plan_id, + database_id, + plan_id, total_query_wait_time_ms ) EXEC sys.sp_executesql @@ -6508,10 +6523,6 @@ BEGIN #query_types; TRUNCATE TABLE #wait_filter; - TRUNCATE TABLE - #plan_ids_with_query_hashes; - TRUNCATE TABLE - #plan_ids_with_total_waits; TRUNCATE TABLE #only_queries_with_hints; TRUNCATE TABLE From c4f6cf8b0cd7799b3ed8334a3d55b42b5e36d6fa Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 02:25:03 +0100 Subject: [PATCH 19/24] Fixed primary key violation in new tables. --- sp_QuickieStore/sp_QuickieStore.sql | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index e8b85f6..cab532b 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4872,15 +4872,18 @@ BEGIN SELECT DISTINCT plan_id FROM #plan_ids_with_query_hashes + WHERE database_id = @database_id OPTION(RECOMPILE);' + @nc10; END ELSE IF @sort_order_is_a_wait = 1 BEGIN SELECT @sql += N' - SELECT + SELECT DISTINCT plan_id - FROM #plan_ids_with_total_waits' + @nc10; + FROM #plan_ids_with_total_waits + WHERE database_id = @database_id + OPTION(RECOMPILE);' + @nc10; END ELSE BEGIN From ff8273c136b24c84df4c81ae0a80bdba156fee1c Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 02:39:04 +0100 Subject: [PATCH 20/24] Added more checks for database_id with the new tables. --- sp_QuickieStore/sp_QuickieStore.sql | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index cab532b..13a9f6c 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -5071,14 +5071,16 @@ CROSS APPLY SELECT @sql += N' JOIN #plan_ids_with_query_hashes AS hashes - ON qsrs.plan_id = hashes.plan_id' + ON qsrs.plan_id = hashes.plan_id + AND hashes.database_id = @database_id' END; IF @sort_order_is_a_wait = 1 BEGIN SELECT @sql += N' JOIN #plan_ids_with_total_waits AS waits - ON qsrs.plan_id = waits.plan_id' + ON qsrs.plan_id = waits.plan_id + AND waits.database_id = @database_id' END; SELECT @@ -7513,14 +7515,16 @@ FROM SELECT @sql += N' JOIN #plan_ids_with_query_hashes AS hashes - ON qsrs.plan_id = hashes.plan_id' + ON qsrs.plan_id = hashes.plan_id + AND hashes.database_id = @database_id' END; IF @sort_order_is_a_wait = 1 BEGIN SELECT @sql += N' JOIN #plan_ids_with_total_waits AS waits - ON qsrs.plan_id = waits.plan_id' + ON qsrs.plan_id = waits.plan_id + AND waits.database_id = @database_id' END; SELECT From a26a404ed044d8c203d23f69ea6ff6fc9e916589 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 02:43:07 +0100 Subject: [PATCH 21/24] Made new database_id checks agree with what is in scope. --- sp_QuickieStore/sp_QuickieStore.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 13a9f6c..e840364 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -7516,7 +7516,7 @@ FROM @sql += N' JOIN #plan_ids_with_query_hashes AS hashes ON qsrs.plan_id = hashes.plan_id - AND hashes.database_id = @database_id' + AND qsrs.database_id = hashes.database_id' END; IF @sort_order_is_a_wait = 1 BEGIN @@ -7524,7 +7524,7 @@ FROM @sql += N' JOIN #plan_ids_with_total_waits AS waits ON qsrs.plan_id = waits.plan_id - AND waits.database_id = @database_id' + AND qsrs.database_id = waits.database_id' END; SELECT From 0a71f028a1ea7406cca41acf36431c06445ff897 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 12:08:41 +0100 Subject: [PATCH 22/24] Stopped multiplication of results in #plan_ids_with_query_hashes --- sp_QuickieStore/sp_QuickieStore.sql | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index e840364..6cc07e2 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -525,7 +525,8 @@ CREATE TABLE database_id int NOT NULL, plan_id bigint NOT NULL, query_hash binary(8) NOT NULL, - plan_hash_count_for_query_hash INT NOT NULL + plan_hash_count_for_query_hash INT NOT NULL, + PRIMARY KEY (database_id, plan_id, query_hash) ); /* @@ -4479,8 +4480,8 @@ BEGIN @sql += N' SELECT @database_id, - ranked_plans.plan_id, - ranked_plans.query_hash, + ranked_plans.plan_id, + ranked_plans.query_hash, ranked_plans.plan_hash_count_for_query_hash FROM ( @@ -4507,7 +4508,7 @@ BEGIN ) AS QueryHashesWithCounts JOIN ( - SELECT + SELECT DISTINCT qsq.query_hash, qsp.plan_id FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq From a714af83b9cff43c58744c8789f8d61be3c0d483 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 13:49:26 +0100 Subject: [PATCH 23/24] Stopped mixing tabs and spaces. --- sp_QuickieStore/sp_QuickieStore.sql | 268 ++++++++++++++-------------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 6cc07e2..9c910af 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4473,9 +4473,9 @@ BEGIN SELECT /* - This sort order is useless if we don't show the - ties, so only DENSE_RANK() makes sense to use. - This is why this is not SELECT TOP. + This sort order is useless if we don't show the + ties, so only DENSE_RANK() makes sense to use. + This is why this is not SELECT TOP. */ @sql += N' SELECT @@ -4485,42 +4485,42 @@ BEGIN ranked_plans.plan_hash_count_for_query_hash FROM ( - SELECT - QueryHashesWithIds.plan_id, - QueryHashesWithCounts.query_hash, - QueryHashesWithCounts.plan_hash_count_for_query_hash, - DENSE_RANK() OVER (ORDER BY QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash DESC) AS ranking - FROM - ( - SELECT - qsq.query_hash, - COUNT(DISTINCT qsp.query_plan_hash) AS plan_hash_count_for_query_hash - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp - ON qsq.query_id = qsp.query_id - JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs - ON qsp.plan_id = qsrs.plan_id - WHERE 1 = 1 - ' + @where_clause - + N' - GROUP - BY qsq.query_hash - ) AS QueryHashesWithCounts - JOIN - ( - SELECT DISTINCT - qsq.query_hash, - qsp.plan_id - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp - ON qsq.query_id = qsp.query_id - JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs - ON qsp.plan_id = qsrs.plan_id - WHERE 1 = 1 - ' + @where_clause - + N' - ) AS QueryHashesWithIds - ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash + SELECT + QueryHashesWithIds.plan_id, + QueryHashesWithCounts.query_hash, + QueryHashesWithCounts.plan_hash_count_for_query_hash, + DENSE_RANK() OVER (ORDER BY QueryHashesWithCounts.plan_hash_count_for_query_hash DESC, QueryHashesWithCounts.query_hash DESC) AS ranking + FROM + ( + SELECT + qsq.query_hash, + COUNT(DISTINCT qsp.query_plan_hash) AS plan_hash_count_for_query_hash + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + GROUP + BY qsq.query_hash + ) AS QueryHashesWithCounts + JOIN + ( + SELECT DISTINCT + qsq.query_hash, + qsp.plan_id + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_plan AS qsp + ON qsq.query_id = qsp.query_id + JOIN ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs + ON qsp.plan_id = qsrs.plan_id + WHERE 1 = 1 + ' + @where_clause + + N' + ) AS QueryHashesWithIds + ON QueryHashesWithCounts.query_hash = QueryHashesWithIds.query_hash ) AS ranked_plans WHERE ranked_plans.ranking <= @TOP OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; @@ -4536,7 +4536,7 @@ BEGIN ( database_id, plan_id, - query_hash, + query_hash, plan_hash_count_for_query_hash ) EXEC sys.sp_executesql @@ -4591,7 +4591,7 @@ BEGIN SELECT TOP (@top) @database_id, qsrs.plan_id, - SUM(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms + SUM(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws ON qsrs.plan_id = qsws.plan_id @@ -4615,7 +4615,7 @@ BEGIN ( database_id, plan_id, - total_query_wait_time_ms + total_query_wait_time_ms ) EXEC sys.sp_executesql @sql, @@ -4673,28 +4673,28 @@ BEGIN SELECT TOP (@top) @database_id, qsrs.plan_id, - MAX(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms + MAX(qsws.total_query_wait_time_ms) AS total_query_wait_time_ms FROM ' + @database_name_quoted + N'.sys.query_store_runtime_stats AS qsrs JOIN ' + @database_name_quoted + N'.sys.query_store_wait_stats AS qsws ON qsrs.plan_id = qsws.plan_id WHERE 1 = 1 AND qsws.wait_category = ' + CASE @sort_order - WHEN 'cpu waits' THEN N'1' - WHEN 'lock waits' THEN N'3' - WHEN 'locks waits' THEN N'3' - WHEN 'latch waits' THEN N'4' - WHEN 'latches waits' THEN N'4' - WHEN 'buffer latch waits' THEN N'5' - WHEN 'buffer latches waits' THEN N'5' - WHEN 'buffer io waits' THEN N'6' - WHEN 'log waits' THEN N'14' - WHEN 'log io waits' THEN N'14' - WHEN 'network waits' THEN N'15' - WHEN 'network io waits' THEN N'15' - WHEN 'parallel waits' THEN N'16' - WHEN 'parallelism waits' THEN N'16' - WHEN 'memory waits' THEN N'17' + WHEN 'cpu waits' THEN N'1' + WHEN 'lock waits' THEN N'3' + WHEN 'locks waits' THEN N'3' + WHEN 'latch waits' THEN N'4' + WHEN 'latches waits' THEN N'4' + WHEN 'buffer latch waits' THEN N'5' + WHEN 'buffer latches waits' THEN N'5' + WHEN 'buffer io waits' THEN N'6' + WHEN 'log waits' THEN N'14' + WHEN 'log io waits' THEN N'14' + WHEN 'network waits' THEN N'15' + WHEN 'network io waits' THEN N'15' + WHEN 'parallel waits' THEN N'16' + WHEN 'parallelism waits' THEN N'16' + WHEN 'memory waits' THEN N'17' END + @where_clause + N' @@ -4714,8 +4714,8 @@ BEGIN #plan_ids_with_total_waits WITH(TABLOCK) ( database_id, - plan_id, - total_query_wait_time_ms + plan_id, + total_query_wait_time_ms ) EXEC sys.sp_executesql @sql, @@ -5073,15 +5073,15 @@ CROSS APPLY @sql += N' JOIN #plan_ids_with_query_hashes AS hashes ON qsrs.plan_id = hashes.plan_id - AND hashes.database_id = @database_id' + AND hashes.database_id = @database_id' END; IF @sort_order_is_a_wait = 1 BEGIN SELECT @sql += N' - JOIN #plan_ids_with_total_waits AS waits + JOIN #plan_ids_with_total_waits AS waits ON qsrs.plan_id = waits.plan_id - AND waits.database_id = @database_id' + AND waits.database_id = @database_id' END; SELECT @@ -6792,24 +6792,24 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' - /* - Bolt any special sorting columns on, because we need them to - be in scope for sorting. - Has the side-effect of making them visible in the final output, - because our SELECT is just x.*. - - But, really, is having the columns visible in the output a bad thing? - I find it's helpful. - */ - + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' - WHEN @sort_order_is_a_wait = 1 - THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' - ELSE N'' - END + /* + Bolt any special sorting columns on, because we need them to + be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having the columns visible in the output a bad thing? + I find it's helpful. + */ + + CASE WHEN @sort_order = 'plan count by hashes' + THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + ELSE N'' + END ) ); END; /*End expert mode 1, format output 0 columns*/ @@ -7037,25 +7037,25 @@ FROM WHEN 'executions' THEN N'qsrs.count_executions' WHEN 'recent' THEN N'qsrs.last_execution_time' WHEN 'plan count by hashes' THEN N'hashes.plan_hash_count_for_query_hash DESC, hashes.query_hash' - ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END + ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' - /* - Bolt any special sorting columns on, because we need them to - be in scope for sorting. - Has the side-effect of making them visible in the final output, - because our SELECT is just x.*. - - But, really, is having the columns visible in the output a bad thing? - I find it's helpful, but it does mean that we have to format them - when applicable. - */ - + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' - WHEN @sort_order_is_a_wait = 1 - THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' - ELSE N'' - END + /* + Bolt any special sorting columns on, because we need them to + be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having the columns visible in the output a bad thing? + I find it's helpful, but it does mean that we have to format them + when applicable. + */ + + CASE WHEN @sort_order = 'plan count by hashes' + THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' + ELSE N'' + END ) ); END; /*End expert mode = 1, format output = 1*/ @@ -7260,21 +7260,21 @@ FROM ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' - /* - Bolt any special sorting columns on, because we need them to - be in scope for sorting. - Has the side-effect of making them visible in the final output, - because our SELECT is just x.*. - - But, really, is having the columns visible in the output a bad thing? - I find it's helpful. - */ - + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' - WHEN @sort_order_is_a_wait = 1 - THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' - ELSE N'' - END + /* + Bolt any special sorting columns on, because we need them to + be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having the columns visible in the output a bad thing? + I find it's helpful. + */ + + CASE WHEN @sort_order = 'plan count by hashes' + THEN N' , hashes.plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' , waits.total_query_wait_time_ms AS total_wait_time_from_sort_order_ms' + ELSE N'' + END ) ); END; /*End expert mode = 0, format output = 0*/ @@ -7480,22 +7480,22 @@ FROM ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'waits.total_query_wait_time_ms' ELSE N'qsrs.avg_cpu_time' END END + N' DESC )' - /* - Bolt any special sorting columns on, because we need them to - be in scope for sorting. - Has the side-effect of making them visible in the final output, - because our SELECT is just x.*. - - But, really, is having the columns visible in the output a bad thing? - I find it's helpful, but it does mean that we have to format them - when applicable. - */ - + CASE WHEN @sort_order = 'plan count by hashes' - THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' - WHEN @sort_order_is_a_wait = 1 - THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' - ELSE N'' - END + /* + Bolt any special sorting columns on, because we need them to + be in scope for sorting. + Has the side-effect of making them visible in the final output, + because our SELECT is just x.*. + + But, really, is having the columns visible in the output a bad thing? + I find it's helpful, but it does mean that we have to format them + when applicable. + */ + + CASE WHEN @sort_order = 'plan count by hashes' + THEN N' , FORMAT(hashes.plan_hash_count_for_query_hash, ''N0'') AS plan_hash_count_for_query_hash, hashes.query_hash' + WHEN @sort_order_is_a_wait = 1 + THEN N' , FORMAT(waits.total_query_wait_time_ms, ''N0'') AS total_wait_time_from_sort_order_ms' + ELSE N'' + END ) ); END; /*End expert mode = 0, format output = 1*/ @@ -7525,7 +7525,7 @@ FROM @sql += N' JOIN #plan_ids_with_total_waits AS waits ON qsrs.plan_id = waits.plan_id - AND qsrs.database_id = waits.database_id' + AND qsrs.database_id = waits.database_id' END; SELECT @@ -7714,10 +7714,10 @@ ORDER BY ' + WHEN 'plan count by hashes' THEN N'x.plan_hash_count_for_query_hash DESC, x.query_hash' ELSE CASE WHEN @sort_order_is_a_wait = 1 THEN N'x.total_wait_time_from_sort_order_ms' ELSE N'x.avg_cpu_time' END END - /* - The ORDER BY is on the same level as the topmost SELECT, which is just SELECT x.*. - This means that to sort formatted output, we have to un-format it. - */ + /* + The ORDER BY is on the same level as the topmost SELECT, which is just SELECT x.*. + This means that to sort formatted output, we have to un-format it. + */ WHEN 1 THEN CASE @sort_order @@ -9153,8 +9153,8 @@ BEGIN @database_name, sort_order = @sort_order, - sort_order_is_a_wait = - @sort_order_is_a_wait, + sort_order_is_a_wait = + @sort_order_is_a_wait, [top] = @top, start_date = From fc27178295100132818c2783c8d926cb2a902869 Mon Sep 17 00:00:00 2001 From: RG Date: Sat, 27 Jul 2024 13:54:13 +0100 Subject: [PATCH 24/24] Moved sort-helping tables to just before #distinct_plans. This lets maintenance plans be filtered out. --- sp_QuickieStore/sp_QuickieStore.sql | 196 ++++++++++++++-------------- 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/sp_QuickieStore/sp_QuickieStore.sql b/sp_QuickieStore/sp_QuickieStore.sql index 9c910af..f7f2637 100644 --- a/sp_QuickieStore/sp_QuickieStore.sql +++ b/sp_QuickieStore/sp_QuickieStore.sql @@ -4449,11 +4449,111 @@ OPTION(RECOMPILE, OPTIMIZE FOR (@top = 9223372036854775807));' + @nc10; )' + @nc10; END; +/* +This section screens out index create and alter statements because who cares +*/ + +SELECT + @current_table = 'inserting #maintenance_plans', + @sql = @isolation_level; + +IF @troubleshoot_performance = 1 +BEGIN + EXEC sys.sp_executesql + @troubleshoot_insert, + N'@current_table nvarchar(100)', + @current_table; + + SET STATISTICS XML ON; +END; + +SELECT + @sql += N' +SELECT DISTINCT + qsp.plan_id +FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp +WHERE NOT EXISTS + ( + SELECT + 1/0 + FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq + JOIN ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt + ON qsqt.query_text_id = qsq.query_text_id + WHERE qsq.query_id = qsp.query_id + AND qsqt.query_sql_text NOT LIKE N''ALTER INDEX%'' + AND qsqt.query_sql_text NOT LIKE N''ALTER TABLE%'' + AND qsqt.query_sql_text NOT LIKE N''CREATE%INDEX%'' + AND qsqt.query_sql_text NOT LIKE N''CREATE STATISTICS%'' + AND qsqt.query_sql_text NOT LIKE N''UPDATE STATISTICS%'' + AND qsqt.query_sql_text NOT LIKE N''SELECT StatMan%'' + AND qsqt.query_sql_text NOT LIKE N''DBCC%'' + AND qsqt.query_sql_text NOT LIKE N''(@[_]msparam%'' + ) +OPTION(RECOMPILE);' + @nc10; + +IF @debug = 1 +BEGIN + PRINT LEN(@sql); + PRINT @sql; +END; + +INSERT + #maintenance_plans WITH(TABLOCK) +( + plan_id +) +EXEC sys.sp_executesql + @sql; + +IF @troubleshoot_performance = 1 +BEGIN + SET STATISTICS XML OFF; + + EXEC sys.sp_executesql + @troubleshoot_update, + N'@current_table nvarchar(100)', + @current_table; + + EXEC sys.sp_executesql + @troubleshoot_info, + N'@sql nvarchar(max), + @current_table nvarchar(100)', + @sql, + @current_table; +END; + +SELECT + @where_clause += N'AND NOT EXISTS + ( + SELECT + 1/0 + FROM #maintenance_plans AS mp + WHERE mp.plan_id = qsrs.plan_id + )' + @nc10; + +/* +Tidy up the where clause a bit +*/ +SELECT + @where_clause = + SUBSTRING + ( + @where_clause, + 1, + LEN(@where_clause) - 1 + ); + /* Populate sort-helping tables, if needed. -Again, these exist just to put in scope +In theory, these exist just to put in scope columns that wouldn't normally be in scope. +However, they're also quite helpful for the next +temp table, #distinct_plans. + +Note that this block must come after #maintenance_plans +because that edits @where_clause and we want to use +that here. */ IF @sort_order = 'plan count by hashes' BEGIN @@ -4750,100 +4850,6 @@ BEGIN END; /*End populating sort-helping tables*/ -/* -This section screens out index create and alter statements because who cares -*/ - -SELECT - @current_table = 'inserting #maintenance_plans', - @sql = @isolation_level; - -IF @troubleshoot_performance = 1 -BEGIN - EXEC sys.sp_executesql - @troubleshoot_insert, - N'@current_table nvarchar(100)', - @current_table; - - SET STATISTICS XML ON; -END; - -SELECT - @sql += N' -SELECT DISTINCT - qsp.plan_id -FROM ' + @database_name_quoted + N'.sys.query_store_plan AS qsp -WHERE NOT EXISTS - ( - SELECT - 1/0 - FROM ' + @database_name_quoted + N'.sys.query_store_query AS qsq - JOIN ' + @database_name_quoted + N'.sys.query_store_query_text AS qsqt - ON qsqt.query_text_id = qsq.query_text_id - WHERE qsq.query_id = qsp.query_id - AND qsqt.query_sql_text NOT LIKE N''ALTER INDEX%'' - AND qsqt.query_sql_text NOT LIKE N''ALTER TABLE%'' - AND qsqt.query_sql_text NOT LIKE N''CREATE%INDEX%'' - AND qsqt.query_sql_text NOT LIKE N''CREATE STATISTICS%'' - AND qsqt.query_sql_text NOT LIKE N''UPDATE STATISTICS%'' - AND qsqt.query_sql_text NOT LIKE N''SELECT StatMan%'' - AND qsqt.query_sql_text NOT LIKE N''DBCC%'' - AND qsqt.query_sql_text NOT LIKE N''(@[_]msparam%'' - ) -OPTION(RECOMPILE);' + @nc10; - -IF @debug = 1 -BEGIN - PRINT LEN(@sql); - PRINT @sql; -END; - -INSERT - #maintenance_plans WITH(TABLOCK) -( - plan_id -) -EXEC sys.sp_executesql - @sql; - -IF @troubleshoot_performance = 1 -BEGIN - SET STATISTICS XML OFF; - - EXEC sys.sp_executesql - @troubleshoot_update, - N'@current_table nvarchar(100)', - @current_table; - - EXEC sys.sp_executesql - @troubleshoot_info, - N'@sql nvarchar(max), - @current_table nvarchar(100)', - @sql, - @current_table; -END; - -SELECT - @where_clause += N'AND NOT EXISTS - ( - SELECT - 1/0 - FROM #maintenance_plans AS mp - WHERE mp.plan_id = qsrs.plan_id - )' + @nc10; - -/* -Tidy up the where clause a bit -*/ -SELECT - @where_clause = - SUBSTRING - ( - @where_clause, - 1, - LEN(@where_clause) - 1 - ); - /* This gets the plan_ids we care about.