- How is the row count estimated for a Nested Loops join in SQL Server?
No differently from a hash or merge join. The display in SSMS often looks different for nested loops because the inner side is per execution (when looking at a pre-execution (estimated) plan).
Fundamentally, you are asking how SQL Server estimates the selectivity of a join. The logical selectivity remains the same whatever physical implementation (nested loops, apply, hash, merge) is chosen.
The broad answer to that question is SQL Server may use one of several techniques based on histogram or frequency (distinct values) information for the join predicate column(s).
There isn't one single formula for either histogram- or frequency-based estimates. There are several modelling variations that may come into play depending on the predicates involved, the available information and database configuration.
The most important configuration factors are which cardinality estimation (CE) model you are using ('default' or 'legacy'), the database compatibility level setting, and whether you have query optimizer hotfixes enabled. That is not a complete list.
The question is exceptionally light on detail—not even providing the join predicates—so I'm not even going to speculate on a specific cause here.
- What factors could lead to such an inaccurate row estimate in this case?
Estimates are calculated based on statistical information using SQL Server's proprietary modelling assumptions and algorithms. Given representative statistics, the most frequent cause of inaccurate estimations is a disconnect between the data and SQL Server's modelling assumptions.
- Is there anything I can do to improve or correct the estimate?
First, ensure statistics are up to date and of good quality. Do not rely on default sampled statistics alone. You may need a higher sampling rate or even FULLSCAN
to provide accurate enough information. You may also need to create additional statistics or indexes, depending on the exact nature of the join.
Once that has been established, the main things you can try are:
Use a different cardinality estimation model
FORCE_DEFAULT_CARDINALITY_ESTIMATION
FORCE_LEGACY_CARDINALITY_ESTIMATION
Use documented model variations
ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS
ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES
ASSUME_FULL_INDEPENDENCE_FOR_FILTER_ESTIMATES
ASSUME_PARTIAL_CORRELATION_FOR_FILTER_ESTIMATES
Use a different optimizer compatibility level if the estimates used to be better
QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_n
The above options are all specified as a USE HINT
query hint.
Resources
If you're really interested in the underlying join selectivity process or how multiple predicates are combined: