skip to content

Subqueries in SELECT, FROM, and WHERE

Where a subquery may sit in a statement and what changes with the position: a computed column in SELECT, a derived table in FROM, or a filter in WHERE/HAVING. Interviewers use placement questions to test whether you know which clause imposes which shape and aliasing rules.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

3

Where can a subquery appear in a SELECT statement, and what does each position contribute?

level: juniorimportance: must knowfreq 70%

answer

  1. position decides what the subquery means
  2. one gives a value, one gives a table
  3. SELECT list, FROM, WHERE and HAVING
  4. the FROM one must be named

basics

~20 s

A subquery can sit in the SELECT list as one computed value per row, in FROM as a derived table that must be aliased, and in WHERE or HAVING as an operand of a predicate.

solid answer

~40 s

Three positions matter. In the **SELECT list** a subquery contributes one column and must yield a single value per output row, so it behaves like a computed expression. In the **FROM clause** it is a *derived table*: it produces a whole result set that the outer query treats like any other table, and it must be given a correlation name. In **WHERE** and **HAVING** it is an operand of a predicate — compared with `=` or `<` when it yields one value, or fed to `IN` or `EXISTS` when it yields a set of rows. `HAVING` is the position that lets you compare a group's aggregate against a value computed by another query. The position dictates the shape: a value, a table, or a predicate operand.

go deeper

for a junior

Be ready to name the three positions and say what each produces: a single value in the select list, a derived table in FROM, and a predicate operand in WHERE or HAVING.

for a middle

Explain what each position constrains — the single-value contract in the select list, the mandatory correlation name on a derived table, and why HAVING is the only filter position that can compare an aggregate against a subquery result.

for a senior

Show judgment about which position to choose when several would work, and recognise the shapes that stop being maintainable, such as several near-identical select-list subqueries or a filter buried three derived tables deep.

for a principal

Own the readability standard for the team: agree where computed values live so a reviewer can read a query top-down, and treat deeply nested FROM subqueries as a refactoring signal rather than a matter of personal style.

## What "subquery" means here A subquery is a complete `SELECT` statement enclosed in parentheses and written inside another statement; the enclosing statement is the *outer query*. There is no single rule for what a subquery may look like. The position you put it in decides what shape it must have, what it contributes to the result, and what names it can see. Interviewers ask about placement because it separates people who learned SQL as a grammar from people who learned it as a set of copied recipes. ## Position 1 — the SELECT list: a computed column In the select list a subquery stands where a column expression stands. It contributes exactly one column, and for each row the outer query emits it must produce at most one value: one column, at most one row. If it returns no row at all, the column is NULL for that row. ```sql SELECT c.name, (SELECT COUNT(*) FROM orders) AS orders_in_system FROM customers c; ``` Because it is an expression, it can go anywhere an expression can — inside a `CASE`, inside arithmetic, as a function argument. What it cannot do is widen the result: a select-list subquery never adds rows or columns beyond the one it occupies. ## Position 2 — the FROM clause: a derived table In `FROM`, a subquery stands where a table reference stands, and the result is called a *derived table* (some engines say *inline view*). It may return any number of rows and columns, and the outer query treats it exactly like a table: join it, filter it, group it, sort by it. ```sql SELECT big.customer_id, big.total FROM (SELECT customer_id, total FROM orders WHERE total > 100) AS big; ``` Two rules travel with this position. First, the derived table must be given a name — the standard requires a correlation name, and PostgreSQL and MySQL both reject the query without one. Second, it is scoped independently: a plain derived table cannot reference columns belonging to another item in the same `FROM` list, or to an enclosing query, unless the `LATERAL` keyword is written in front of it. ## Position 3 — WHERE and HAVING: a predicate operand In `WHERE` the subquery is one side of a predicate, and there are two families. A comparison predicate (`=`, `<`, `>=`) needs a single value on the subquery side. A membership or quantified predicate (`IN`, `EXISTS`, `ANY`, `ALL`) accepts a whole result set. ```sql SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products); ``` `HAVING` is the same idea moved past grouping. Because `HAVING` filters groups rather than rows, it is the only filter position where a subquery result can be compared against an aggregate of the group: ```sql SELECT customer_id, SUM(total) AS spend FROM orders GROUP BY customer_id HAVING SUM(total) > (SELECT AVG(total) FROM orders); ``` ## Other legal positions A scalar subquery is a value expression, so it is also permitted as an `ORDER BY` sort key and inside a `CASE` branch, though both are rare and usually less readable than computing the value once and naming it. Subqueries appear in `INSERT`, `UPDATE` and `DELETE` statements too, where the same positional logic applies: a single-value subquery on the right of an assignment, a set-producing subquery as the source of rows or as a filter. ## Choosing a position The practical rule of thumb: if the subquery produces one annotation value that belongs next to a column, the select list reads best. If it produces a set of rows the rest of the query needs to join, filter or sort on, put it in `FROM` and name it. If it only decides whether a row survives, put it in `WHERE` as a predicate operand and let the predicate do the work. ## What goes wrong The three classic failures map exactly onto the three positions. A select-list subquery that can return more than one row raises a runtime error the moment a second row appears. A derived table without an alias fails to parse on the strict engines. And a filter written in `WHERE` that needs an aggregate of the group belongs in `HAVING` instead — `WHERE` runs before grouping and has no group to talk about.

  • Can a subquery be used as a sort key in ORDER BY?
    A sort key is a value expression and a scalar subquery is a value expression, so it is permitted. In practice it is rare and hard to read: most authors compute the value in the select list or in a derived table and sort by that name instead. Verify support in your engine before relying on it.
  • Which of those positions require the subquery to return exactly one column?
    The select list and any comparison predicate such as `= (SELECT …)` need one column and at most one row. A derived table in `FROM` may return any number of columns and rows. `IN` takes one column but many rows, and `EXISTS` ignores the select list entirely, asking only whether any row came back.
  • Where does a filter go if it compares a group's aggregate to a value from another table?
    In `HAVING`. Something like `HAVING SUM(o.amount) > (SELECT AVG(total) FROM budgets)` compares each group's aggregate against a single value produced elsewhere. `WHERE` cannot express it: it filters individual rows before grouping happens, so no aggregate of the group exists yet for it to compare.

A subquery is like a clause in brackets in English: put it after a noun and it describes that noun, put it where the subject belongs and the whole sentence is about it. Same words, different grammatical role.

saying these in an interview costs you the question

  • Thinks subqueries are only allowed in the WHERE clause
  • Says a derived table in FROM needs no alias
  • Claims a select-list subquery may return several rows
  • Believes a derived table in FROM can see outer-query columns
  • Assumes every subquery can be swapped for a join unchanged

context

open as a page

Why must a derived table in the FROM clause be given an alias?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A derived table is a table reference in the FROM clause, and every table there needs a name so its columns can be qualified unambiguously. The standard requires that correlation name, and PostgreSQL and MySQL reject the query without it.

open as a page

Why prefer a derived table in FROM over several scalar subqueries in the SELECT list?

level: middleimportance: should knowfreq 45%

basics

~20 s

Each select-list subquery yields one column and is usable nowhere else in the statement. A derived table computes the group once, exposes several columns at once, and the outer query can filter, join and sort on them by name.

open as a page