Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Subqueries

IN (SELECT ...) and the scalar subquery are supported as sugar over machinery that already existed — each is rewritten at parse time into the join or constant it always denoted, so nothing here adds a new execution path:

-- Planned as a SEMI JOIN against the (anonymous) subquery binding.
SELECT * FROM 'facts.csv' WHERE x IN (SELECT k FROM 't.csv');

-- NOT IN plans as an ANTI JOIN with SQL's NULL rule — see below.
SELECT * FROM 'facts.csv' WHERE x NOT IN (SELECT k FROM 't.csv');

-- A scalar subquery runs ONCE, before the outer query, and its single cell is
-- spliced in as a constant — so the comparison pushes down to the source like
-- any literal. This is the incremental-extraction idiom:
SELECT * FROM conn.orders WHERE ts > (SELECT max(ts) FROM 'lake/orders.parquet');

-- The same thing, named — useful when several statements share the value:
LET hi = (SELECT max(ts) FROM 'lake/orders.parquet');
SELECT * FROM conn.orders WHERE ts > $hi;

The subquery of IN must produce exactly one named column, and the test must sit as a top-level AND condition of WHERE — under an OR, a NOT or a CASE it cannot be lifted into a join without changing what the predicate means, so those shapes are parse errors (spell them as an explicit join). The left side must be a plain column. The literal-list form IN (1, 2, 3) is unchanged.

A scalar query — inline or as LET x = (SELECT ...); — must produce exactly one column; one row is the value, zero rows read as NULL (standard SQL), and more than one row is an error. The inline form is evaluated per statement, not per row: it may not reference columns of the outer query.

NOT IN is standard three-valued NOT IN. A single NULL from the subquery makes x NOT IN (...) unknown for every row, so no row survives; a NULL x survives only an empty subquery. To keep the rows that match no non-null key — what an extraction script usually means — say so in the subquery: WHERE k IS NOT NULL.

EXISTS has no spelling: the uncorrelated form is degenerate (all rows or none) and the useful form is correlated. A correlated subquery — one referencing a column of the outer query — has no equivalent here and is not planned: it needs either decorrelation in an optimiser or per-row execution of the inner query, and the second is fatal for a streaming engine. Rewrite it as a join — the equality correlation EXISTS usually carries is exactly IN on that key.