Lesson Description
Write correlated subqueries that evaluate each row against its own context. This lesson helps you solve comparison-style problems while staying precise about row scope and performance tradeoffs.
Lesson Description
Write correlated subqueries that evaluate each row against its own context. This lesson helps you solve comparison-style problems while staying precise about row scope and performance tradeoffs.
Easy Project
Mini project: Turn this lesson into a real-world query artifact by writing one clean business report query and validating output quality.
A correlated subquery references a column from the outer query. It re-executes for each row of the outer query.
SELECT first_name, last_name, salary, department_id
FROM employees e
WHERE salary `>` (
SELECT AVG(salary)
FROM employees inner_e
WHERE inner_e.department_id `=` e.department_id -- references outer row
);
e.department_id links the inner query to the current outer row. For each employee, the inner query calculates that employee's department average.
| Regular subquery | Correlated subquery | |
|---|---|---|
| Executes | Once | Once per outer row |
| References outer query | No | Yes |
| Typical use | IN, scalar comparison | WHERE col > (SELECT ...) |
-- Patients who have at least one completed appointment
SELECT first_name, last_name
FROM patients p
WHERE EXISTS (
SELECT 1 FROM appointments a
WHERE a.patient_id `=` p.id
AND a.status `=` 'completed'
);
Correlated subqueries can be slow on large tables — they run N times (once per outer row). For better performance, rewrite as a JOIN with a derived table:
-- Equivalent and usually faster
SELECT e.first_name, e.last_name, e.salary, e.department_id
FROM employees e
JOIN (
SELECT department_id, AVG(salary) AS dept_avg
FROM employees
GROUP BY department_id
) dept ON dept.department_id `=` e.department_id
WHERE e.salary `>` dept.dept_avg;
Or use a window function:
SELECT first_name, last_name, salary, department_id
FROM (
SELECT *,
AVG(salary) OVER (PARTITION BY department_id) AS dept_avg
FROM employees
) t
WHERE salary `>` dept_avg;
EXPLAIN ANALYZE shows "Seq Scan" on a large table inside a subquery loop, rewrite as a JOIN.This lesson is part of Subqueries & CTEs. Focus on the core idea in Correlated subqueries, then validate with deliberate practice.
What to master
Common mistakes
High-level strategy
Task ladder
Transparent data checks
Retention loop
Logical reasoning for commands
WHERE
Why: Limits rows to only the business-relevant subset.
Memory cue: Filter early to reduce noise.
ORDER BY
Why: Makes output deterministic and reviewable.
Memory cue: No ORDER BY means no guaranteed row order.
AS alias
Why: Makes output columns readable for teams and reports.
Memory cue: If the name is clear, the query is easier to trust.
Concept check
3 quick questions. One at a time. Instant score at the end.
Return first_name, last_name, salary, and department_id for employees whose salary exceeds the average salary in their own department. Use a correlated subquery. Order by department_id, then salary descending.
No results yet
Run a query to see results here
From the store sandbox, return name, price, and category_id for products whose price exceeds the average price in their own category. Use a correlated subquery. Order by category_id, price descending.
No results yet
Run a query to see results here
From the hospital sandbox, return first_name and last_name of patients whose appointment count exceeds the average number of appointments per patient. Use a correlated subquery. Order by last_name.
No results yet
Run a query to see results here