Introduction to Subqueries
A subquery (sometimes called an inner query or nested query) is a complete SQL query written inside another SQL query. The outer query, often called the outer query or main query, uses the result produced by the subquery to do its own work — filter rows, compute a value, or act as a source table to select from.
Think of a subquery as a way to answer a smaller question first, and then feed that answer into a bigger question. For example, "which products cost more than the average product price" is really two questions chained together: first, "what is the average product price" (the subquery), and then, "which products are above that number" (the outer query). SQL lets you write both in a single statement instead of running two separate queries and gluing the results together in application code.
-- The inner query computes one number: the average price -- The outer query uses that number to filter products SELECT product_name, price FROM products WHERE price > ( SELECT AVG(price) FROM products );
Where a Subquery Can Appear
Subqueries are not restricted to one spot in a statement — SQL allows them almost anywhere a value, a row, or a table is expected. The clause it sits in changes what shape of result it is allowed to return.
Location | Also Called | What It Returns | Example Use |
|---|---|---|---|
In | Scalar subquery | A single value (one row, one column) | Show each row alongside a company-wide average |
In | Derived table / inline view | A full result set treated as a table | Pre-aggregate data, then filter or join the result |
In | Filtering subquery | A single value, a list of values, or a set of rows | Keep only rows matching a computed condition |
Standalone with | Existence check | Not a value at all — just whether any row exists | Test for the presence of related rows |
Why Use a Subquery at All?
You could often get the same answer with a JOIN, a temporary table, or two separate queries run from your application. Subqueries earn their place because they let you express multi-step logic — "first compute this, then use it here" — in a single, self-contained SQL statement. That has real benefits.
Readability for step-by-step logic — the query visually mirrors how you'd explain the problem out loud: "find customers who did X, where X is defined by this smaller query."
No round trips — the database computes the intermediate result internally, instead of your application code running one query, reading the result, and building a second query.
Atomicity — the whole computation happens in one query execution against a single, consistent snapshot of the data.
Encapsulation — a subquery used as a derived table can pre-shape or pre-aggregate data before the outer query ever touches it, keeping the outer query simpler.
A Quick Look at Each Form
-- 1. Scalar subquery in SELECT — one value per outer row SELECT product_name, price, (SELECT AVG(price) FROM products) AS avg_price FROM products; -- 2. Derived table in FROM — a subquery acting as a table SELECT department, avg_salary FROM ( SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department ) AS dept_averages WHERE avg_salary > 60000; -- 3. Filtering subquery in WHERE SELECT customer_id, name FROM customers WHERE customer_id IN ( SELECT customer_id FROM orders WHERE total > 1000 ); -- 4. Existence check with EXISTS SELECT customer_id, name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id );
Each of these forms is covered in depth on its own page — scalar subqueries, subqueries in WHERE, derived tables in FROM, correlated subqueries, and EXISTS all have their own rules, gotchas, and performance trade-offs worth understanding individually.