Intermediate · SQL in depth · topic 23 of 54 · 20 min
Subqueries, CTEs and window functions
Explanation
A query inside a query
To find products priced above average you need two steps: work out the average, then compare each product with it. A subquery does the first step inside brackets. The database runs it, gets one number, and uses that number in the outer query.
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price DESC;IN, EXISTS and NOT EXISTS
A subquery can also return a list. IN checks whether a value is in that list. EXISTS asks a yes or no question for each outer row: "does at least one matching row exist?". A subquery that uses a column of the outer query (o.customer_id = c.id) is called correlated: it is checked once per outer row.
-- customers who bought something from the kitchen category
SELECT name FROM customers
WHERE id IN (
SELECT o.customer_id
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE p.category = 'kitchen'
);
-- customers with no orders at all
SELECT c.name FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);Subqueries in FROM and CTEs
A subquery in FROM acts like a temporary table that exists only for this query. A CTE (Common Table Expression) does the same job with a name, written first with WITH. You can define several, each using the ones before it. Most people find CTEs much easier to read and debug.
WITH totals AS (
SELECT s.class, s.name, SUM(m.score) AS total
FROM students s
JOIN marks m ON m.student_id = s.id
GROUP BY s.id, s.class, s.name
)
SELECT class, AVG(total) AS class_average
FROM totals
GROUP BY class;A first look at window functions
GROUP BY squeezes each group into one row. A window function instead adds a value to every row, worked out over a "window" of related rows. OVER (PARTITION BY class ORDER BY score DESC) means: split rows by class, sort each class by score, then number or rank them. SQLite and PostgreSQL both support this.
SELECT s.class, s.name, m.score,
ROW_NUMBER() OVER (PARTITION BY s.class ORDER BY m.score DESC) AS position
FROM students s
JOIN marks m ON m.student_id = s.id
WHERE m.subject = 'Maths';| score | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 90 | 1 | 1 | 1 |
| 85 | 2 | 2 | 2 |
| 85 | 3 | 2 | 2 |
| 70 | 4 | 4 | 3 |
Watch the lesson
Subqueries, CTEs and window functions · 1/6
Some questions need two steps. Subqueries let one query use the answer of another.
Prefer a video by a teacher? Find free videos on “sql subquery cte window functions row_number tutorial” on YouTube ↗
In real life: Board result toppers on a school portal
When a school portal publishes results, it shows the topper of each section and each student's rank in class. The backend builds a CTE of total marks per student, then uses RANK() OVER (PARTITION BY class ORDER BY total DESC) so students with equal marks share a rank. The same pattern finds the latest order of each customer for a "Buy again" list, or the most recent KYC document per user.
WITH totals AS (
SELECT s.id, s.class, s.name, SUM(m.score) AS total
FROM students s JOIN marks m ON m.student_id = s.id
GROUP BY s.id, s.class, s.name
)
SELECT class, name, total,
RANK() OVER (PARTITION BY class ORDER BY total DESC) AS class_rank
FROM totals
ORDER BY class, class_rank;Practice (0/3 done)
Table: products(id, name, category, price). List the name and price of every product that costs more than the average price of all products. Highest price first. Use a subquery, do not type the average yourself.
Tables: customers(id, name, city) and orders(id, customer_id, order_date, amount, status). Using EXISTS, list the names of customers who have at least one order with amount above 1000. Sort by name.
Tables: students(id, name, class) and marks(student_id, subject, score). Find the student with the highest total score in each class. Use a CTE for totals and ROW_NUMBER() OVER (PARTITION BY class ORDER BY total DESC). Show class, name and total, sorted by class.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.