Why does Recursive CTE estimate just 1 row?
Cardinality estimation for recursive common table expressions is extremely limited.
Under the original cardinality estimation model, the estimate is a simple sum of the cardinality estimates for the anchor and recursive parts. This is equivalent to assuming the recursive part is executed exactly once.
In SQL Server 2014, with the new cardinality estimation model enabled, the logic is slightly changed to assume three executions of the recursive part, with the same number of rows returned by each iteration.
Both of these are uneducated guesses, so it is no surprise that the use of recursive CTes usually results in poor quality estimations. More generally, estimating the result of a recursive process is nigh-on impossible, so the optimizer doesn't even try. This is not changed by using a particularly simple recursive structure, clearly intended to produce sequential numbers - the optimizer has no logic to detect this pattern.
In your particular case, the final estimation is one because the optimizer makes further guesses about the selectivity of predicates like [Recr1007]<(100)
(in the Filter at Node ID 3) and ([Recr1003]*(100)+[Recr1007])<=(10000)
(the residual predicate on the Nested Loops Join at Node ID 2). Again, these are guesses, and the results are unfortunate, though not surprising.
Is there anyway to improve the estimate while keeping the CTE approach?
Not that I am aware of.
Has there ever been a request for a "with (AssumeMinRows=N)" hint?
Not directly in those terms. There have been plenty of requests for materializing a CTE, which would help if such materialization came with automatic statistics generation. I won't list the others because you seem to have commented on most of those suggestions already :)
There have also been suggestions for selectivity hints, but nothing like this has made its way into the product yet.
As noted in comments on the question, your best bet right now is to use a real numbers table instead of generating one on-the-fly using a recursive CTE. A second option is to use manual materialization - a temporary table - as I am sure you are aware.
Best Answer
This cardinality estimation (CE) issue surfaces when:
Note: The particular calculator used to determine the selectivity is not important.
Details
The CE computes the selectivity of the outer join as the sum of:
The only difference between an outer and inner join is that an outer join also returns rows that do not match on the join predicate. The anti join provides exactly this difference. Cardinality estimation for inner and anti join is easier than for outer join directly.
The join selectivity estimation process is very straightforward:
SPT
of the pass-through predicate is assessed.IsFalseOrNull
component.1 - SPT
SPT
The anti join represents rows that will 'pass through' the join. The inner join represents rows that will not 'pass through'. Note that 'pass through' means rows that flow through the join without running the inner side at all. To emphasise: all rows will be returned by the join, the distinction is between rows that run the inner side of the join before emerging, and those that do not.
Clearly, adding
1 - SPT
toSPT
should always give a total selectivity of 1, meaning all rows are returned by the join, as expected.Indeed, the above calculation works exactly as described for all values of
SPT
except 1.When
SPT
= 1, both inner join and anti join selectivities are estimated to be zero, resulting in a cardinality estimate (for the join as a whole) of one row. As far as I can tell, this is unintentional, and should be reported as a bug.A related issue
This bug is more likely to manifest than one might think, due to a separate CE limitation. This arises when the
CASE
expression uses anEXISTS
clause (as is common). For example the following modified query from the question does not encounter the unexpected cardinality estimate:Introducing a trivial
EXISTS
does cause the issue to surface:Using
EXISTS
introduces a semi join (highlighted) to the execution plan:The estimate for the semi join is fine. The problem is that the CE treats the associated probe column as a simple projection, with a fixed selectivity of 1:
This automatically meets one of the conditions required for this CE issue to manifest, regardless of the contents of the
EXISTS
clause.For important background information, see Subqueries in
CASE
Expressions by Craig Freedman.