Lesson 16
How a join runs: nested loop, hash join, merge join
For readers who finished lesson 15 and can read an EXPLAIN plan.
- Nested loop
- Hash join
- Merge join
- Making the planner choose another
- 4 exercises
Every query runs on real PostgreSQL, in your browser. It downloads once, about 6 MB, then starts in a few seconds.
For a reference page on the same statements, read PostgreSQL JOINs Explained with Examples.
The idea
A nested loop looks up the matches of each row
A nested loop takes the rows of one side one by one and, for each, finds its matches on the other side. It wins when one side has few rows, ideally found through an index.
Try it
One customer and their orders: a nested loop
explain select * from customers c join orders o on o.customer_id = c.customer_id where c.customer_id = 7;
Nested Loop. The primary key's index finds customer 7, one row, so orders is read only once for her matches.
The idea
A hash join builds a lookup table from one side
A hash join reads the smaller side once into a hash table, keyed by the join column, and then reads the bigger side once, looking each row up. It wins when both sides are whole tables without a useful order.
Try it
Every customer with every order: a hash join
explain select * from customers c join orders o on o.customer_id = c.customer_id;
Hash Join. The Hash step under it builds the table from customers, the smaller side; orders is scanned once against it.
The idea
A merge join walks two sorted lists side by side
A merge join reads both sides in the order of the join column and pairs them in one pass. It wins when both sides come sorted already, as indexes deliver them, and when the answer has to be sorted anyway.
Try it
Orders and their lines, sorted: a merge join
explain select * from orders o join order_items oi on oi.order_id = o.order_id order by o.order_id;
Merge Join over two index scans: orders_pkey and order_items_pkey both deliver the rows sorted by order_id.
Try it
Switch a method off, and the planner picks another
set enable_hashjoin = off tells PostgreSQL to avoid hash joins, for the session. It is a way to compare plans, not a setting for real work. Each example below sets it back with reset all.
set enable_hashjoin = off; explain select * from customers c join orders o on o.customer_id = c.customer_id; reset all;
The same join as on slide 5, now a Merge Join: customers comes sorted from its index, and orders is sorted first.
Try it
Switch off the merge join too
set enable_hashjoin = off; set enable_mergejoin = off; explain select * from customers c join orders o on o.customer_id = c.customer_id; reset all;
A Nested Loop: each order looks its customer up in customers_pkey. The Memoize step remembers the customers it has already looked up.
In DbSchema
See the plan of a query as a tree

The SQL Editor of DbSchema can show the plan of a query as a tree, each step with its table and its cost, for a database you connect to.
The Community Edition is free. Every download starts with a 15-day trial of all features.
Exercise 1 of 4
Which join method does EXPLAIN show for all customers with all their orders?
explain select * from customers c join orders o on o.customer_id = c.customer_id;
Each answer sends the exercise and whether the answer was right to dbschema.com, without your IP address.
Exercise 2 of 4
And for the orders of one customer, customer 7?
explain select * from customers c join orders o on o.customer_id = c.customer_id where c.customer_id = 7;
Each answer sends the exercise and whether the answer was right to dbschema.com, without your IP address.
Exercise 3 of 4
And for orders joined to order_items, sorted by order_id?
explain select * from orders o join order_items oi on oi.order_id = o.order_id order by o.order_id;
Each answer sends the exercise and whether the answer was right to dbschema.com, without your IP address.
Exercise 4 of 4
Why does the join for one customer use a nested loop?
Each answer sends the exercise and whether the answer was right to dbschema.com, without your IP address.