Chapter 14 · Indexes and Query Execution

Sargability, Statistics, and Cardinality Estimation

An available index is useful only when the optimizer can map a predicate to its ordered keys and estimate the result size credibly. Sargability and statistics connect SQL text to plan quality.

Intermediate135–165 minutesPredicate rewrites + estimation reasoningLast reviewed: August 2026

Learning outcomes

Make predicates searchable and estimates credible

01

Define sargability as the optimizer’s ability to map a predicate to an ordered search argument.

02

Rewrite function-wrapped, cast-heavy, and pattern predicates into index-friendly forms.

03

Use expression indexes when the transformation is a stable part of the workload.

04

Explain how selectivity and cardinality estimates influence plan choice.

05

Refresh and inspect SQLite statistics with ANALYZE and reason about correlated columns.

Sargability connects SQL to index order

A predicate is commonly called sargable when it exposes a search argument that an index can navigate. The database needs a relationship such as indexed_key = constant, a bounded range, or a compatible prefix—not an opaque transformation that must be computed for every row.

SQL predicate
Normalize expressions
Match operators to index keys
Estimate qualifying range
Choose access path

A syntactically valid predicate can still hide the ordered key from the optimizer.

Date filtering: half-open ranges beat wrapped columns

sqlite · index the stored value
CREATE INDEX idx_order_dateON sales_order (ordered_at);
sqlite · non-sargable month extraction
SELECT COUNT(*)FROM sales_orderWHERE strftime('%Y-%m', ordered_at) = '2025-06';
sqlite · sargable half-open interval
SELECT COUNT(*)FROM sales_orderWHERE ordered_at >= '2025-06-01'  AND ordered_at <  '2025-07-01';

The half-open range preserves the raw indexed value and handles timestamps throughout the final day without fragile “23:59:59” boundaries.

Use an expression index when the expression is the contract

sqlite · indexed normalization
CREATE INDEX idx_customer_email_lowerON customer (lower(email));SELECT customer_id, emailFROM customerWHERE lower(email) = lower('Customer0417@Example.com');

Expression indexes are appropriate when the transformation is deterministic, frequent, and standardized. They are not a reason to index every ad hoc function users might write.

Pattern matching and prefixes

Predicate shapeTypical B-tree opportunity
email = 'customer0417@example.com'Exact seek.
email >= 'customer04' AND email < 'customer05'Explicit prefix range.
email LIKE 'customer04%'May use a compatible text index depending on collation and LIKE settings.
email LIKE '%0417%'Leading wildcard usually prevents a narrow ordered seek.
lower(email) = ...Needs a matching expression index or normalized stored column.
sqlite · deterministic prefix range
CREATE INDEX idx_customer_emailON customer (email);SELECT customer_id, emailFROM customerWHERE email >= 'customer04'  AND email <  'customer05'ORDER BY email;

Implicit conversions and type alignment

text · predicate review checklist
Column type: INTEGERParameter type: integer, not textColumn expression: unchanged when possibleCollation: matches indexed collationTimezone boundary: converted once before queryPattern: anchored unless substring search is intentional

Database products differ in coercion rules, but the safe principle is stable: compare compatible types and collations. Convert the parameter once rather than wrapping the indexed column for every row.

Cardinality is estimated row count

If a relation has N rows and the optimizer estimates predicate selectivity s, the estimated output cardinality is:

\[ \widehat{|R_P|} = N \cdot \widehat{\operatorname{sel}(P)} \]

Plan decisions depend on this estimate: nested loop versus hash join, index lookup versus scan, sort memory, parallelism, and join order.

Estimate inputWhat it approximates
Row count and page countBase relation size.
Distinct-value countEquality selectivity.
Most-common valuesSkewed hot values that differ from uniform assumptions.
Histogram boundariesRange selectivity.
Null fractionIS NULL and general distribution.
Extended or multicolumn statisticsDependencies and correlations between columns.

Refresh SQLite statistics

sqlite · collect and inspect planner statistics
CREATE INDEX IF NOT EXISTS idx_order_customer_dateON sales_order (customer_id, ordered_at);CREATE INDEX idx_order_status_dateON sales_order (status, ordered_at);ANALYZE;SELECT tbl, idx, statFROM sqlite_stat1WHERE tbl IN ('customer', 'sales_order')ORDER BY tbl, idx;

ANALYZE records statistics that help SQLite compare candidate plans. Statistics should represent production-like data; a tiny empty test database teaches the optimizer very little about the real workload.

Correlation breaks naive independence

Suppose region and channel are correlated. A simple estimator may multiply selectivities:

\[ \widehat{\operatorname{sel}(A\land B)} \approx \widehat{\operatorname{sel}(A)} \times \widehat{\operatorname{sel}(B)} \]

If mobile orders are concentrated in one region, the true joint selectivity can differ substantially. PostgreSQL extended statistics can model dependencies and multivariate distributions; SQLite’s statistics model is simpler, so schema, indexes, and query shape deserve careful testing.

Prove the rewrite with plans

sqlite · compare wrapped and range predicates
EXPLAIN QUERY PLANSELECT order_idFROM sales_orderWHERE strftime('%Y-%m', ordered_at) = '2025-06';EXPLAIN QUERY PLANSELECT order_idFROM sales_orderWHERE ordered_at >= '2025-06-01'  AND ordered_at <  '2025-07-01';

The second form should expose a bounded range on idx_order_date. The first can use an expression index only if one was explicitly created for the same expression.

Checkpoint

Repair the predicate

  1. Why is strftime applied to every stored timestamp often less index-friendly than a date range?
  2. When is an expression index justified?
  3. Why does a leading wildcard usually defeat a narrow B-tree seek?
  4. What is the relationship between selectivity and estimated cardinality?
  5. How can correlated columns cause a poor plan even when single-column statistics are accurate?
Review the answers

The wrapped form hides the raw ordered key, while a range directly bounds it. An expression index is justified for a deterministic, repeated transformation. A leading wildcard provides no known starting prefix. Estimated cardinality equals base rows times estimated selectivity. Multiplying independent single-column estimates can be badly wrong when values co-occur non-independently.

Summary and references

  • Sargable predicates expose ordered keys and compatible operators.
  • Half-open ranges are robust for timestamp intervals.
  • Expression indexes support standardized deterministic transformations.
  • Statistics drive selectivity and cardinality estimates.
  • Validate rewrites with plans and representative data.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.