From 5403fbfa5f15549347043b39d0acf70054ccf251 Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Sun, 10 May 2026 15:54:31 +0100 Subject: [PATCH 1/5] Allow filter by "Execution Type" #320 --- .../Controls/QueryStoreGridControl.axaml | 14 +++++++++-- .../Controls/QueryStoreGridControl.axaml.cs | 25 +++++++++++++++++-- src/PlanViewer.Core/Models/QueryStorePlan.cs | 1 + .../Services/QueryStoreService.cs | 24 +++++++++++++++--- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index 29555d1..eba8d0e 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -79,21 +79,31 @@ + SelectedIndex="0" SelectionChanged="SearchType_SelectionChanged"> + - + + + + + + + + + > FetchTopPlansAsync( var phase3QueryJoin = needsQueryJoin ? " JOIN sys.query_store_query AS q ON p.query_id = q.query_id\n" : ""; + var phase2ExecutionTypeClause = ""; + if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + { + phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; + parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + } // Time-range filter: always filter on interval start_time (indexed). // The hoursBack fallback also uses interval start_time instead of @@ -218,7 +224,7 @@ WHERE EXISTS SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id -) +){phase2ExecutionTypeClause} GROUP BY rs.plan_id OPTION (RECOMPILE); @@ -1004,6 +1010,12 @@ public static async Task FetchGroupedByQueryHashAsync( parameters.Add(new SqlParameter("@filterModule", moduleVal)); } var filterSql = filterClauses.Count > 0 ? "\n" + string.Join("\n", filterClauses) : ""; + var phase2ExecutionTypeClause = ""; + if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + { + phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; + parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + } var sql = $@" SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -1042,7 +1054,7 @@ INSERT INTO #plan_stats SUM(rs.count_executions), MAX(rs.last_execution_time) FROM sys.query_store_runtime_stats AS rs -WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id) +WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id){phase2ExecutionTypeClause} GROUP BY rs.plan_id OPTION (RECOMPILE); @@ -1263,6 +1275,12 @@ public static async Task FetchGroupedByModuleAsync( parameters.Add(new SqlParameter("@filterQueryHash", filter.QueryHash.Trim())); } var filterSql = filterClauses.Count > 0 ? "\n" + string.Join("\n", filterClauses) : ""; + var phase2ExecutionTypeClause = ""; + if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + { + phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; + parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + } var sql = $@" SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; @@ -1301,7 +1319,7 @@ INSERT INTO #plan_stats SUM(rs.count_executions), MAX(rs.last_execution_time) FROM sys.query_store_runtime_stats AS rs -WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id) +WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id){phase2ExecutionTypeClause} GROUP BY rs.plan_id OPTION (RECOMPILE); From 1deba1a5cc4da6c7b7ca7b712ef380586fda2e2f Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Sun, 10 May 2026 15:55:18 +0100 Subject: [PATCH 2/5] Add the "Execution Type" to the grids. #320 --- .../Controls/QueryStoreGridControl.axaml | 1 + .../Controls/QueryStoreGridControl.axaml.cs | 2 + .../Dialogs/QueryStoreHistoryWindow.axaml | 1 + .../Models/QueryStoreGroupBy.cs | 1 + .../Models/QueryStoreHistoryRow.cs | 1 + src/PlanViewer.Core/Models/QueryStorePlan.cs | 1 + .../Services/QueryStoreService.cs | 37 +++++++++++++++---- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index eba8d0e..b99d849 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -360,6 +360,7 @@ + diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs index e9987d1..da3566b 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs @@ -556,6 +556,7 @@ private static QueryStorePlan GroupedRowToPlan(QueryStoreGroupedPlanRow row) QueryText = row.QueryText, PlanXml = row.PlanXml, CountExecutions = row.CountExecutions, + ExecutionTypeDesc = row.ExecutionTypeDesc, TotalCpuTimeUs = row.TotalCpuTimeUs, TotalDurationUs = row.TotalDurationUs, TotalLogicalIoReads = row.TotalLogicalIoReads, @@ -1886,6 +1887,7 @@ public double WaitMaxGrandTotal public string QueryHash => Plan.QueryHash; public string QueryPlanHash => Plan.QueryPlanHash; public string ModuleName => Plan.ModuleName; + public string ExecutionTypeDesc => Plan.ExecutionTypeDesc; public string ExecsDisplay => Plan.CountExecutions.ToString("N0"); public string TotalCpuDisplay => (Plan.TotalCpuTimeUs / 1000.0).ToString("N0"); diff --git a/src/PlanViewer.App/Dialogs/QueryStoreHistoryWindow.axaml b/src/PlanViewer.App/Dialogs/QueryStoreHistoryWindow.axaml index 04d9188..cccbcba 100644 --- a/src/PlanViewer.App/Dialogs/QueryStoreHistoryWindow.axaml +++ b/src/PlanViewer.App/Dialogs/QueryStoreHistoryWindow.axaml @@ -147,6 +147,7 @@ + diff --git a/src/PlanViewer.Core/Models/QueryStoreGroupBy.cs b/src/PlanViewer.Core/Models/QueryStoreGroupBy.cs index 6d62f70..1111854 100644 --- a/src/PlanViewer.Core/Models/QueryStoreGroupBy.cs +++ b/src/PlanViewer.Core/Models/QueryStoreGroupBy.cs @@ -44,6 +44,7 @@ public class QueryStoreGroupedPlanRow /// for a query_hash/plan_hash pair. Only meaningful for leaf-level (QueryId/PlanId) rows. /// public bool IsTopRepresentative { get; set; } + public string ExecutionTypeDesc { get; set; } = ""; } /// diff --git a/src/PlanViewer.Core/Models/QueryStoreHistoryRow.cs b/src/PlanViewer.Core/Models/QueryStoreHistoryRow.cs index b275acf..e89b5b7 100644 --- a/src/PlanViewer.Core/Models/QueryStoreHistoryRow.cs +++ b/src/PlanViewer.Core/Models/QueryStoreHistoryRow.cs @@ -27,6 +27,7 @@ public class QueryStoreHistoryRow public int MinDop { get; set; } public int MaxDop { get; set; } public DateTime? LastExecutionUtc { get; set; } + public string ExecutionTypeDesc { get; set; } = ""; // Display-formatted properties (2 decimal places) public string AvgDurationMsDisplay => AvgDurationMs.ToString("N2"); diff --git a/src/PlanViewer.Core/Models/QueryStorePlan.cs b/src/PlanViewer.Core/Models/QueryStorePlan.cs index d5fa582..5e05e62 100644 --- a/src/PlanViewer.Core/Models/QueryStorePlan.cs +++ b/src/PlanViewer.Core/Models/QueryStorePlan.cs @@ -23,6 +23,7 @@ public class QueryStorePlan public string QueryHash { get; set; } = ""; public string QueryPlanHash { get; set; } = ""; public string ModuleName { get; set; } = ""; + public string ExecutionTypeDesc { get; set; } = ""; public string QueryText { get; set; } = ""; public string PlanXml { get; set; } = ""; diff --git a/src/PlanViewer.Core/Services/QueryStoreService.cs b/src/PlanViewer.Core/Services/QueryStoreService.cs index 851f781..11feb51 100644 --- a/src/PlanViewer.Core/Services/QueryStoreService.cs +++ b/src/PlanViewer.Core/Services/QueryStoreService.cs @@ -205,7 +205,8 @@ FROM sys.query_store_runtime_stats_interval AS rsi total_physical_reads float NOT NULL, total_memory_pages float NOT NULL, total_executions bigint NOT NULL, - last_execution_time datetimeoffset NOT NULL + last_execution_time datetimeoffset NOT NULL, + execution_type_desc nvarchar(60) NOT NULL ); INSERT INTO #plan_stats SELECT @@ -217,7 +218,8 @@ INSERT INTO #plan_stats SUM(rs.avg_physical_io_reads * rs.count_executions), SUM(rs.avg_query_max_used_memory * rs.count_executions), SUM(rs.count_executions), - MAX(rs.last_execution_time) + MAX(rs.last_execution_time), + MAX(rs.execution_type_desc) FROM sys.query_store_runtime_stats AS rs WHERE EXISTS ( @@ -242,6 +244,7 @@ WITH ranked AS ( ps.total_memory_pages, ps.total_executions, ps.last_execution_time, + ps.execution_type_desc, CASE WHEN ps.total_executions > 0 THEN ps.total_cpu_us / ps.total_executions ELSE 0 END AS avg_cpu_us, CASE WHEN ps.total_executions > 0 @@ -275,7 +278,8 @@ SELECT TOP ({topN}) CAST(r.total_writes AS bigint) AS total_writes, CAST(r.total_physical_reads AS bigint) AS total_physical_reads, CAST(r.total_memory_pages AS bigint) AS total_memory_pages, - r.last_execution_time + r.last_execution_time, + r.execution_type_desc INTO #top_plans FROM ranked AS r WHERE 1 = 1 {rnClause} @@ -307,7 +311,8 @@ FROM ranked AS r WHEN q.object_id <> 0 THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) ELSE N'' - END + END, + tp.execution_type_desc FROM #top_plans AS tp JOIN sys.query_store_plan AS p ON tp.plan_id = p.plan_id JOIN sys.query_store_query AS q ON p.query_id = q.query_id @@ -352,6 +357,7 @@ ELSE N'' QueryHash = reader.IsDBNull(18) ? "" : reader.GetString(18), QueryPlanHash = reader.IsDBNull(19) ? "" : reader.GetString(19), ModuleName = reader.IsDBNull(20) ? "" : reader.GetString(20), + ExecutionTypeDesc = reader.IsDBNull(21) ? "" : reader.GetString(21), }); } @@ -398,7 +404,8 @@ THEN SUM(rs.avg_rowcount * rs.count_executions) / SUM(rs.count_executions) SUM(rs.avg_physical_io_reads * rs.count_executions), MIN(rs.min_dop), MAX(rs.max_dop), - MAX(rs.last_execution_time) + MAX(rs.last_execution_time), + MAX(rs.execution_type_desc) FROM sys.query_store_runtime_stats rs JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id @@ -510,7 +517,8 @@ THEN SUM(rs.avg_rowcount * rs.count_executions) / SUM(rs.count_executions) SUM(rs.avg_physical_io_reads * rs.count_executions), MIN(rs.min_dop), MAX(rs.max_dop), - MAX(rs.last_execution_time) + MAX(rs.last_execution_time), + MAX(rs.execution_type_desc) FROM sys.query_store_runtime_stats rs JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id @@ -555,6 +563,7 @@ JOIN sys.query_store_query q MinDop = (int)reader.GetInt64(16), MaxDop = (int)reader.GetInt64(17), LastExecutionUtc = reader.IsDBNull(18) ? null : ((DateTimeOffset)reader.GetValue(18)).UtcDateTime, + ExecutionTypeDesc = reader.IsDBNull(19) ? "" : reader.GetString(19), }); } @@ -625,7 +634,8 @@ THEN SUM(rs.avg_rowcount * rs.count_executions) / SUM(rs.count_executions) MIN(rs.min_dop), MAX(rs.max_dop), MAX(rs.last_execution_time), - SUM(rs.avg_query_max_used_memory * rs.count_executions) + SUM(rs.avg_query_max_used_memory * rs.count_executions), + MAX(rs.execution_type_desc) FROM sys.query_store_runtime_stats rs JOIN sys.query_store_runtime_stats_interval rsi ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id @@ -670,6 +680,7 @@ JOIN sys.query_store_query q MaxDop = (int)reader.GetInt64(16), LastExecutionUtc = reader.IsDBNull(17) ? null : ((DateTimeOffset)reader.GetValue(17)).UtcDateTime, TotalMemoryMb = reader.GetDouble(18), + ExecutionTypeDesc = reader.IsDBNull(19) ? "" : reader.GetString(19), }); } @@ -1226,6 +1237,12 @@ FROM ranked } } + if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + { + foreach (var r in result.LeafRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; + foreach (var r in result.IntermediateRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; + } + return result; } @@ -1512,6 +1529,12 @@ FROM ranked } } + if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + { + foreach (var r in result.LeafRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; + foreach (var r in result.IntermediateRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; + } + return result; } From 95c99b9ac9a3ec1b9723cc07c1302f3190562d82 Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Sun, 10 May 2026 15:59:27 +0100 Subject: [PATCH 3/5] Make combobox wider #320 --- src/PlanViewer.App/Controls/QueryStoreGridControl.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index b99d849..a76fe32 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -78,7 +78,7 @@ - From 8916b6cfce70be87041143c35e841dec4bd65dbb Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Sun, 10 May 2026 16:18:09 +0100 Subject: [PATCH 4/5] Add the "Execution Type" text to the TextBlock #320 --- src/PlanViewer.App/Controls/QueryStoreGridControl.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index a76fe32..f6ea508 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -96,7 +96,7 @@ KeyDown="SearchValue_KeyDown"/> - + From bcb043f925210189d9234744a46daa3620a82856 Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Sun, 10 May 2026 19:34:32 +0100 Subject: [PATCH 5/5] Implements suggested fixes + "Failed" combo --- .../Controls/QueryStoreGridControl.axaml | 2 + .../Controls/QueryStoreGridControl.axaml.cs | 12 ++- src/PlanViewer.Core/Models/QueryStorePlan.cs | 6 +- .../Services/QueryStoreService.cs | 88 ++++++++++++------- 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index f6ea508..d5ae202 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -99,9 +99,11 @@ + + diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs index da3566b..b6af494 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs @@ -603,6 +603,7 @@ private static QueryStorePlan AggregateGroupedRows(List + /// One or more execution_type_desc values to filter by. + /// Single value → equality predicate; multiple values (e.g. "Aborted","Exception" for "Failed") → IN predicate. + /// + public string[]? ExecutionTypeDescs { get; set; } } public class QueryStorePlan diff --git a/src/PlanViewer.Core/Services/QueryStoreService.cs b/src/PlanViewer.Core/Services/QueryStoreService.cs index 11feb51..137c084 100644 --- a/src/PlanViewer.Core/Services/QueryStoreService.cs +++ b/src/PlanViewer.Core/Services/QueryStoreService.cs @@ -139,10 +139,14 @@ public static async Task> FetchTopPlansAsync( ? " JOIN sys.query_store_query AS q ON p.query_id = q.query_id\n" : ""; var phase2ExecutionTypeClause = ""; - if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + if (filter?.ExecutionTypeDescs?.Length > 0) { - phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; - parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + var etParamNames = filter.ExecutionTypeDescs + .Select((_, i) => $"@executionType{i}") + .ToList(); + phase2ExecutionTypeClause = $"\nAND rs.execution_type_desc IN ({string.Join(", ", etParamNames)})"; + for (var i = 0; i < filter.ExecutionTypeDescs.Length; i++) + parameters.Add(new SqlParameter($"@executionType{i}", filter.ExecutionTypeDescs[i])); } // Time-range filter: always filter on interval start_time (indexed). @@ -219,7 +223,12 @@ INSERT INTO #plan_stats SUM(rs.avg_query_max_used_memory * rs.count_executions), SUM(rs.count_executions), MAX(rs.last_execution_time), - MAX(rs.execution_type_desc) + -- Pick execution_type_desc from the most-recently-executed interval to avoid + -- alphabetical bias: MAX would choose 'Regular' over 'Aborted'. + RTRIM(CAST(SUBSTRING(MAX( + CONVERT(char(27), CAST(rs.last_execution_time AS datetime2(7)), 121) + + CAST(ISNULL(rs.execution_type_desc, '') AS char(60)) + ), 28, 60) AS nvarchar(60))) FROM sys.query_store_runtime_stats AS rs WHERE EXISTS ( @@ -449,6 +458,7 @@ JOIN sys.query_store_plan p MinDop = (int)reader.GetInt64(16), MaxDop = (int)reader.GetInt64(17), LastExecutionUtc = reader.IsDBNull(18) ? null : ((DateTimeOffset)reader.GetValue(18)).UtcDateTime, + ExecutionTypeDesc = reader.IsDBNull(19) ? "" : reader.GetString(19), }); } @@ -1022,10 +1032,14 @@ public static async Task FetchGroupedByQueryHashAsync( } var filterSql = filterClauses.Count > 0 ? "\n" + string.Join("\n", filterClauses) : ""; var phase2ExecutionTypeClause = ""; - if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + if (filter?.ExecutionTypeDescs?.Length > 0) { - phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; - parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + var etParamNames = filter.ExecutionTypeDescs + .Select((_, i) => $"@executionType{i}") + .ToList(); + phase2ExecutionTypeClause = $"\nAND rs.execution_type_desc IN ({string.Join(", ", etParamNames)})"; + for (var i = 0; i < filter.ExecutionTypeDescs.Length; i++) + parameters.Add(new SqlParameter($"@executionType{i}", filter.ExecutionTypeDescs[i])); } var sql = $@" @@ -1051,7 +1065,8 @@ FROM sys.query_store_runtime_stats_interval AS rsi total_physical_reads float NOT NULL, total_memory_pages float NOT NULL, total_executions bigint NOT NULL, - last_execution_time datetimeoffset NOT NULL + last_execution_time datetimeoffset NOT NULL, + execution_type_desc nvarchar(60) NOT NULL ); INSERT INTO #plan_stats SELECT @@ -1063,7 +1078,11 @@ INSERT INTO #plan_stats SUM(rs.avg_physical_io_reads * rs.count_executions), SUM(rs.avg_query_max_used_memory * rs.count_executions), SUM(rs.count_executions), - MAX(rs.last_execution_time) + MAX(rs.last_execution_time), + RTRIM(CAST(SUBSTRING(MAX( + CONVERT(char(27), CAST(rs.last_execution_time AS datetime2(7)), 121) + + CAST(ISNULL(rs.execution_type_desc, '') AS char(60)) + ), 28, 60) AS nvarchar(60))) FROM sys.query_store_runtime_stats AS rs WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id){phase2ExecutionTypeClause} GROUP BY rs.plan_id @@ -1103,6 +1122,7 @@ THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) SUM(ps.total_memory_pages) AS total_memory_pages, SUM(ps.total_executions) AS total_executions, MAX(ps.last_execution_time) AS last_execution_time, + MAX(ps.execution_type_desc) AS execution_type_desc, ROW_NUMBER() OVER (PARTITION BY q.query_hash ORDER BY SUM(ps.{metricCol}) DESC) AS rnum FROM #plan_stats ps JOIN sys.query_store_plan p ON ps.plan_id = p.plan_id @@ -1121,7 +1141,8 @@ ELSE N'' END CAST(total_physical_reads AS bigint) AS total_physical_reads, CAST(total_memory_pages AS bigint) AS total_memory_pages, total_executions, - last_execution_time + last_execution_time, + execution_type_desc INTO #plan_hash_rows FROM ph WHERE rnum <= 5; @@ -1144,6 +1165,7 @@ THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) CAST(ps.total_memory_pages AS bigint) AS total_memory_pages, ps.total_executions, ps.last_execution_time, + ps.execution_type_desc, ROW_NUMBER() OVER (PARTITION BY q.query_hash, p.query_plan_hash ORDER BY ps.{metricCol} DESC) AS rn_top, ROW_NUMBER() OVER (PARTITION BY q.query_hash, p.query_plan_hash ORDER BY ps.{metricCol} ASC) AS rn_bottom FROM #plan_stats ps @@ -1175,7 +1197,8 @@ FROM ranked r.total_memory_pages, r.total_executions, r.last_execution_time, -CASE WHEN r.rn_top = 1 THEN 1 ELSE 0 END AS is_top +CASE WHEN r.rn_top = 1 THEN 1 ELSE 0 END AS is_top, +r.execution_type_desc FROM #ranked_light r JOIN sys.query_store_query_text qt ON r.query_text_id = qt.query_text_id JOIN sys.query_store_plan p ON r.plan_id = p.plan_id; @@ -1212,6 +1235,7 @@ FROM ranked CountExecutions = reader.GetInt64(13), LastExecutedUtc = ((DateTimeOffset)reader.GetValue(14)).UtcDateTime, IsTopRepresentative = reader.GetInt32(15) == 1, + ExecutionTypeDesc = reader.IsDBNull(16) ? "" : reader.GetString(16), }); } @@ -1233,16 +1257,11 @@ FROM ranked TotalMemoryGrantPages = reader.GetInt64(8), CountExecutions = reader.GetInt64(9), LastExecutedUtc = ((DateTimeOffset)reader.GetValue(10)).UtcDateTime, + ExecutionTypeDesc = reader.IsDBNull(11) ? "" : reader.GetString(11), }); } } - if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) - { - foreach (var r in result.LeafRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; - foreach (var r in result.IntermediateRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; - } - return result; } @@ -1293,10 +1312,14 @@ public static async Task FetchGroupedByModuleAsync( } var filterSql = filterClauses.Count > 0 ? "\n" + string.Join("\n", filterClauses) : ""; var phase2ExecutionTypeClause = ""; - if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) + if (filter?.ExecutionTypeDescs?.Length > 0) { - phase2ExecutionTypeClause = "\nAND rs.execution_type_desc = @executionTypeDesc"; - parameters.Add(new SqlParameter("@executionTypeDesc", filter.ExecutionTypeDesc.Trim())); + var etParamNames = filter.ExecutionTypeDescs + .Select((_, i) => $"@executionType{i}") + .ToList(); + phase2ExecutionTypeClause = $"\nAND rs.execution_type_desc IN ({string.Join(", ", etParamNames)})"; + for (var i = 0; i < filter.ExecutionTypeDescs.Length; i++) + parameters.Add(new SqlParameter($"@executionType{i}", filter.ExecutionTypeDescs[i])); } var sql = $@" @@ -1322,7 +1345,8 @@ FROM sys.query_store_runtime_stats_interval AS rsi total_physical_reads float NOT NULL, total_memory_pages float NOT NULL, total_executions bigint NOT NULL, - last_execution_time datetimeoffset NOT NULL + last_execution_time datetimeoffset NOT NULL, + execution_type_desc nvarchar(60) NOT NULL ); INSERT INTO #plan_stats SELECT @@ -1334,7 +1358,11 @@ INSERT INTO #plan_stats SUM(rs.avg_physical_io_reads * rs.count_executions), SUM(rs.avg_query_max_used_memory * rs.count_executions), SUM(rs.count_executions), - MAX(rs.last_execution_time) + MAX(rs.last_execution_time), + RTRIM(CAST(SUBSTRING(MAX( + CONVERT(char(27), CAST(rs.last_execution_time AS datetime2(7)), 121) + + CAST(ISNULL(rs.execution_type_desc, '') AS char(60)) + ), 28, 60) AS nvarchar(60))) FROM sys.query_store_runtime_stats AS rs WHERE EXISTS (SELECT 1 FROM #intervals AS i WHERE i.runtime_stats_interval_id = rs.runtime_stats_interval_id){phase2ExecutionTypeClause} GROUP BY rs.plan_id @@ -1377,6 +1405,7 @@ THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) SUM(ps.total_memory_pages) AS total_memory_pages, SUM(ps.total_executions) AS total_executions, MAX(ps.last_execution_time) AS last_execution_time, + MAX(ps.execution_type_desc) AS execution_type_desc, ROW_NUMBER() OVER (PARTITION BY CASE WHEN q.object_id <> 0 THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) @@ -1402,7 +1431,8 @@ THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) CAST(total_physical_reads AS bigint) AS total_physical_reads, CAST(total_memory_pages AS bigint) AS total_memory_pages, total_executions, - last_execution_time + last_execution_time, + execution_type_desc INTO #qhash_rows FROM qh WHERE rnum <= 5; @@ -1425,6 +1455,7 @@ THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) CAST(ps.total_memory_pages AS bigint) AS total_memory_pages, ps.total_executions, ps.last_execution_time, + ps.execution_type_desc, ROW_NUMBER() OVER (PARTITION BY CASE WHEN q.object_id <> 0 THEN OBJECT_SCHEMA_NAME(q.object_id) + N'.' + OBJECT_NAME(q.object_id) @@ -1468,7 +1499,8 @@ FROM ranked r.total_memory_pages, r.total_executions, r.last_execution_time, - CASE WHEN r.rn_top = 1 THEN 1 ELSE 0 END AS is_top + CASE WHEN r.rn_top = 1 THEN 1 ELSE 0 END AS is_top, + r.execution_type_desc FROM #ranked_light r JOIN sys.query_store_query_text qt ON r.query_text_id = qt.query_text_id JOIN sys.query_store_plan p ON r.plan_id = p.plan_id; @@ -1505,6 +1537,7 @@ FROM ranked CountExecutions = reader.GetInt64(13), LastExecutedUtc = ((DateTimeOffset)reader.GetValue(14)).UtcDateTime, IsTopRepresentative = reader.GetInt32(15) == 1, + ExecutionTypeDesc = reader.IsDBNull(16) ? "" : reader.GetString(16), }); } @@ -1525,16 +1558,11 @@ FROM ranked TotalMemoryGrantPages = reader.GetInt64(7), CountExecutions = reader.GetInt64(8), LastExecutedUtc = ((DateTimeOffset)reader.GetValue(9)).UtcDateTime, + ExecutionTypeDesc = reader.IsDBNull(10) ? "" : reader.GetString(10), }); } } - if (!string.IsNullOrWhiteSpace(filter?.ExecutionTypeDesc)) - { - foreach (var r in result.LeafRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; - foreach (var r in result.IntermediateRows) r.ExecutionTypeDesc = filter.ExecutionTypeDesc!; - } - return result; }