Correlated Subqueries

RoadmapsSQL

Overview

A correlated subquery references a column from the outer query, so it is re-evaluated for every row the outer query considers. This is the key difference from a regular (non-correlated) subquery, which runs once and produces a fixed result. The most common pattern is EXISTS: SELECT c.name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.status = 'paid' ) ORDER BY c.name; For each customer row, the inner query checks: 'does at least one paid order exist for THIS customer?' The reference to c.id makes it correlated. Result: Alice, Bob, Carlos, Emma, Farah. Correlated scalar subqueries also work — returning one value per outer row: SELECT c.name, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS order_count FROM customers c ORDER BY c.name; For each customer, the inner query counts THAT customer's orders. Greg and Hana get 0. Mental model: a regular subquery is a constant — computed once, plugged in. A correlated subquery is a function — called once per outer row with that row's values as arguments.

Correlated subqueries express per-row logic that is awkward or impossible with plain JOINs or non-correlated subqueries. 'Find employees earning more than their department average' or 'find customers who have at least one paid order' map naturally to correlated patterns. EXISTS in particular is the idiomatic way to test for the presence or absence of related rows.

Where used: existence checks (does this user have at least one X?), per-row aggregates in SELECT without GROUP BY, row-by-row comparisons against group-level statistics

Why learn this

Query walkthrough

SELECT c.name
FROM customers c
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.customer_id = c.id AND o.status = 'paid'
)
ORDER BY c.name;

Focus: For each customer row, watch the correlated subquery check whether any paid order exists for THAT customer. The reference to c.id is what makes it correlated.

Common mistakes

Glossary

idiomatic
The most natural, standard, and widely accepted way of doing something in a specific programming language.
degrade
To drop in quality or performance, such as a query becoming slower as the amount of data increases.
alias
A temporary alternative name given to a table to make queries shorter and easier to write.

Recall questions

Understanding checks

Which customer names appear?

Greg, Hana

NOT EXISTS returns TRUE when the inner query finds zero rows. Greg (id=7) and Hana (id=8) have no rows in orders, so for them the inner query is empty and NOT EXISTS is TRUE.

Which employees appear and why?

Tom (150000), Wendy (130000)

For each employee, the inner query computes the average salary of THAT employee's department. Executive avg = 200000 — Sara equals it (200000 > 200000 is false). Engineering avg = (150000+120000+110000)/3 = 126666.67 — only Tom (150000) exceeds it. Sales avg = (130000+95000+90000)/3 = 105000 — only Wendy (130000) exceeds it. So only Tom and Wendy appear.

Practice tasks

Flip EXISTS to NOT EXISTS

This query finds customers who have at least one paid order. Modify it to find customers who do NOT have any paid orders. Keep the ORDER BY.

Challenge

Employees earning above their department average

Write a query that returns the name and salary of every employee whose salary is strictly above the average salary in their own department. Order by name.

Continue learning

Previous: Scalar & IN Subqueries

Return to SQL Roadmap