Skip to content
Foundations

Subqueries, and when they are secretly a loop

A subquery that mentions the outer query runs once per outer row. One that does not runs once. The difference does not show in the SQL.

12 min · runs against the shop dataset

A scalar subquery returns one value. No rows makes it null; more than one row makes it an error, which is the right outcome — a query that asked a question with two answers should say so rather than silently pick one.

run it

biggestnumeric(10,2)missingnumeric(10,2)
1894.19null

1 row · 0 examined · 0 pages read

Correlated, and what it costs

A subquery that references a column from the enclosing query cannot be computed once, because its answer depends on which outer row is asking. So it runs again for every one of them. That is an N+1, written in one statement instead of a loop, and it is invisible in the SQL — you can only see it in the plan.

The uncorrelated version costs one execution however many rows probe it. Same shape, same length, entirely different cost.

EXISTS asks a question that always has an answer

EXISTS is the one test in SQL that never returns unknown: either a row was found or it was not, whatever nulls it contained. That is why NOT EXISTS is the safe way to write “none of these” and NOT IN is not.

run it

nametext
alan23
alan24
leslie
leslie22
margaret21

5 rows · 24 examined · 1 page read

Most correlated subqueries can be written as joins, and most of the time the planner will treat them identically. Write whichever reads more clearly — and then look at the plan, because “the planner will handle it” is a belief, and the plan is a fact.

Did it land?

  • Above the average

    Return the id and total of every order worth more than the average order, largest first, then by id.

  • Nothing shipped yet

    Return the names of every customer who has no shipped order — including customers with no orders at all. Order by name.