What is a nested query in SQL?
A nested query is a SELECT placed inside another SQL statement to produce a scalar value, a set of values, an existence test, or a derived relation for the outer query. “Nested query” and “subquery” are commonly used interchangeably. Correctness depends on the inner query's shape, cardinality, NULL behavior, and relationship to the outer row.
SELECT employee_id, salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
);The inner aggregate returns one value, so scalar comparison is valid. A different nested query might return many values and require IN, EXISTS, or a join. Start by stating the expected inner result shape; most nested-query bugs are shape mismatches disguised as syntax.
Distinguish scalar, set, correlated, and derived-table queries
A scalar subquery must return at most one row and one column. A set subquery returns one column with zero or more rows for membership or quantified comparison. A correlated subquery references columns from the current outer row. A derived table in FROM returns a relation with named columns and may feed joins, filters, or aggregation.
| Shape | Contract | Typical use |
|---|---|---|
| Scalar | 0–1 row, one column | Threshold or lookup |
| Set | Many values | Membership |
| Correlated | Depends on outer row | Existence or row-specific test |
| Derived relation | Named rows and columns | Staged transformation |
The same business question can often be expressed in more than one shape. Choose the clearest semantic contract first, then inspect how the database optimizes it. Shorter syntax is not automatically safer or faster.
Make every inner and outer reference unambiguous
Nested scopes make unqualified column names dangerous. An inner query may resolve a name from its own tables when the author intended an outer reference, or capture an outer column when a local one was expected. Give every relation a concise alias and qualify columns at correlation boundaries. This turns hidden dependencies into visible review points.
SELECT c.customer_id
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.status = 'open'
);Review each outer reference as an input parameter to the inner query. Ask whether the correlation key is complete, especially for tenant, effective-date, or versioned data. An incomplete key can match a valid row from the wrong tenant or period while appearing syntactically perfect.
Prove a scalar subquery cannot return multiple rows
A scalar subquery that unexpectedly returns two rows fails at runtime in many engines. Do not hide the problem with an arbitrary MIN, MAX, or row limit. Establish why one row is correct: a unique key, an approved winner rule, an aggregate over the intended population, or deterministic ranking followed by a single-row filter.
Zero rows normally become NULL in scalar context, which may propagate through arithmetic or comparisons. Define whether “not found” means unknown, zero, false, or an error. Preserve a found/not-found indicator when downstream consumers need to distinguish an absent lookup from a stored NULL.
Treat NOT IN and NULL as a semantic hazard
NOT IN can surprise when the subquery returns NULL. Because SQL uses three-valued logic, comparison against a list containing an unknown value may become UNKNOWN for every candidate, yielding no rows. If the intended question is “no matching row exists,” NOT EXISTS with an explicit correlation predicate usually states the requirement more safely.
SELECT c.customer_id
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM blocked_customers b
WHERE b.customer_id = c.customer_id
);Still inspect nullable correlation keys and business rules. A NULL customer ID may represent bad data, an anonymous entity, or a valid unknown state. Choosing NOT EXISTS fixes a logical construct; it does not decide how unidentified records should be governed.
Measure repeated work instead of assuming row-by-row execution
A correlated query is logically evaluated for each outer row, but optimizers may transform it into a semi join, anti join, aggregate join, or another set-based plan. Conversely, a seemingly simple expression may remain a repeated nested-loop probe. Inspect the actual execution plan and runtime counters rather than declaring all correlation slow.
Warning signals include a large outer input, repeated scans of a costly inner subtree, parameter-sensitive selectivity, and correlation predicates without suitable access paths. Capture actual versus estimated rows, execution count per operator, logical reads, elapsed time, and representative parameter values. That evidence identifies the mechanism a rewrite must remove.
Unfold opaque nesting into testable relational stages
Useful rewrites include replacing repeated scalar lookups with one joined lookup, pre-aggregating detail before joining to a parent, converting existence checks to semi-join forms, or extracting a deeply nested derived table into named CTE stages. Each stage should declare its grain and key, not merely receive a descriptive name.
WITH order_totals AS (
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
WHERE status = 'paid'
GROUP BY customer_id
)
SELECT c.customer_id, t.total_amount
FROM customers c
LEFT JOIN order_totals t
ON t.customer_id = c.customer_id;A rewrite can change duplicate behavior, NULL propagation, or the preservation of unmatched parents. Treat it as a new implementation of the same contract, not cosmetic cleanup. Compare results before accepting performance gains.
Use CTEs for clarity, not as a universal performance switch
A CTE can expose stage boundaries, remove repeated expressions, and let reviewers inspect grain transitions. It is not inherently faster. Engines differ in whether a CTE is inlined, materialized, reused, or optimized independently, and options can change by version. A derived table may produce the same plan.
Choose the form that makes the logical contract easiest to review, then measure. If an intermediate result is reused and expensive, verify whether the engine actually reuses it. If materialization helps, consider a temporary table with explicit indexes and statistics—but include write cost, concurrency, and cleanup in the comparison.
Prove a nested-query rewrite preserves results
Run old-minus-new and new-minus-old comparisons using the engine's set-difference operator where available, but remember that set difference may hide duplicate multiplicity. Also compare row counts at the intended grain, duplicate-key distributions, null counts, aggregates, and hashes over stable ordered outputs. Investigate every difference before performance testing.
- Test zero, one, and multiple rows from each scalar candidate.
- Insert NULL into set subqueries and test positive and negative membership.
- Use duplicate parent and child keys to expose fan-out.
- Test unmatched parents when changing to joins.
- Verify tenant, date, and version components in every correlation key.
- Compare operator execution counts and actual rows.
- Benchmark representative small, large, and skewed populations.
- Preserve fixtures and expected outputs as regression evidence.
Diagnose a customer exclusion query that returns nothing
Suppose eligible customers are filtered with customer_id NOT IN (SELECT customer_id FROM blocked_customers). One blocked record has a NULL customer ID, so the predicate becomes UNKNOWN for every candidate and the result is empty. Preserve that row as evidence, confirm how unidentified blocked records should be handled, and rewrite the intended anti-match with NOT EXISTS.
Then compare old and new behavior for null outer keys, duplicate blocked rows, and cross-tenant identifiers. Add a data-quality monitor for null blocklist keys. The final fix contains both a logically correct query and a governance rule for invalid identifiers.
Inspect how the optimizer transforms nested logic
Look for semi joins, anti joins, nested loops, spools or materialization, repeated scans, aggregate placement, and estimate errors. Record whether the inner subtree runs once or once per outer row. A good plan for one parameter may degrade for another, so include representative selectivity and data skew.
Avoid selecting a rewrite solely because its estimated cost is lower. Use actual runtime metrics under comparable conditions, confirm result equivalence, and observe memory, reads, spills, and concurrency. Optimization is accepted only after meaning and operational behavior both improve or remain within agreed bounds.
Inspect the complete nested query before refactoring
Paste a sanitized complete statement into the InfiniSynapse SQL Complexity Checker to review nested depth, correlation, joins, aggregates, filters, windows, and repeated structures together. Then use your database's actual plan and result fixtures to validate the recommended changes.
Open SQL Complexity CheckerRemove credentials, secrets, personal data, and sensitive literals.Nested query SQL frequently asked questions
Usually yes; both describe a query expression used inside another statement.
It fails when the inner query returns more than the one row its context allows.
A NULL in the returned set can make comparisons UNKNOWN and eliminate every row.
No. They improve structure, while performance depends on engine and plan behavior.
Compare both difference directions, duplicates, NULLs, aggregates, edge cases, and runtime evidence.
Official nested-query documentation
Keep a nested query when it states the contract clearly
Nesting is not a defect by itself. A short uncorrelated scalar aggregate can make a comparison threshold obvious. EXISTS can express an existence requirement more directly than joining, deduplicating, and projecting a relation whose columns are never used. A compact derived table can isolate one aggregate grain without creating unnecessary global names.
Keep the form when its shape is obvious, the scope is explicit, the plan is acceptable, and an extracted stage would add ceremony without improving validation. Refactor when the same logic is repeated, cardinality is hidden, correlation is hard to see, intermediate outputs need testing, or plan evidence shows repeated work. The decision should follow review and runtime needs rather than a blanket style rule.
Review every nesting boundary as a data contract
For each inner SELECT, write its output columns, maximum row count, grain, nullable fields, outer dependencies, and expected execution frequency. Then identify the consuming operator and ask what happens for zero rows, one row, multiple rows, duplicates, and NULL. This small worksheet converts a visually deep query into a sequence of falsifiable claims.
Preserve the worksheet with the pull request or analytical review. When schemas, uniqueness constraints, or engine versions change, it identifies exactly which assumptions require renewed testing. That documentation is especially valuable for business-critical exclusions, pricing lookups, and effective-dated logic.
