Lesson Description
Build confidence in Subqueries & CTEs by practicing this lesson with structured prompts and real query execution.
Lesson 3 of 4
Progress: 0/3 exercises solved (0%)
Solve all exercises below to unlock the next lesson.
Lesson Description
Build confidence in Subqueries & CTEs by practicing this lesson with structured prompts and real query execution.
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.
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.
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.
Lesson 3 of 4
0/3 solved (0%)
Finish this lesson to unlock next.