Drills
Write the query. Graded against a reference solution’s own output — the rows, and the order only when the reference actually asked for one.
Foundations
- The expensive orders
Return the id and total of every order over 1000, most expensive first. Two columns, and an order the query actually promises.
- Label every order
For each order, return its id and a label: 'big' when the total is over 1000, 'unknown' when there is no total at all, and 'small' otherwise. Order by id. Call the second column size.
- The orders nobody priced
Return the ids of every order with no total, in ascending order.
- Everyone who is not signed up
Return the names of every customer who is not signed up to the newsletter — including the ones who never answered. Order by name.
- Customers who never ordered
Return the names of every customer with no orders at all, ordered by name.
- What each city spent
Return each city and the total of its orders, biggest spender first, then by city name. Call the second column spent. Cities with no orders are not wanted.
- How many, and how many priced
For each customer id that has orders, return the id, how many orders they placed, and how many of those have a total. Call the columns customer_id, placed and priced. Order by customer_id.
- Only the busy cities
Return each city with more than five shipped orders, and that count, busiest first then by city. Call the count shipped_orders.
- Above the average
Return the id and total of every order worth more than the average order, largest first, then by id.
- Nothing shipped yet
Return the names of every customer who has no shipped order — including customers with no orders at all. Order by name.
Modelling
- The answer is already a table
Return how many submissions answered the plan question with pro. Call the count submissions. The same responses exist in two places; use the one the database can index.
Indexes
- One actor's events
Return the id and duration of every event belonging to actor 137, oldest id first. There is an index on actor_id; the budget assumes you let the planner use it.
- Without touching the table
Return the actor_id of every event for actor 200 — that column and nothing else, ordered by actor_id. The index already carries everything you need, so the table should never be read.
- Both halves of the key
Return the ids of every 'error' event that happened on or after 2024-06-01, oldest first. There is an index on (kind, happened_on).
- The function that costs you the index
Return the ids of every event whose email is '[email protected]', regardless of how it was capitalised in the query — oldest id first. There is no index on lower(email), so find the way to ask that keeps the index you do have.
The planner
- Read it before you run it
Return the actor_id and event count for the five busiest actors among 'purchase' events, busiest first then by actor_id. Call the count events.
- The rare value and the common one
Return the ids of every 'error' event, oldest first. There are far fewer of these than of any other kind, and the plan should reflect that.
- Give it something to hash
Return each customer's name alongside their order totals, ordered by name then total. Only customers who have orders. Two columns: name and total.
- Somebody's report got slowplan reading
This ran in a nightly report that used to finish in seconds. Nobody has changed the query. Read the plan and say which node is costing you, and what you would do about it.
- The join that reads one side over and overplan reading
Two small tables, and a plan that does far more work than either of them contains. Which node, and why?
Production
- Ask once, not once per row
For every actor with events, return the actor_id and how many events they have, busiest first then by actor_id. Call the count events. One pass, not one query per actor.
- The twentieth page, cheaply
Return the next twenty event ids after id 4000, in ascending order. Do it the way that costs the same on page twenty thousand as on page one.
Generated SQL
- A query an agent wrote
An agent produced `SELECT * FROM events WHERE lower(email) = '[email protected]' ORDER BY id`. It returns the right rows. Write the query that returns the same id, actor_id and happened_on — and lets the database use the index it already has.
21 drills and 2 plan readings. Every drill is graded on the rows it returned, the pages it read against a par derived from the reference, and — where the exercise is about one — the access path the planner chose.