Your query:
DECLARE @Id INT = 18471
SELECT c.UserId FROM dbo.Comments AS c
INNER HASH JOIN dbo.Users AS u
ON c.UserId = u.Id
AND c.UserId = @Id
has c.UserId = u.Id
and AND c.UserId = @Id
.
By implication, that means @Id = u.Id
so the query simplifies to:
DECLARE @Id INT = 18471
SELECT c.UserId
FROM dbo.Comments AS c
INNER HASH JOIN dbo.Users AS u
ON @Id = u.Id
But now there's no equality join predicate between the two joined tables. It's effectively:
DECLARE @Id INT = 18471
SELECT c.UserId
FROM dbo.Comments AS c
INNER HASH JOIN dbo.Users AS u
WHERE @Id = u.Id
Which is then logically:
DECLARE @Id INT = 18471
SELECT c.UserId
FROM dbo.Comments AS c
CROSS JOIN dbo.Users AS u
WHERE @Id = u.Id
A hash join requires an equality join predicate. Consequently, it can't be employed to execute this query specification.
So far, this is all a variation of what Craig Freedman wrote in Implied Predicates and Query Hints back in 2009.
Since then, someone at Microsoft realised that a compilation failure in this case was confusing to users and counterproductive in general. So, it was fixed in one or more Cumulative Updates.
These days, if a compilation failure would result from this simplification, it is not applied. This is one of the rare cases where a join hint is ignored. Unfortunately, the fix was only developed for literal values like 18471, not variables or parameters*.
The exception being when OPTION (RECOMPILE)
is present. This allows the parameter embedding optimization, where the runtime value of the variable or parameter is injected into the query as part of a local recompilation that does not cache a new plan or replace any existing one. With a literal now visible to the optimizer, the fix kicks in and compilation succeeds.
An odd consequence is that you can't get an estimated plan with a variable or parameter and OPTION (RECOMPILE)
, but the plan runs successfully when you hit "go". The parameter embedding optimization is only functional at runtime, not when generating an estimated plan.
* This is similar to the situation with the SelOnSeqPrj
rule (doc, original problem described by me), which only worked with literals for many years. Eventually, they were bribed or threatened sufficiently to do the extra work to encompass variables and parameters. This might eventually happen for this example as well.