Nested-loop, hash, and merge joins
Choose a join algorithm from predicate support, input size, ordering, skew, and memory instead of ranking operator names universally.
On this page
The question for today
What you’ll understand
- Compare naïve and indexed nested-loop work.
- Separate hash-build, probe, output, and spill costs.
- Explain when sorted inputs let a merge join avoid an additional sort.
A logical join specifies matching rows; a physical join supplies the mechanism. Nested loops, hashing, and merging expose different opportunities and failure modes. Their practical comparison must include the number and width of input rows, available indexes or ordering, supported predicates, duplicate groups, and the output itself. An algorithm cannot avoid producing an enormous required answer merely by having a favorable input-size bound.
Nested loops can mean repeated scans or selective probes
A naïve nested loop considers each outer row against every inner row, evaluating the join condition on every pair. It supports broad predicate forms but can perform a product of input sizes. An indexed nested loop is a different physical strategy: an outer key drives a lookup into the inner relation, so only candidate matching rows need inspection. This can be excellent for a small outer input and a selective usable index. Index traversal, random access, repeated keys, visibility checks, and the number of returned matches all affect its actual cost.
A row-count underestimate is especially dangerous when it turns a supposedly tiny outer input into thousands of repeated expensive lookups. Conversely, replacing every nested loop with hashing can force a large build and delay useful output for a query needing only a few rows. Establish what the loop rescans and whether it has a parameterized access path. The phrase nested loop alone does not tell you whether the inner operation is a full table scan, a cached small relation, an index probe, or another complex subplan.
Hashing changes access, not the required multiplicity
For a supported equality condition, a hash join builds a table from one input and probes it with the other. Good hash behavior and an in-memory build can make expected input processing roughly linear, plus output work. Build size is a byte-and-overhead question, not merely a row-count comparison. A narrower larger relation can sometimes be cheaper to build than a smaller wide one. A hash collision requires an equality check, and nonmatching extra join conditions still need evaluation; hashing does not establish SQL equality by itself.
Skew creates two distinct risks. Many rows in one bucket can hurt lookup or partition balance, while a key repeated on both sides genuinely requires a product of matching rows. The second cost is unavoidable output, not just a bad hash function. If the build does not fit its available memory, partitioning and temporary storage can add substantial work. Null handling and outer-join bookkeeping also depend on the requested semantics. Use a complete plan to identify the actual build side and inspect observed batches or spill evidence before calling a hash join cheap.
Merge joins exploit compatible order
A merge join advances through inputs ordered compatibly with the join comparison, matching equal key groups. If the order is already available, it can avoid building a hash table or sorting those inputs again. If order must be created, sorting belongs in the comparison. Duplicates can require revisiting or retaining an equal-key group and still emit many pairs. Do not confuse linear movement through sorted keys with a promise of linear total output when a large matching group exists. Collation, comparison semantics, and supported merge conditions determine whether the order is usable.
Use the cache lab to inspect a narrow reuse effect relevant to repeated probes. Reset, set both capacity C=4 and stride s=4, predict twelve hits in the initial sixteen requests, then Play or Step. Separately Reset, set both C=4 and s=1, predict zero initial hits, then Play or Step. The first fixed modulo-sixteen trace cycles over four addresses; the second cycles over sixteen. This is an exact toy LRU trace, not a join, an index, or a processor benchmark. It illustrates why probe locality belongs in a cost argument without selecting the winning SQL algorithm.
Work it through
Separate candidate checks from input processing
An inner equality join has 1,000 outer rows and 5,000 inner rows. Each outer row matches exactly one inner row. Compare a naïve full-scan nested loop with a hypothetical in-memory hash join using one abstract unit per input row and one per emitted result.
The naïve loop checks 1,000 × 5,000 = 5,000,000 candidate pairs. Its 1,000 emitted rows are separate result work rather than a reduction of the candidate count.
The hash teaching model processes 5,000 build rows and 1,000 probe rows, totaling 6,000 input units. Add 1,000 result units for a modeled total of 7,000.
An indexed nested loop is not priced by the five-million-pair figure. If a suitable index exists, its own probe, candidate, and result costs must be modeled; the given numbers do not establish its measured latency or whether it beats the hash plan.
The calculation explains why full rescanning can be expensive while preserving room for a different indexed-loop strategy to win.
The living notebook
Explore the model
Interactive lab · cache
Probe reuse is one input to a join cost
Reset, set both C=4 and s=4, predict twelve hits among the initial sixteen modulo-sixteen requests, then Play or Step. Separately Reset, set both C=4 and s=1, predict zero initial hits, then Play or Step. This fixed fully associative LRU stream is neither a join nor a real index-cache trace.
- Hits / requests
- 0 / 16
- Hit rate
- 0%
- Address-stream period
- 16 addresses
A fully associative LRU cache starts empty and requests address (i·s) mod 16. Hits move an address to the newest position; misses evict the oldest when full. Playback starts with 16 requests and adds two per second, capped at 4096 explicitly simulated requests. This toy stream is not a processor-cache benchmark. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forRepeated access to a fitting set differs from a larger cycle, but the toy trace cannot choose a SQL join algorithm.
Pause & reason
Check your understanding
Your turn
Try it yourself
Two already compatibly sorted inputs contain 100 and 200 copies of the same join key. An inner equality join has no additional filter. How many result rows does this group require, and which cost does that expose?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Count matching pairs, not just distinct keys or input rows.
Compare with the solution
The group requires 100×200=20,000 rows. Even though merge processing can advance through sorted keys efficiently, emission and duplicate-group handling must account for those pairs. This is required multiplicity, not evidence that the inputs should have been deduplicated or that sorting was ineffective.
Keep the idea
Take it with you
- Inspect the actual inner access path before pricing a nested loop.
- Hash build memory, skew, and required output all matter.
- Merge joins need compatible order, with any sorting and duplicate-group work included.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.