SQL theory · page 8 of 10

How PostgreSQL plans a query

Statistics, scans, join methods and sargable conditions.

SQL says what you want, not how to get it. For each query, PostgreSQL's planner considers the ways it could run, estimates the cost of each, and picks the cheapest. EXPLAIN shows the chosen plan; EXPLAIN ANALYZE runs it and adds what really happened.

Statistics and estimates

The planner's estimates come from statistics about each column: how many distinct values, the most common ones, and their spread. ANALYZE refreshes them (autovacuum does it in the background). The key estimate is selectivity: the share of rows a condition keeps. A condition that keeps few rows favours an index; one that keeps most rows favours reading the whole table.

Reading a table

ScanHowCheap when
Sequential scanreads every page of the table in ordera large share of rows is wanted
Index scanwalks the index, fetches each rowfew rows are wanted
Bitmap scancollects matches from the index, then reads their pages in ordera moderate share
Index-only scananswers from the index alonethe index holds every column needed

Joining tables

Join methodHowGood for
Nested loopfor each row on one side, look up matches on the othera small outer side and an index on the inner
Hash joinbuild a hash table of the smaller side, probe it with the largerlarge, unsorted inputs joined on equality
Merge joinwalk two inputs sorted on the join key side by sideinputs already sorted, for example by an index

Why a B-tree is fast

A B-tree keeps keys sorted in pages of a few hundred entries each, in levels. Each level narrows the search by a factor of a few hundred, so the number of pages read grows with the logarithm of the table's size:

\text{levels} \approx \log_{b} n \quad\text{e.g. } \log_{300} 200{,}000 \approx 2.1

Sargable conditions

An index can serve a condition only on the column as it is stored. WHERE loss_date >= '2025-01-01' can use an index on loss_date; WHERE EXTRACT(YEAR FROM loss_date) = 2025 cannot, because the index is not sorted by year. Rewrite conditions as ranges on the column (such conditions are called sargable), or index the expression itself.

EXPLAIN
SELECT *
FROM claims
WHERE claim_id = 42;
An index scan on the primary key: one row out of 1,534. Try it in the playground
Measure, do not guess: compare EXPLAIN ANALYZE before and after a change, on realistic data.
Practise it on Day 8

Check your understanding

Answer every question, then check.

1. What does EXPLAIN ANALYZE add to EXPLAIN?

2. A condition keeps 90% of a table's rows. Which scan is likely cheapest?

3. Why can't an index on loss_date serve WHERE EXTRACT(YEAR FROM loss_date) = 2025?

An unhandled error has occurred. Reload 🗙

Reconnecting…

Your connection to Data Analytics SQL Lab dropped, probably a Wi-Fi blip. Hold on while it reconnects. Your work is kept.

The connection could not be restored. The page will reload in a few seconds.