Join OR Clause: skip the dynamic seek for a list of parameters (#558) - #559
Conversation
Rule 15 matched the operator shape alone. WHERE t.A IN (@p1, @p2) on an indexed column builds the same Nested Loops / Merge Interval / TopN Sort / Concatenation / Constant Scan chain as a dynamic seek over the parameter values, so a query with no join was told to rewrite as UNION ALL (#558). The rule now also reads what each lookup branch produces, and fires only when at least one branch takes a value from another input: an outer column, or an expression the outer input computed ([Expr1002] for ON t.A = o.X + 1 OR ...). Branches of parameters, variables, literals and functions of them are a parameter list. A branch with nothing to read keeps the warning. Fixtures: the reporter's plan, plus a mixed column/parameter OR join and an OR join on outer expressions, both captured on SQL Server 2022. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ZVrq8tpA2DBPEFEqahFK6
|
Reviewed the Rule 15 change ( Traced No null-safety issues ( Nothing to flag. The mutation-style test coverage described in the PR body (five deliberately-wrong implementations, each caught by a specific test) matches what I'd want to see for a detection-shape change like this. |
Summary
Fixes #558.
Rule 15 (Join OR Clause) checked only the operator shape: Nested Loops, Merge Interval, TopN Sort, Concatenation, and two or more Constant Scan branches.
WHERE t.A IN (@p1, @p2)on an indexed column builds the same shape. It is a dynamic seek over the parameter values. So the rule told the user to rewrite a query with no join as a UNION ALL.The rule now also reads what each lookup branch produces:
[Posts].[OwnerUserId] as [p].[OwnerUserId]. It can also be an expression that the outer input computes, such as[Expr1002].[@p1]and(62). Functions of them, such asLikeRangeStart([@a]), are the same case.The rule fires when at least one branch reads another input. When a branch has no values to read, the rule keeps the warning, as before.
The issue suggested a check for column names only. That check loses a real warning. An OR join on expressions of the outer columns (
ON t.A = o.X + 1 OR t.A = o.Y + 1) produces[Expr1002]and[Expr1003], and no column names. So the new check counts every name that is not a parameter or a variable as another input.Checked on SQL Server 2022
I captured actual plans for nine query shapes on a local SQL Server 2022 instance (16.0.4255.1). Then I ran
planview analyzeon each plan, before and after the change.WHERE t.A IN (@p1, @p2), the query in the issueWHERE t.A IN (@a, @b)with local variablesWHERE t.A IN (@p1, ABS(@p2))WHERE t.V LIKE @a OR t.V LIKE @bON t.A = o.X OR t.A = o.YON t.A = o.X OR t.A = @pON t.A = o.X + 1 OR t.A = o.Y + 1The LIKE query still gets an Expensive Operator warning, before and after. That warning comes from rule 35, which this change does not touch.
Tests
in_list_dynamic_seek_plan.sqlplanis the actual plan from the issue. It gets no Join OR Clause warning.planview analyzenow reports no warnings on it.join_or_mixed_parameter_plan.sqlplanandjoin_or_expression_plan.sqlplanare real OR joins from SQL Server 2022. Each one keeps its warning.ReadsAnotherInputcover parameters, variables, functions of parameters and a user-defined function. They also cover outer columns,[Expr]names, a table variable column, and a bracket inside a string literal.Allin place ofAnyWarningBaseline.txtchanges only by the sections for the three new fixtures. No existing fixture gains or loses a warning.ComparisonBaseline.txtadds 8 pairings, and each one includes a new fixture. It removes 2 pairings, which now go through a new fixture in name order. The text of every other pairing is unchanged.Not in this PR
In
ComparisonBaseline.txt, the statement text of the new fixture is(10 int, 20 int)SELECT .... That is a different bug. Parameter substitution puts values into the declaration list at the start of a cachedsp_executesqlstatement. A separate PR fixes it and updates these lines.🤖 Generated with Claude Code
https://claude.ai/code/session_017ZVrq8tpA2DBPEFEqahFK6