Subquery in SQL: Single-Row, Correlated, and Nested Queries (with Examples)
Master subquery in sql: understand nested queries across SELECT, FROM, and WHERE clauses, correlated vs non-correlated subqueries, and performance fixes.
Writing a subquery in sql is one of the most powerful techniques in relational database programming. When business stakeholders ask questions that require multiple steps of aggregation—such as "Which customers placed orders larger than the average order value?" or "Which employees earn more than the median salary of their department?"—a single flat query is insufficient. Subqueries allow you to calculate intermediate metrics on the fly and immediately feed them into outer filtering or projection logic.
However, subqueries can quickly become a performance bottleneck if constructed carelessly. Nested subqueries that run once per row (correlated subqueries) can degrade query speed from milliseconds to minutes across large tables, and subtle three-valued logic behaviors with NOT IN can return zero rows unexpectedly.
In this tutorial, you will learn the four clauses where a subquery in sql can be placed, contrast correlated versus non-correlated execution plans, master EXISTS vs IN, and discover when to refactor subqueries into Common Table Expressions (CTEs) or window functions.
For broader interview prep, explore our 30 SQL Interview Questions for Analysts and review our foundational SQL Cheat Sheet.
Monthly searches for SQL subqueries and nested query syntax
Over 65% of intermediate SQL interview screening questions test candidate understanding of subquery execution, correlated loops, and CTE equivalence.
Subquery in SQL: 4 Execution Locations
A subquery can be placed inside four distinct clauses of a SQL statement: WHERE, FROM, SELECT, and HAVING.
1. Subquery in the WHERE Clause (Filtering with Aggregates)
The most common use case: filtering individual rows against a dynamic aggregate calculated from the dataset.
-- Find orders with an amount greater than the overall company average
SELECT
order_id,
customer_id,
amount
FROM orders
WHERE amount > (
SELECT AVG(amount)
FROM orders
);The database executes the inner query (SELECT AVG(amount) FROM orders) first, obtains a single scalar number (e.g. 245.50), and substitutes it into the outer WHERE amount > 245.50.
2. Subquery in the FROM Clause (Derived Tables)
When placed in FROM, the subquery acts as a temporary table created on the fly.
-- Calculate the average number of orders placed per customer
SELECT
AVG(order_count) AS avg_orders_per_customer
FROM (
SELECT
customer_id,
COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
) AS customer_summaries;Mandatory Table Aliases for Derived Tables
Most relational engines (including PostgreSQL, MySQL, and SQL Server) throw a syntax error if you fail to give a derived table an alias. Always append an alias after the closing parenthesis: ) AS customer_summaries;.
3. Subquery in the SELECT Clause (Scalar Projections)
Retrieves a single scalar value per row to display alongside detail columns.
SELECT
employee_name,
salary,
(SELECT AVG(salary) FROM employees) AS company_avg_salary,
salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;While functional, calculating repeated scalar subqueries in SELECT is often better written using window functions: AVG(salary) OVER ().
4. Subquery in the HAVING Clause
Filters grouped results against an aggregated subquery threshold.
SELECT
department,
SUM(salary) AS dept_payroll
FROM employees
GROUP BY department
HAVING SUM(salary) > (
SELECT AVG(dept_total)
FROM (
SELECT SUM(salary) AS dept_total
FROM employees
GROUP BY department
) AS totals
);Types of Subqueries: Correlated vs Non-Correlated
Understanding the difference between correlated and non-correlated subqueries is a favorite topic in technical interviews.
-- Non-Correlated: Executes ONCE total
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- Correlated: Executes ONCE PER CANDIDATE ROW
SELECT e.employee_id, e.name, e.salary, e.department
FROM employees e
WHERE e.salary > (
SELECT AVG(sub.salary)
FROM employees sub
WHERE sub.department = e.department -- Reference to outer row!
);The Mechanics of a Correlated Subquery
In the correlated example above, the inner query references e.department from the outer table.
- The database picks row 1 of the outer table (say, Meera in
Engineering). - It runs the inner query substituting
Engineeringto find the average engineering salary. - If Meera's salary is higher, row 1 is kept.
- The engine advances to row 2 and repeats the entire calculation.
If the table contains 100,000 rows, an unindexed correlated subquery might execute 100,000 queries. In modern data warehouses, replacing correlated subqueries with window functions or pre-aggregated joins yields massive performance gains.
Subquery Operators: IN, ANY, ALL, and EXISTS
When an inner subquery returns multiple rows, simple scalar operators (=, >, <) will fail. You must use multi-row comparison operators:
| Operator | Syntax | Evaluates to TRUE if... |
|---|---|---|
IN | WHERE id IN (SELECT id ...) | Outer value matches at least one item in subquery list |
NOT IN | WHERE id NOT IN (SELECT id ...) | Outer value matches no item in list (Danger with NULLs) |
EXISTS | WHERE EXISTS (SELECT 1 ...) | Subquery returns at least one row (stops evaluating on first match) |
NOT EXISTS | WHERE NOT EXISTS (SELECT 1 ...) | Subquery returns 0 rows |
> ALL | WHERE salary > ALL (SELECT salary ...) | Outer value is greater than the maximum value in subquery |
> ANY | WHERE salary > ANY (SELECT salary ...) | Outer value is greater than at least one value in subquery |
IN vs EXISTS Example
-- Using IN:
SELECT customer_name
FROM customers
WHERE customer_id IN (
SELECT DISTINCT customer_id
FROM orders
WHERE order_date >= '2026-01-01'
);
-- Using EXISTS (Faster on large datasets with indexes):
SELECT c.customer_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= '2026-01-01'
);EXISTS does not need to return column data; standard convention is to write SELECT 1. The query engine halts scanning the orders table the moment it encounters the first matching order for customer c.
Common Pitfalls and How to Fix Them
1. Single-Row Subquery Returns Multiple Rows
-- FAILS with: ERROR: more than one row returned by a subquery used as an expression
SELECT * FROM products
WHERE category_id = (SELECT category_id FROM categories WHERE name LIKE 'A%');If two categories start with 'A', = fails.
- Fix: Either change
=toIN, or guarantee a single row withLIMIT 1.
2. The NOT IN with NULL Disaster
Consider this query to find customers who have never placed an order:
-- DANGEROUS!
SELECT customer_id, name
FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);If the orders table contains a single row where customer_id IS NULL, NOT IN evaluates to UNKNOWN for every single candidate row. The query returns 0 rows, even if hundreds of customers never ordered!
- Fix: Always use
NOT EXISTSor filter NULLs:
SELECT c.customer_id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);Subquery vs CTE vs Window Function
| Feature / Criteria |
|---|
To explore how CTEs compare in practice, read our comprehensive SQL CTE Guide.
When Analysts Use Subqueries in Real Work
Identifying Inactive or Churned Accounts. Using NOT EXISTS to find users who registered 90 days ago but have zero recorded sessions or purchases in event logs.
Outlier Detection. Filtering out fraudulent credit card transactions whose value exceeds three standard deviations from the user's historical average:
WHERE amount > (SELECT AVG(amount) + 3 * STDDEV(amount) FROM transactions WHERE user_id = t.user_id)Two-Stage Aggregations. Computing the maximum of departmental averages in workforce analytics reports.
To practice writing subqueries against real database schemas, solve interactive challenges on our SQL Practice Platform.
Subquery in SQL: Performance Benchmarking vs Joins and Window Functions
Database query optimizers handle subqueries differently depending on whether they are correlated or non-correlated:
- Un-nesting and Decorrelation: Modern optimizers (PostgreSQL, MySQL 8+, Oracle) attempt to rewrite independent
INsubqueries into semijoins or hash joins automatically. - Correlated Subquery Bottlenecks: A correlated subquery in the
SELECTorWHEREclause that forces an $O(N \times M)$ nested-loop scan across 1,000,000 records will lock CPU threads. Rewriting the calculation into an innerLEFT JOINon a pre-aggregated subquery or using window functions (ROW_NUMBER(),SUM() OVER (...)) reduces runtime from 45 seconds to 300 milliseconds. - Defensive NULL Handling with NOT IN: Never write
WHERE id NOT IN (SELECT parent_id FROM t)ifparent_idcan contain a singleNULL. Three-valued logic causesNOT IN (..., NULL)to evaluate toUNKNOWN, returning zero rows. Always useNOT EXISTSinstead.
Discover more query patterns in our SQL Tutorials hub and practice on Topfolio Practice.
Related SQL Tutorials
- What Is SQL? The Complete Beginner to Pro Guide
- SQL Joins Explained with Practical Examples
- SQL Window Functions Guide
- SQL Cheat Sheet for Analysts
- Explore All Guides in the SQL Tutorials Hub
Master SQL Query Writing
Build confidence writing subqueries, CTEs, and window functions with interactive real-world datasets.
Start Free SQL CourseFrequently Asked Questions
What is a subquery in SQL?
A subquery in SQL is a SELECT query nested inside another SQL statement (such as an outer SELECT, INSERT, UPDATE, or DELETE). It is always enclosed in parentheses and provides intermediate results to the outer query.
What is the difference between a correlated and a non-correlated subquery?
A non-correlated subquery executes once independently, and its result is passed to the outer query. A correlated subquery references columns from the outer query, causing it to re-execute once for every candidate row processed by the outer query.
Why does NOT IN return no rows when a subquery contains NULL?
In SQL three-valued logic, if the subquery returns even a single NULL, NOT IN evaluates to UNKNOWN for all values, filtering out every row. Always filter out NULLs in the inner query or use NOT EXISTS instead.
Can a subquery be placed in the FROM clause?
Yes. A subquery in the FROM clause is called a 'derived table' or 'inline view'. In most SQL dialects like PostgreSQL and MySQL, every derived table must be assigned a table alias (for example: FROM (SELECT ...) AS sub).
When should I use a CTE instead of a subquery in SQL?
Use a Common Table Expression (WITH clause) when you have complex, multi-level nesting or need to reference the same intermediate dataset multiple times. CTEs improve readability and maintainability.

Written by
Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.
Related Articles
CREATE TABLE in MySQL: Syntax, Data Types & Constraints Guide
Master CREATE TABLE in MySQL with syntax examples, primary keys, foreign keys, AUTO_INCREMENT, constraints, and InnoDB engine best practices.
DDL SQL Commands: Complete Guide to Data Definition Language
Master DDL SQL commands: CREATE, ALTER, DROP, TRUNCATE, and RENAME with practical syntax, schema constraints, and DDL vs DML comparisons.
Delete Duplicate Records in SQL: 3 Proven Methods with Examples
Learn how to delete duplicate records in SQL using ROW_NUMBER() CTEs, self-joins with MIN/MAX IDs, and safe transaction workflows across dialects.