4.4.7 · D3Databases

Worked examples — Subqueries — correlated vs uncorrelated

3,965 words18 min readBack to topic

This is the "cover every case" workshop for the parent topic. Before we solve anything, we lay out every kind of situation a subquery question can throw at you. Then each worked example is stamped with the exact cell it covers, so by the end no scenario is left dark.

If a word here feels unexplained, it was built in the parent note — but we re-anchor the key ones as we go.


The scenario matrix

Think of a subquery question as a point on a grid. Two big axes decide almost everything:

  • Axis 1 — does the inner query borrow a column from the outer row? No = uncorrelated (runs once). Yes = correlated (runs conceptually once per outer row).
  • Axis 2 — what SHAPE does the inner query return? A single number (scalar), a single column of many values (list, tested with IN/ANY/ALL — quantifier operators we fully define in Cell K), or just a yes/no existence flag (boolean via EXISTS).

On top of those two axes sit the nasty edge cases that break naive answers: empty results, NULL values, and degenerate inputs (a table with zero rows, a group of size one). Here is the full board.

Cell Correlation Inner shape The trap it tests
A Uncorrelated Scalar Global constant threshold
B Uncorrelated List (IN) Membership test
C Uncorrelated List with a NULL inside NOT IN silently returns nothing
D Correlated Scalar aggregate Per-row threshold
E Correlated Boolean (EXISTS) Short-circuit existence
F Correlated Boolean (NOT EXISTS) "Has none" / anti-join
G Degenerate Empty inner result AVG of nothing = NULL, comparison collapses
H Degenerate Group of size 1 Row is its own average → strict > fails
I Real-world word problem Correlated scalar Reading English into SQL
J Exam twist Correlated Rewrite as a [[Joins — inner outer cross
K Uncorrelated List with > ALL / > ANY Quantified comparison over a whole list

The eleven worked examples below (A through K) hit every cell of this matrix.

Before the examples, meet the data pictorially — the red "correlation" arrows in the figure are the whole point of this chapter, so keep the picture in your head.

Figure s01 — what it draws (readable without the image): on the left, three blue boxes stack the customers Kim(id 1), Lee(id 2), Mo(id 3). On the right, three green boxes stack the orders 101, 102, 103, each tagged with the customer it belongs to (101→cust 1, 102→cust 1, 103→cust 3). A red arrow is drawn from each order back to its owning customer whenever orders.customer_id = customer.id. So Kim receives two red arrows (orders 101 and 102), Mo receives one (order 103), and Lee receives none. That missing arrow on Lee is the single most important thing in the picture: every correlated example (D, E, F, J) walks along these arrows, and the anti-join (Cell F) is literally "find the customer with no arrow" — Lee.

Figure — Subqueries — correlated vs uncorrelated

Cell A — Uncorrelated scalar (global threshold)

Figure s02 — what it draws (readable without the image): two side-by-side bar charts of the five salaries (Ana 40k, Bo 60k, Cy 55k, Di 55k, Ed 90k), bars coloured by department (dept 10 blue, dept 20 yellow, dept 30 green). The left panel overlays one red dashed horizontal line at 60000 — the single global average of Cell A; only Ed's bar pokes above it. The right panel instead draws one short red line per department at that department's own average (dept 10 at 50000, dept 20 at 55000, dept 30 at 90000) — the per-group thresholds of Cell D; here only Bo's bar clears its own department's line. Left = "one shared line" (uncorrelated), right = "one line per group" (correlated).

Figure — Subqueries — correlated vs uncorrelated

Cell B — Uncorrelated list with IN


Cell C — The NOT IN + NULL trap


Cell D — Correlated scalar (per-row threshold)


Cell E — Correlated boolean EXISTS


Cell F — Correlated NOT EXISTS (the NULL-safe anti-join)


Cell G — Degenerate: empty inner result


Cell H — Degenerate: group of size one


Cell I — Real-world word problem


Cell J — Exam twist: rewrite the correlated subquery as a JOIN


Cell K — Uncorrelated list with > ALL / > ANY


Recall Quick self-test on the matrix

Which cell does "employees earning more than the company average" hit? ::: Cell A — uncorrelated scalar, global constant threshold. Which two cells return {Kim, Mo}? ::: Cell B (IN) and Cell E (EXISTS) — same set, different mechanism. Why did NOT IN (Cell C) return zero customers instead of {Lee}? ::: A NULL in the list makes id <> NULL UNKNOWN, so the whole AND-chain is UNKNOWN and every row is filtered out. What does AVG of an empty group return, and what does comparing to it do? ::: NULL; any comparison to NULL is UNKNOWN, so the WHERE keeps zero rows (Cell G). Why must the JOIN rewrite (Cell J) use DISTINCT? ::: A customer with several orders produces several join rows; DISTINCT collapses them to match the one-row-per-customer semantics of EXISTS. What does > ALL(list) reduce to, and > ANY(list)? ::: > ALL = greater than the MAX of the list; > ANY = greater than the MIN of the list (Cell K).


Connections

  • Subqueries — correlated vs uncorrelated (index 4.4.7) — the parent this workshop drills into.
  • Joins — inner outer cross — Cell J's semi-join rewrite.
  • Aggregate Functions GROUP BY — Cells A, D, G, H lean on AVG/COUNT.
  • EXISTS vs IN vs ANY — Cells C, E, F, K contrast these operators.
  • NULL handling in SQL — the three-valued logic behind Cells C and G.
  • Query Optimizer Execution Plans — why Cell J's rewrite matters for speed.