A Query Inside a Query

The manager of Pixel Shop wants to know which games cost more than the average game. You know how to find the average:

SELECT AVG(price) FROM games;
AVG(price)
28.5

So you could write a second query with WHERE price > 28.5. It works, but you've copied the number by hand: when the shop changes a price, the average changes too, and your query quietly gives a wrong answer. And the obvious shortcut doesn't work at all:

SELECT title
FROM games
WHERE price > AVG(price);
Error on line 1: misuse of aggregate function AVG()

WHERE decides about one row at a time, before any groups are made (remember the order in which the parts of a query run), so there's no average yet that it could compare with.

The way out is to put the first query, in parentheses, right where the number was:

SELECT title, price
FROM games
WHERE price > (SELECT AVG(price) FROM games)
ORDER BY price DESC, title;
titleprice
Knight's Oath60
Goal Rush50
Ice Hockey Pro45
Dragon Forge40
……

The query in parentheses is a subquery: a query inside another query. The database runs it first, gets one value, 28.5, and then runs the outer query as if you had typed 28.5 in its place. But unlike a typed number, the subquery computes the average again every time the query runs, so it's always right.

A subquery is a complete query, with its own SELECT and FROM, and it can have its own WHERE. When it returns a single value (one row with one column), you can use it wherever a number, a text or a date could stand. This query finds the latest order: the subquery finds the latest date, and the outer query the order placed on that day:

SELECT *
FROM orders
WHERE ordered_at = (SELECT MAX(ordered_at) FROM orders);
idcustomer_idordered_atstatus
3042025-12-22paid

A subquery can be part of a calculation too: price - (SELECT AVG(price) FROM games) AS difference in SELECT shows how much more or less than the average each game costs.

Typical mistake: a subquery that finds several rows where one value is expected. Which games cost the same as an Action game?

SELECT title, price
FROM games
WHERE price = (SELECT price FROM games WHERE genre = 'Action')
ORDER BY title;
titleprice
Mystery Manor25
Shadow Ninja25
Tiny Kingdoms25

There are four Action games, for $25, $35, $30 and $40, but the query found only the games for $25. It isn't an error in SQLite: when a subquery used as a value finds several rows, SQLite quietly takes the first one (here the price of Shadow Ninja, the first Action game in the table) and ignores the others. Other databases stop with an error. A subquery used as a value should therefore return exactly one value: usually an aggregate function such as AVG() or MAX(), or a condition that matches exactly one row. For a whole list of values, there's IN, which comes next.

Instructions

The query in the editor shows the reviews with fewer stars than the average, but the average, 3.96, is typed in by hand. Replace the number with a subquery that computes the average of stars in the table reviews. The result stays the same:

game_idcustomer_idstars
262
8112
153
………

Below it, write a query that shows the title and the price of the games that are more expensive than Dragon Forge. Don't type its price: find it with a subquery that looks the game up by its title. Sort the games from the most expensive one, and games with the same price by title:

titleprice
Knight's Oath60
Goal Rush50
Ice Hockey Pro45

Finally, show the orders from the last 30 days of the shop: the id, customer_id and ordered_at of the orders placed no earlier than 30 days before the latest order. Compute that day in a subquery, with MAX() inside date(…, '-30 days'). (A real shop would count back from today, date('now', '-30 days'), but the data of Pixel Shop ends in December 2025.) Sort the orders by date:

idcustomer_idordered_at
28132025-11-28
2932025-12-12
3042025-12-22

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

1/8