Optimizers typically estimate the combined selectivity of two AND-ed predicates by multiplying their individual selectivities. Explain the assumption behind that, describe a realistic case where it fails badly, and how you would address it.
answer
- sel(A AND B) = sel(A) × sel(B) — independence
- Per-column stats can't hold joint distributions
- city/state, model/make, postcode/region
- Correlated ANDs → under-estimate → fragile plans
- Fix: extended/multi-column statistics first
basics
~20 sIt assumes the columns are statistically independent. When they are correlated — a city implies its state, a model implies its manufacturer — multiplying selectivities counts the same restriction twice and severely under-estimates rows. Fix with multi-column statistics, a materialized combined column, or a schema change that removes the redundancy.
solid answer
~60 sSingle-column statistics give a selectivity per predicate. To combine them the optimizer assumes **independence**: `sel(A AND B) = sel(A) × sel(B)`. Independence is what makes per-column statistics sufficient — storing joint distributions for every column pair is combinatorially impossible. It fails whenever columns carry redundant information. `city = 'Springfield' AND state = 'IL'`: the state predicate removes almost nothing once the city is fixed, but the optimizer multiplies by state's selectivity anyway and under-estimates by roughly the correlation factor. Same shape for `model` and `manufacturer`, `postal_code` and `region`, `product` and `category`. The consequence is characteristic: a tiny estimate makes a nested loop or a small hash table look ideal; the actual row count is orders of magnitude higher and the plan collapses. Remedies, best first: create **multi-column / extended statistics** on the correlated set so the optimizer records their joint distinct count and dependency; **remove the redundancy** in the schema, since a functional dependency like city→state is usually a normalization issue; **materialize a combined column** with its own statistics; and only as a last resort, pin the plan or hint it.
code
text · 9 linesaddresses: 10,000,000 rows
sel(city = 'Springfield') = 10,000 / 10,000,000 = 0.001
sel(state = 'IL') = 200,000 / 10,000,000 = 0.02
independence: 0.001 * 0.02 = 0.00002 -> estimated 200 rows
reality: city implies state -> actual ~9,800 rows
error ~ 49x, in the under-estimating (dangerous) directiongo deeper
Know that combined selectivity is estimated by multiplying, that this assumes the columns are unrelated, and that related columns break it.
Work a numeric example, state that the error direction is under-estimation, and name multi-column/extended statistics as the standard remedy.
Diagnose it from an executed plan, distinguish it from stale statistics by the fact that refreshing does not help, and rank the remedies from extended statistics through schema change to plan pinning.
Discuss it as a structural limit of per-column statistics, weigh declaring statistics per correlated set against normalizing the dependency away, and decide how much plan robustness the workload should buy versus plan optimality.
## Why independence is assumed at all Statistics are stored per column: row count, distinct-value count, null fraction, a histogram, a most-common-value list. From those, the optimizer can estimate any single predicate. But a query with several predicates needs the *joint* distribution, and storing joint distributions is infeasible — a table with 30 columns has 435 pairs, and higher-order combinations explode from there. So optimizers make the only tractable default assumption: the columns are statistically independent, and `sel(A AND B) = sel(A) × sel(B)` with the analogous inclusion–exclusion form for OR. This is not laziness; it is the one assumption that makes per-column statistics sufficient. It happens to be right often enough — genuinely unrelated attributes such as an account's balance and its country really are close to independent. ## The failure mode It breaks whenever the predicates restrict overlapping information. Take a 10-million-row address table: - `city = 'Springfield'` matches 10,000 rows → selectivity 0.001 - `state = 'IL'` matches 200,000 rows → selectivity 0.02 Independence gives combined selectivity `0.001 × 0.02 = 0.00002` → an estimate of **200 rows**. But the vast majority of Springfield rows in this data are in Illinois, so the true count is close to 10,000 — a **50× under-estimate**. The optimizer double-counted a restriction that had already been applied: knowing the city almost determines the state. The general rule: when a **functional dependency** or a strong statistical association exists between columns, independence under-estimates, and the error scales with the strength of the association and the number of correlated predicates. Three correlated predicates can under-estimate by several orders of magnitude. The same trap appears across joins. If a filter on one table correlates with the join key distribution, join cardinality estimates inherit the error and it compounds upward. Note the direction: correlated AND-ed predicates almost always *under*-estimate, which is the dangerous direction. Under-estimation selects fragile plans — a nested loop whose inner side gets probed once per outer row, an index seek plus row lookups where a scan was right, a hash table sized far too small. Those are exactly the plans that degrade catastrophically rather than gracefully when the real row count shows up. ## Recognizing it in the wild Signals: - An executed plan where a base-table access or a filter shows estimated rows in the tens or hundreds and actual rows in the hundreds of thousands, with **multiple predicates on the same table**. - The estimate is approximately the product of what each predicate alone would give — a quick sanity check that the independence assumption is the culprit rather than stale statistics. - Predicate column names that hint at a hierarchy: city/state/country, product/category, model/make, postcode/region, service/datacenter. - Refreshing statistics does not help, because each individual column's statistics were already accurate. This is the tell that distinguishes correlation from staleness. ## Remedies in order of preference **1. Multi-column / extended statistics.** All major engines offer a way to record joint information about a column set: functional dependency degree, joint distinct counts, and in some cases multivariate MCV lists. Creating them on the correlated set lets the optimizer stop multiplying and use the recorded joint behaviour. This is targeted, cheap, and does not change the query or the schema — the default first move. The costs are extra maintenance on statistics collection and the fact that you must know which column sets to declare, which is why it is reactive rather than automatic. **2. Remove the redundancy.** A hard functional dependency like city→state usually means the schema stores derived data. Normalizing it away — the city row carries the state, and queries join — eliminates both the estimation problem and a class of data-integrity bugs. Not always the right call for a denormalized read path, but worth naming, because the estimation problem is a symptom of the modeling choice. **3. Materialize the combination.** A generated/computed column holding the combined key, indexed and analyzed, gives the optimizer a single column with accurate statistics for the predicate that matters. Useful when the correlated predicates always appear together. **4. Reshape the query.** Sometimes the redundant predicate can simply be dropped — if city already implies state, the state predicate adds nothing but estimation error. This requires the dependency to be a real invariant, not merely usual. **5. Robustness over optimality.** Where the workload cannot tolerate a plan flip, hints, plan baselines, or forced plans pin a known-good plan. Treat this as a containment measure with an expiry: it freezes behaviour, including freezing out better plans as the data changes. **6. Execution-time adaptivity.** Some engines can re-plan or switch operators when observed cardinalities diverge from estimates, or feed observed counts back for the next compile. Where available, this addresses the general problem rather than the one query. ## What a strong answer includes Name the assumption, explain *why* it exists (per-column statistics cannot represent joint distributions), give a concrete correlated example with numbers, state that the error direction is under-estimation and that under-estimation selects fragile plans, and offer multi-column statistics as the first tool while acknowledging that the deeper issue is often a functional dependency stored in the schema.
- You refresh statistics on both columns and the estimate does not improve. What does that tell you?That the individual column statistics were already accurate and the error lives in how they are combined — the independence assumption — rather than in staleness. Each predicate alone would be estimated correctly; only their conjunction is wrong. That is the signal to create multi-column or extended statistics on the correlated set, or to remove the redundancy in the schema, instead of tuning statistics targets further.
- Why don't optimizers just collect statistics on every column combination automatically?Because the number of combinations is combinatorial — a 30-column table has 435 pairs and vastly more higher-order sets — so collecting and maintaining them all would be prohibitively expensive in both analyze time and storage. Engines therefore default to independence and let you declare the specific column sets that matter, or infer a limited number from observed workload. Some systems narrow the gap with execution feedback, using observed cardinalities from previous runs instead of pre-computed joint statistics.
Estimating how many people are both left-handed and wear glasses by multiplying the two rates is fine — but doing the same for 'lives in Springfield' and 'lives in Illinois' double-counts, because the first fact already told you the second.
saying these in an interview costs you the question
- Claiming correlated AND-ed predicates cause over-estimation; the characteristic error is under-estimation
- Assuming a statistics refresh fixes correlation error, when per-column statistics were already correct
- Believing engines store joint distributions for all column pairs by default
- Treating it purely as a tuning problem and never questioning a schema that stores a functional dependency
- Reaching for a hint or a pinned plan before trying multi-column statistics