You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/PlanViewer.Core/Services/PlanAnalyzer.cs
+49-9Lines changed: 49 additions & 9 deletions
Original file line number
Diff line number
Diff line change
@@ -921,21 +921,38 @@ _ when nonSargableReason.StartsWith("Function call") =>
921
921
?GetOperatorOwnElapsedMs(node)>0
922
922
:node.CostPercent>=20;
923
923
924
-
if(colCount<=3&&isSignificant)
924
+
if(isSignificant)
925
925
{
926
926
varscanKind=node.PhysicalOp=="Clustered Index Scan"
927
927
?"Clustered index scan"
928
928
:"Heap table scan";
929
-
varindexAdvice=node.PhysicalOp=="Clustered Index Scan"
930
-
?"Consider a nonclustered index on the output columns (as key or INCLUDE) so SQL Server can read a narrower structure."
931
-
:"Consider a clustered or nonclustered index on the output columns so SQL Server can read a narrower structure.";
932
929
933
-
node.Warnings.Add(newPlanWarning
930
+
if(colCount<=3)
934
931
{
935
-
WarningType="Bare Scan",
936
-
Message=$"{scanKind} reads the full table with no predicate, outputting {colCount} column(s): {Truncate(node.OutputColumns,200)}. {indexAdvice} For analytical workloads, a columnstore index may be a better fit.",
937
-
Severity=PlanWarningSeverity.Warning
938
-
});
932
+
// Narrow output: a nonclustered rowstore index can cover this cheaply.
933
+
varindexAdvice=node.PhysicalOp=="Clustered Index Scan"
934
+
?"Consider a nonclustered index on the output columns (as key or INCLUDE) so SQL Server can read a narrower structure."
935
+
:"Consider a clustered or nonclustered index on the output columns so SQL Server can read a narrower structure.";
936
+
937
+
node.Warnings.Add(newPlanWarning
938
+
{
939
+
WarningType="Bare Scan",
940
+
Message=$"{scanKind} reads the full table with no predicate, outputting {colCount} column(s): {Truncate(node.OutputColumns,200)}. {indexAdvice} For analytical workloads, a columnstore index may be a better fit.",
941
+
Severity=PlanWarningSeverity.Warning
942
+
});
943
+
}
944
+
else
945
+
{
946
+
// Wider output: rowstore NC index isn't a great fit (would have to
947
+
// carry too many columns), but columnstore doesn't care about column
948
+
// count. Suggest it for analytical / aggregate-style workloads.
949
+
node.Warnings.Add(newPlanWarning
950
+
{
951
+
WarningType="Bare Scan",
952
+
Message=$"{scanKind} reads the full table with no predicate, outputting {colCount} columns. A nonclustered rowstore index isn't a great fit for wide outputs, but if this is an analytical or aggregate-style query, a columnstore index (CCI or NCCI) can scan the same data far more cheaply — column count doesn't penalize columnstore the way it does rowstore indexes.",
953
+
Severity=PlanWarningSeverity.Warning
954
+
});
955
+
}
939
956
}
940
957
}
941
958
@@ -1229,6 +1246,29 @@ _ when nonSargableReason.StartsWith("Function call") =>
1229
1246
w.Message=$"Implicit conversion prevented an index seek, forcing a scan instead. Fix the data type mismatch: ensure the parameter or variable type matches the column type exactly. {w.Message}";
1230
1247
}
1231
1248
}
1249
+
1250
+
// Rule 35: Expensive Operator — always show operators that take a significant
1251
+
// share of statement time even when no other rule has something to say. Joe
1252
+
// (#215 C8) wanted expensive scans that the tool had nothing to suggest on
1253
+
// to still surface as top items. Threshold: self-time >= 20% of statement
1254
+
// elapsed. Only emits if no other warning is already on the node to avoid
1255
+
// doubling up. The benefit % is just the self-time share.
Message=$"{node.PhysicalOp} took {selfMs:N0}ms ({pct:N1}% of statement elapsed) but no specific rule identified a fix. Worth investigating: is the row volume necessary? Are upstream estimates driving this operator harder than it should be?",
0 commit comments