skip to content

Does writing SELECT 1 instead of SELECT * inside an EXISTS subquery make it faster?

level: middleimportance: nice to knowfreq 32%

answer

  1. the predicate asks a yes/no question
  2. nothing consumes the subquery's columns
  3. the manuals say the list is disregarded
  4. it must still parse, just not evaluate
  5. a style choice wearing a performance costume

basics

~10 s

No. EXISTS tests only whether the subquery produces a row, so its select list is not evaluated for values. SELECT 1, SELECT * and SELECT NULL all behave identically; the choice is purely stylistic.

solid answer

~40 s

`EXISTS` is a predicate over row existence: it is true if the subquery returns at least one row, whatever that row contains. The subquery's select list is therefore never evaluated for its values, and PostgreSQL and MySQL both document that it is disregarded. `SELECT 1`, `SELECT *` and `SELECT NULL` produce identical plans and identical results. The list still has to *parse and resolve*, so an unknown column name is an error even though its value is never used — that is the only way it can matter. This is worth knowing precisely because it is the one place the usual `SELECT *` advice does not apply: the star inside `EXISTS` is idiomatic and costs nothing, and a candidate who "optimises" it is applying a rule without understanding why the rule exists.

go deeper

for a junior

Recall that EXISTS only asks whether a row exists, so what the subquery selects has no effect on the answer or the speed.

for a middle

Explain that the select list is not evaluated for values but is still name-resolved, and that the real cost of an EXISTS lives in the correlation predicate and its index support.

for a senior

Use this to show calibration: recognise when a general rule does not apply, and be sceptical of before-and-after timings that a warm cache or a re-plan could explain.

for a principal

Frame conventions accordingly — a standard can be adopted for readability without being justified with a false performance claim, and mislabelled rationale is what makes rules survive past their usefulness.

## What EXISTS asks for `EXISTS (subquery)` is a predicate, not a value expression. It evaluates to true when the subquery would produce at least one row and false when it would produce none. It never inspects the contents of that row — there is no operation defined on the subquery's output other than "is it empty". Consequently the subquery's select list carries no information into the enclosing query. ```sql -- these three are the same question SELECT c.id FROM customers c WHERE EXISTS (SELECT * FROM orders o WHERE o.customer_id = c.id); SELECT c.id FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); SELECT c.id FROM customers c WHERE EXISTS (SELECT NULL FROM orders o WHERE o.customer_id = c.id); ``` PostgreSQL's and MySQL's manuals both state explicitly that what appears in the `SELECT` list of an `EXISTS` subquery does not matter. The plans are the same; the results are the same. The engine stops as soon as existence is decided, and it was never going to materialise those columns in the first place. ## Why the myth is so persistent Two reasons. First, the general advice "never write `SELECT *`" is taught as an unconditional rule, and people apply rules where they were never needed. Second, it feels like it *ought* to matter — the star means "all columns" everywhere else, so it looks like a request for work. The mistake is treating the subquery as if it produced a result set the outer query consumes; it does not, it produces a truth value. This is a good calibration question in an interview. Someone who says "always use `SELECT 1`, it's faster" is repeating folklore. Someone who says "it makes no difference; the list is ignored, though I write `1` because it signals intent" understands both the semantics and why the convention exists anyway. ## The one way the list still matters The select list must still be a legal, resolvable expression list. This errors, even though no value is ever needed: ```sql -- error: no such column, caught at resolution time SELECT c.id FROM customers c WHERE EXISTS (SELECT o.no_such_column FROM orders o WHERE o.customer_id = c.id); ``` So the subquery is not exempt from name resolution — only from evaluation. That distinction is worth stating explicitly, because it is the precise boundary between "ignored" and "absent". A second, smaller practical note: `SELECT *` inside `EXISTS` is convenient because it never has to be maintained. There is no column name in it to become wrong when the table changes, which is exactly the opposite of the situation in a stored view or an `INSERT ... SELECT`. ## What actually determines the cost Since the projection is free, everything that decides whether an `EXISTS` subquery is cheap or expensive lives elsewhere: the correlation predicate, whether an index serves it, and how many outer rows the predicate is evaluated for. If an `EXISTS` is slow, the projection is the last place to look — and "I changed `*` to `1` and it got faster" is almost always a measurement artefact such as a warm cache or a re-planned statement. ## Style, stated honestly Many teams standardise on `SELECT 1` purely for readability: it signals to a human reader that no columns are wanted and pre-empts the reviewer who would otherwise flag the star out of habit. That is a fine reason. It is just not a performance reason, and presenting it as one is the error the question is probing for.

  • Does the same reasoning apply to NOT EXISTS?
    Yes. `NOT EXISTS` is the same existence test with the result negated, so the subquery's select list is equally irrelevant. The interesting differences between `NOT EXISTS` and alternatives such as `NOT IN` are about null handling and join shape, not about what the subquery projects.
  • Is there any way the EXISTS subquery's select list can affect the statement?
    Only through resolution, not evaluation. The list must be valid SQL naming resolvable columns, so referring to a column that does not exist raises an error even though its value would never be read. Beyond that, the contents are disregarded and cannot change the result or the plan.

saying these in an interview costs you the question

  • Claims SELECT 1 is measurably faster than SELECT *
  • Thinks EXISTS materializes the subquery's columns
  • Says the star forces a full scan inside the subquery
  • Assumes an unknown column name is ignored along with the value
  • Applies the no-SELECT-* rule without asking whether it applies

context