Product Dojo is a members' platform. Sign in to open the lessons, clear
stages, and earn the certificate for each one. Your rank and credentials
stay with your account across every device you use.
Free to join, through GitHub, Google or LinkedIn. No password to set.
Not unlocked yet
Stages open in order. Clear the one before this to come back here.
Deciding what comes first, and how much comes back
A table has no order. Whatever sequence your rows arrive in without an
ORDER BY is an accident of how the data happens to be stored,
and it can change without warning. This stage is about taking control of
that — and about the fact that "top 10" is meaningless until you have.
Ready?
Step-by-step lessons
Order and Size
Four short lessons: why unsorted results are not stable, how to reverse a sort, what a second sort column is actually for, and how LIMIT and OFFSET take a slice out of a sorted list.
1
Rows Are a Set, Not a List
A table is a set of rows. Sets have no order. When you
run SELECT * FROM users and the rows come back by
user_id, that is a coincidence of how this small table is
stored — not a promise, and not something to build on.
The coincidence holds until it does not. Add an index, delete some rows
and insert new ones, run the same query on a bigger machine that reads in
parallel, and the order changes. The bug that follows is a nasty one,
because the query did not change and nothing errored.
SELECT name, signup_date
FROM users
ORDER BY signup_date;
ORDER BY is the last clause in the query and the last thing
to run. Ascending is the default: smallest number first, earliest date
first, A before Z.
The rule in one line
If a human will read the result, it needs an ORDER BY. "It
came out sorted when I tested it" is an observation, not a guarantee,
and it is not one you can put in a report.
Quick check
A query with no ORDER BY has returned rows in id order every day for a year. Is that order guaranteed?
2
Direction Attaches to a Column
DESC reverses a sort; ASC is the default and
can be written out for clarity. The important detail is that the keyword
applies to the one column it follows, not to the whole
clause.
ORDER BY plan, name DESC -- plan ascending, name descending
ORDER BY plan DESC, name DESC -- both descending
The first of those is a very common accident. Someone wants both columns
reversed, writes DESC once at the end, and gets a result
that is half right — and half right is the hardest kind of wrong to
notice.
Sorting works on whatever the column holds. Text sorts alphabetically,
numbers numerically, and dates chronologically provided they are
stored sensibly. This database stores dates as text in
YYYY-MM-DD form precisely because that format sorts
correctly as text; 14/01/2023 would not.
Quick check
What does ORDER BY category, price DESC do?
3
A Second Column Only Breaks Ties
List several columns and the database sorts by the first, then uses the
second only where the first is equal, then the third where both are
equal. Exactly like a phone book: surname first, forename only when two
people share a surname.
SELECT name, plan
FROM users
ORDER BY plan, name;
This matters more than it sounds. Sorting only by a column with few
distinct values — a plan, a status, a country — leaves large groups of
rows tied, and tied rows are in arbitrary order. Your
report looks sorted, and the rows inside each plan can still shuffle
between runs.
Where this bites
Paginating with ORDER BY plan LIMIT 20 OFFSET 20 when
plan has three values means the database is free to order the ties
differently for page one and page two. Rows appear on both pages,
other rows appear on neither, and nobody can reproduce it.
A sort used for pagination must end in something unique, usually the id.
Quick check
Twelve users sorted by ORDER BY plan alone. What is guaranteed about the five free users?
4
Taking a Slice
LIMIT caps how many rows come back. It is applied
after the sort, and that ordering is the entire reason
"top 5" means anything.
SELECT order_id, amount
FROM orders
ORDER BY amount DESC
LIMIT 5;
Drop the ORDER BY and you still get five rows — just five
arbitrary ones. That is a genuinely useful thing when you want a peek at
an unfamiliar table, and a genuinely wrong thing when someone asked for
the biggest orders.
OFFSET throws rows away before LIMIT takes its
slice, which turns the pair into a window you can slide down a sorted
list.
LIMIT 3 OFFSET 1 -- skip the winner, take the next three
LIMIT 20 OFFSET 40 -- page 3, at 20 rows per page
Use it as a seatbelt too
When you are exploring a table you do not know the size of, put
LIMIT 100 on the end of every query. It costs nothing when
the table is small and saves you from pulling back ten million rows
when it is not.
Quick check
Which query returns the three most expensive products?
Write it yourself
Query Lab
Four queries about the order rows come back in, and how to take just the top of a list. Here the row order is part of the answer, so the checks compare position by position.
0 of 4 cleared
Real SQLite runs right here in your browser — nothing to install, nothing
sent to a server. The database is rebuilt from scratch before every single
run, so nothing you write can break it and nothing carries over between
exercises.
The Northwind Analytics databaseA small SaaS product's database: who signed up, what they subscribed to, what they bought, and what they did in the app.
Loading the tables…
01
Put the rows in an order
To do
A table has no order. Rows come back in whatever sequence the database
finds convenient, and that sequence can change without warning. If the
order matters, you have to say so.
SELECT name, signup_date FROM users ORDER BY signup_date;
ORDER BY goes last, after WHERE. Ascending is
the default: smallest number first, earliest date first, A before Z.
Your task: return name and
signup_date for every user, oldest signup first.
query.sql
SQLiteCtrl↵ to run
Hint
ORDER BY signup_date — dates in this database are stored as text in YYYY-MM-DD form, which sorts correctly as text precisely because of that ordering.
Output
02
Largest first
To do
Add DESC to reverse a sort. It applies to the one column it
follows, not to the whole clause — which matters as soon as you sort on
two things.
ORDER BY price DESC
ASC exists too, and is the default. Writing it out costs
nothing and removes any doubt for the next reader.
Your task: return name and
price from products, most expensive first.
query.sql
SQLiteCtrl↵ to run
Hint
ORDER BY price DESC — the keyword goes after the column name, not before it.
Output
03
Sort on two columns
To do
List several columns and the database sorts by the first, then uses the
second only to break ties, then the third, and so on. That is exactly
how a phone book works: surname first, first name only when two people
share a surname.
ORDER BY plan, name
Each column carries its own direction. ORDER BY plan, name
DESC sorts plan ascending and only the names descending — a
common and quiet source of wrong answers.
Your task: return name, plan
and country, sorted by plan and then by
name, both ascending.
query.sql
SQLiteCtrl↵ to run
Hint
ORDER BY plan, name — two columns, comma separated, no direction keywords needed since ascending is the default.
Output
04
Just the top of the list
To do
LIMIT cuts the result to the first N rows. It is applied
after the sort, which is the only reason "top 5" means
anything: without an ORDER BY, a LIMIT gives
you five arbitrary rows rather than the five biggest.
ORDER BY amount DESC LIMIT 5;
Your task: return the order_id and
amount of the five largest orders, largest first.
query.sql
SQLiteCtrl↵ to run
Hint
Two lines: ORDER BY amount DESC, then LIMIT 5. The sort has to come first or the limit picks the wrong five.
Output
Boss round
Assignment: The Runners-Up
One graded query. Sorting and a window into the middle of the list — pass every check and the stage is cleared.
This one is graded. You can see 2
of the checks up front, and 2
stay hidden until you run. The hidden ones test the same job from angles
you have not been shown, so code that genuinely solves the problem clears
this and code shaped around the visible examples does not.
Pass them all and the stage is yours.
The runners-up
To do
Marketing is writing a page about the products that are expensive but not
the flagship. They want the second, third and fourth most
expensive products — skipping the priciest one entirely.
OFFSET is how you skip. LIMIT 3 OFFSET 1 throws
away the first row and returns the next three, so it pairs with
ORDER BY to take any window out of a sorted list.
Your task: return name and
price from products — three rows, most expensive
of the three first, with the single priciest product excluded.
query.sql
SQLiteCtrl↵ to run
Hint
ORDER BY price DESC, then LIMIT 3 OFFSET 1. OFFSET counts rows to throw away, so 1 skips exactly the top row.
Output
Lock it in
Guess the Term
Read the clues and name the concept. The fewer clues you need, the more points you score.
Round 1Score 0
Keep it handy
ORDER BY & LIMIT Quick Reference
The whole stage on one screen.
Sorting
1
ORDER BY col
Ascending. Last clause in the query.
2
ORDER BY col DESC
Descending. The keyword attaches to that one column.
3
ORDER BY a, b
b only breaks ties on a.
Slicing
LIMIT n
First n rows, after the sort.
OFFSET n
Throw away n rows first.
Pagination
Page p at size s is LIMIT s OFFSET (p-1)*s.
Traps
1
LIMIT with no sort
Arbitrary rows, not the top ones.
2
One DESC, two columns
Only the column before it is reversed.
3
Unstable pagination
Ties shuffle between pages. End the sort on something unique.