Scalar & IN Subqueries

RoadmapsSQL

Overview

A subquery is a SELECT nested inside another SQL statement. It runs first and feeds its result into the outer query. Three common shapes: 1. **Scalar subquery** — returns exactly ONE value (one row, one column). You can use it anywhere a single value is expected: in SELECT, WHERE, or even HAVING. SELECT name, price FROM products WHERE price > (SELECT AVG(price) FROM products) ORDER BY price; The inner SELECT AVG(price) returns 85.82 (the average of all 6 products). The outer query then keeps only products whose price exceeds that single number: Monitor (199.99), Desk (149.99), Chair (89.99). 2. **IN subquery** — returns a list of values (one column, many rows). Used with WHERE col IN (subquery) to filter the outer query against that list. SELECT name FROM customers WHERE id IN (SELECT customer_id FROM orders WHERE status = 'paid') ORDER BY name; The inner query finds customer_ids with paid orders: {1, 2, 3, 5, 6}. The outer query returns the matching names: Alice, Bob, Carlos, Emma, Farah. 3. **Derived Table** — a subquery in the FROM clause that acts as a temporary table. It returns a full grid of rows and columns and MUST be given an alias. SELECT AVG(total) FROM (SELECT SUM(price) as total FROM orders GROUP BY customer_id) AS sub; Think of subqueries as 'compute a value or a list first, then plug it in'. The inner query produces a row-set; the outer query consumes it.

Subqueries let you express multi-step logic in a single statement — 'find products above the average price' or 'find customers who ordered at least once' — without needing temporary tables or multiple round-trips. They are the bridge between simple single-table queries and the powerful compositional SQL that real dashboards and APIs rely on.

Where used: filtering rows against a derived threshold (e.g. above-average price), checking membership in a dynamic list (e.g. customers who placed orders), embedding computed values in SELECT for inline comparisons

Why learn this

Query walkthrough

SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price;

Focus: Watch the scalar subquery resolve to a single number (85.82), then see WHERE filter the products row-set down to only those above that threshold.

Common mistakes

Glossary

nested
Placed or hidden inside something else of the same type, like one query sitting inside another.
compositional
Relating to the ability to combine simple parts to build more complex structures or logic.
genuinely
Truly or authentically; used when the logic actually requires a specific approach rather than just seeming to.

Recall questions

Understanding checks

Which products appear and in what order?

Chair (89.99), Desk (149.99), Monitor (199.99)

AVG(price) of all 6 products = (49.99+19.99+199.99+149.99+89.99+4.99)/6 = 85.82. Only Chair, Desk, and Monitor exceed 85.82, ordered by ascending price.

Which customers does this return?

Greg, Hana

The inner query returns all customer_ids that have orders: {1,2,3,4,5,6}. NOT IN keeps customers whose id is not in that set — Greg (7) and Hana (8). This is the 'anti-semi-join' pattern for finding rows with no related records.

Practice tasks

Switch from scalar to IN subquery

This query finds products priced above the average. Modify it to instead find products whose id appears in any order_item (i.e. products that have been ordered at least once). Use an IN subquery on the order_items table and keep the ORDER BY.

Challenge

Customers who spent above average

Write a query that lists the names of customers whose total spending (SUM of product price × quantity across their orders and order_items) exceeds the average customer total spending. Order by name. Hint: you will need JOINs inside the subqueries and the outer query.

Continue learning

Previous: WHERE & Operators

Previous: Aggregate Functions

Next: Correlated Subqueries

Next: CTEs (WITH)

Return to SQL Roadmap