All 40 lessons
Lesson 21 of 40 · Subqueries & CTEs
SQL Subqueries
By Skillancy Editorial Team
Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.
A subquery is a query inside another query. The inner query runs first and hands its result to the outer query. Subqueries let you answer questions in steps, like "which products cost more than the average product?" when you don't know the average in advance.
Where subqueries can go
| Type | Returns | Typical place | Example use |
|---|---|---|---|
| Scalar | one value | WHERE, SELECT | compare to the average |
| List | one column, many rows | WHERE ... IN (...) | customers who bought X |
| Table (derived table) | rows and columns | FROM | aggregate, then filter or join |
Syntax
SELECT ...
FROM table_name
WHERE column > (SELECT AGGREGATE(column) FROM table_name);Examples
1. Scalar subquery in WHERE
Products priced above the average price:
SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price DESC;Try it in the SQL CompilerResult:
| product_name | price |
|---|---|
| Samsung 43" Smart TV | 31990.00 |
| Redmi Note 13 Pro | 23999.00 |
| Women's Silk Saree | 8499.00 |
(3 rows)
The subquery returns one number (the average, about ₹5,465), and the outer query compares every row to it.
2. Scalar subquery in SELECT
Show how each product's price compares with the average:
SELECT product_name,
price,
ROUND((SELECT AVG(price) FROM products), 0) AS avg_price,
ROUND(price - (SELECT AVG(price) FROM products), 0) AS diff_from_avg
FROM products
ORDER BY diff_from_avg DESC
LIMIT 4;Try it in the SQL CompilerResult:
| product_name | price | avg_price | diff_from_avg |
|---|---|---|---|
| Samsung 43" Smart TV | 31990.00 | 5465 | 26525 |
| Redmi Note 13 Pro | 23999.00 | 5465 | 18534 |
| Women's Silk Saree | 8499.00 | 5465 | 3034 |
| Cricket Bat (Kashmir Willow) | 3499.00 | 5465 | -1966 |
(4 rows)
3. List subquery with IN
Customers who have bought something from the Electronics category:
SELECT customer_name, city
FROM customers
WHERE customer_id IN (
SELECT o.customer_id
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
WHERE p.category_id = 1
)
ORDER BY customer_name;Try it in the SQL CompilerResult:
| customer_name | city |
|---|---|
| Aarav Sharma | Bengaluru |
| Arjun Patel | Ahmedabad |
| Farhan Khan | Lucknow |
| Karan Mehta | Mumbai |
| Rohan Gupta | Delhi |
(5 rows)
Using IN instead of a join means each customer appears once, however many Electronics items they bought.
4. NOT IN
Customers who have never placed an order:
SELECT customer_name
FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders);Try it in the SQL CompilerResult:
| customer_name |
|---|
| Divya Menon |
(1 row)
This is safe here because orders.customer_id never contains NULL. If it might, use NOT EXISTS instead.
5. Subquery in FROM (derived table)
Aggregate first, then work with the result like a table. Average order value per customer, keeping only customers above ₹10,000:
SELECT customer_id, orders, ROUND(avg_order_value, 2) AS avg_order_value
FROM (
SELECT o.customer_id,
COUNT(DISTINCT o.order_id) AS orders,
SUM(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100)) / COUNT(DISTINCT o.order_id) AS avg_order_value
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.status = 'Delivered'
GROUP BY o.customer_id
) AS customer_stats
WHERE avg_order_value > 10000
ORDER BY avg_order_value DESC;Try it in the SQL CompilerResult:
| customer_id | orders | avg_order_value |
|---|---|---|
| 11 | 1 | 24098.05 |
| 3 | 3 | 11061.17 |
(2 rows)
6. Which product is the most expensive?
The question aggregate functions alone couldn't answer in lesson 13:
SELECT product_name, price
FROM products
WHERE price = (SELECT MAX(price) FROM products);Try it in the SQL CompilerResult:
| product_name | price |
|---|---|
| Samsung 43" Smart TV | 31990.00 |
(1 row)
If two products tied for the top price, both would be returned, unlike ORDER BY price DESC LIMIT 1.
Common mistakes
A scalar subquery that returns more than one row:
SELECT product_name
FROM products
WHERE price > (SELECT price FROM products WHERE category_id = 1);Try it in the SQL CompilerError: more than one row returned by a subquery used as an expression
Use an aggregate (MAX, AVG) to get one value, or > ALL (...) / > ANY (...).
- NOT IN with possible NULLs in the subquery, which returns no rows.
- Deeply nested subqueries. Three levels deep is hard to read. Rewrite with CTEs.
Practice question
List orders whose total quantity of items is greater than the average quantity per order line.
Show solution
SELECT order_id, SUM(quantity) AS total_qty
FROM order_items
GROUP BY order_id
HAVING SUM(quantity) > (SELECT AVG(quantity) FROM order_items)
ORDER BY total_qty DESC
LIMIT 6;Try it in the SQL CompilerResult:
| order_id | total_qty |
|---|---|
| 1019 | 5 |
| 1005 | 5 |
| 1003 | 4 |
| 1023 | 4 |
| 1021 | 3 |
| 1016 | 3 |
(6 rows)
Key takeaways
- A subquery is a query in brackets inside another query.
- Scalar subqueries return one value;
INsubqueries return a list;FROMsubqueries return a table. - Derived tables let you aggregate first and filter or join afterwards.
- For readability, prefer CTEs once queries get long.
Frequently asked questions
What is a subquery in SQL?
A subquery is a SELECT statement inside another query, written in brackets. Its result is used by the outer query as a single value, a list of values or a temporary table.
Subquery or JOIN: which is faster?
Modern optimizers often turn simple IN subqueries into joins, so performance is usually similar. Choose whichever is clearer, and check EXPLAIN if a query is slow.
Does a subquery in FROM need an alias?
Yes, in PostgreSQL (before version 16), MySQL and SQL Server a derived table must have an alias, for example FROM (SELECT ...) AS t.
Keep learning SQL
One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Prefer a one-page summary? Download the free SQL cheat sheet (PDF)