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

TypeReturnsTypical placeExample use
Scalarone valueWHERE, SELECTcompare to the average
Listone column, many rowsWHERE ... IN (...)customers who bought X
Table (derived table)rows and columnsFROMaggregate, 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 Compiler

Result:

product_nameprice
Samsung 43" Smart TV31990.00
Redmi Note 13 Pro23999.00
Women's Silk Saree8499.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 Compiler

Result:

product_namepriceavg_pricediff_from_avg
Samsung 43" Smart TV31990.00546526525
Redmi Note 13 Pro23999.00546518534
Women's Silk Saree8499.0054653034
Cricket Bat (Kashmir Willow)3499.005465-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 Compiler

Result:

customer_namecity
Aarav SharmaBengaluru
Arjun PatelAhmedabad
Farhan KhanLucknow
Karan MehtaMumbai
Rohan GuptaDelhi

(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 Compiler

Result:

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 Compiler

Result:

customer_idordersavg_order_value
11124098.05
3311061.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 Compiler

Result:

product_nameprice
Samsung 43" Smart TV31990.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 Compiler

Error: 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 Compiler

Result:

order_idtotal_qty
10195
10055
10034
10234
10213
10163

(6 rows)

Key takeaways

  • A subquery is a query in brackets inside another query.
  • Scalar subqueries return one value; IN subqueries return a list; FROM subqueries 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.

Newsletter

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)