liveBook · Manning

Execution plan

As explained in Just Use Postgres!

A query execution plan is the database engine's planned sequence of operations for executing a SQL query, optionally including runtime analysis.

An execution plan is the database engine’s planned sequence of operations for producing a query result. SQL describes the requested result, while PostgreSQL chooses how to perform it; the written query and executed operations can differ. Use EXPLAIN to inspect that choice before changing a query or creating an index. section 3.3.2 section 4.4

Read a plan by following its operations from the deepest scan or join toward the final result:

  • Identify access methods such as Seq Scan, Index Scan, Bitmap Index Scan, or Bitmap Heap Scan. section 4.4 section 4.6.1
  • Trace joins, filters, aggregation, sorting, uniqueness, and limiting to understand the execution flow. section 3.3.2 section 4.6.1
  • Use EXPLAIN ANALYZE to add actual times, row counts, loops, planning time, and execution time; use buffers when I/O details matter. section 4.4
  • Compare plans rather than assuming an index is faster: PostgreSQL may prefer a sequential scan when a predicate matches many rows. section 4.6.1 section 4.10

Compare a sequential scan with an index scan

For `SELECT count(*) FROM game.player_stats WHERE win_count > 100`, EXPLAIN reports an Aggregate over a Seq Scan because no index exists on `win_count`. A lookup by `player_id = 250` instead uses `Index Scan using player_stats_pkey`. EXPLAIN ANALYZE adds measured execution details, reporting one returned row and an execution time of 0.077 ms in the example. section 4.4

Inspecting a multi-CTE query

A least-popular-songs query defines `plays_cte` and `user_play_counts`, then ranks results. Its plan shows a Hash Join, GroupAggregate, Subquery Scan, Sort, and Limit. The separately defined `plays_cte` does not appear as its own plan node because PostgreSQL folds it into the later computation. section 3.3.2

Listing 3.3 Finding least-popular songs across three or more users
WITH plays_cte AS (
    SELECT s.title, s.duration, p.play_duration, p.user_id
    FROM streaming.plays p
    JOIN streaming.songs s ON p.song_id = s.id
    WHERE p.play_start_time::DATE BETWEEN '2024-09-15' AND '2024-09-16'
      AND p.play_duration < (s.duration / 2)
),
user_play_counts AS (
    SELECT title, duration, COUNT(DISTINCT user_id) AS user_count,
      MIN(play_duration) AS min_play_duration,
      COUNT(*) AS total_play_count
    FROM plays_cte
    GROUP BY title, duration
)
SELECT title, duration, min_play_duration, total_play_count
FROM user_play_counts
WHERE user_count >= 3
ORDER BY min_play_duration ASC
LIMIT 3;

Run the least-popular-songs query, then prepend `EXPLAIN (analyze, costs off, timing off)` to compare its readable SQL structure with PostgreSQL’s chosen operations. section 3.3.2

How Just Use Postgres! covers it

How deep it goes
Explains plan nodes, runtime metrics, scans, joins, aggregation, sorting, and planner tradeoffs without covering complete optimizer internals.
Examples
Demonstrates CTE ranking, player statistics, regional filtering, JSON expression indexes, and time-series aggregation plans.
What it assumes
Assumes familiarity with SQL queries and introduces EXPLAIN-based inspection without requiring prior plan-reading knowledge.
What you can do after
Diagnose query costs with PostgreSQL EXPLAIN and EXPLAIN ANALYZE, then evaluate whether indexes or query changes improve execution.
How it connects
Connects SQL queries with EXPLAIN, query planners, indexes, sequential scans, index scans, joins, aggregation, and sorting.

Also explained in

Compare how 4 books explain execution plan →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Just Use Postgres!. The section links point to the parts of the book it draws on.