Skip to content

Milestone 23: uncorrelated subqueries, folded to a constant - #34

Merged
YheChen merged 1 commit into
masterfrom
subqueries
Jul 31, 2026
Merged

Milestone 23: uncorrelated subqueries, folded to a constant#34
YheChen merged 1 commit into
masterfrom
subqueries

Conversation

@YheChen

@YheChen YheChen commented Jul 31, 2026

Copy link
Copy Markdown
Owner
SELECT id FROM orders WHERE total > (SELECT AVG(total) FROM orders);

The parser has refused (SELECT …) for twenty-two milestones. It no longer does,
for the shape where the answer is simple and the simplicity is not a shortcut.

An uncorrelated subquery is a constant

It names nothing outside itself, so it depends on no row of the query around it
and has one value for the whole statement however many rows that statement
scans. It is run once and substituted before binding:

WHERE total > (SELECT AVG(total) FROM orders)
WHERE total > 200

That is not an optimisation of a more general mechanism; for this shape it is the
entire semantics. Doing it before binding is what makes everything downstream
work unchanged: the planner, the index matcher and the cost model were all
written against column <op> literal, and after folding that is exactly what
this is. total = (SELECT MAX(total) FROM orders) uses the index on total,
which it could not if the subquery survived into the plan — checked by planning
it, not by asserting it.

Four refusals, each a decision

  • More than one row is an error. No answer is defensible, and taking the
    first would make the query depend on physical order — the kind of wrong that
    looks right until a VACUUM moves a page.
  • More than one column is an error, and SELECT * counts as more even when
    the table has one column today, so an ALTER TABLE cannot turn a working
    query into a wrong one.
  • Zero rows is NULL, not an error. x = NULL is UNKNOWN for every row, which
    is the answer. An error here would mean a reasonable query failing because a
    table happened to be empty.
  • A correlated subquery is refused by name. It would otherwise have been
    refused by accident: o.city does not resolve inside a subquery over
    orders i, so the binder would have said "no column named 'city'" about a
    column that plainly exists. Correlation is detected first, by comparing the
    subquery's qualifiers against the outer aliases, and MAX(orders.total) inside
    a query over orders is correctly not correlated.

Folding happens in both _execute_select and _execute_explain. EXPLAIN does
not go through the first and must show the plan the query really gets, which
means EXPLAIN runs the subquery: you cannot plan around a constant you have not
computed.

Verification

  • Thirteen hand-written cases, five about what is refused.
  • The differential generator emits a scalar subquery in 59 of the 1,024
    CI-seed queries, using COUNT(*) and MIN/MAX over an INTEGER column —
    the forms where both engines agree on the result's type as well as its value.
  • make ci green. 1,714 Python tests, 160 frontend.

🤖 Generated with Claude Code

    SELECT id FROM orders WHERE total > (SELECT AVG(total) FROM orders);

The parser has refused `(SELECT …)` for twenty-two milestones. It no
longer does, for the shape where the answer is simple and the simplicity
is not a shortcut.

An uncorrelated subquery names nothing outside itself, so it depends on
no row of the query around it and has one value for the whole statement
however many rows that statement scans. It is run once and substituted
BEFORE binding, which is what makes everything downstream work
unchanged: the planner, the index matcher and the cost model were all
written against `column <op> literal`, and after folding that is exactly
what this is. `total = (SELECT MAX(total) FROM orders)` uses the index on
total, which it could not if the subquery survived into the plan.

Four refusals, each a decision rather than a gap:

- more than one row, because no answer is defensible and taking the first
  makes the query depend on physical order
- more than one column, with SELECT * counting as more even when the
  table has one today, so an ALTER TABLE cannot turn a working query
  into a wrong one
- zero rows is NULL rather than an error, because `x = NULL` is UNKNOWN
  for every row and that is the answer
- a correlated subquery is refused BY NAME. It would otherwise have been
  refused by accident, as "no column named city" for a column that
  plainly exists, and that is an hour of somebody's life

Folding happens in both _execute_select and _execute_explain. EXPLAIN
does not go through the first, and must show the plan the query really
gets, which means EXPLAIN runs the subquery: you cannot plan around a
constant you have not computed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chen-db Ready Ready Preview Jul 31, 2026 8:20am

@YheChen
YheChen merged commit 3b96dff into master Jul 31, 2026
6 checks passed
@YheChen
YheChen deleted the subqueries branch July 31, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant