π Lesson 8: Sorting & Limiting Results
Raw query results come back in whatever order MySQL feels like returning them. That's fine for debugging, but real applications need sorted results and pagination. In this lesson, you'll learn to control the order and quantity of your output.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Sort results with
ORDER BYin ascending and descending order - Sort by multiple columns with priority ordering
- Limit output to a fixed number of rows with
LIMIT - Skip rows with
OFFSETfor pagination - Combine
WHERE,ORDER BY, andLIMITin real-world patterns - Understand how NULL values are sorted
Estimated Time: 40 minutes
Prerequisites: WHERE clauses and filtering (Lesson 7)
π In This Lesson
ORDER BY β Sorting Results
Without ORDER BY, MySQL returns rows in no guaranteed order β it might be insertion order, primary key order, or something else entirely. Never rely on default order. If you want a specific order, always use ORDER BY.
Ascending Order (ASC) β The Default
-- Sort books by price, cheapest first
SELECT title, price FROM books ORDER BY price;
-- Same thing β ASC is the default
SELECT title, price FROM books ORDER BY price ASC;
Output:
+-------------------------+-------+
| title | price |
+-------------------------+-------+
| Pride and Prejudice | 7.99 |
| The Great Gatsby | 8.99 |
| The Catcher in the Rye | 8.99 |
| Brave New World | 9.49 |
| 1984 | 9.99 |
| Fahrenheit 451 | 10.49 |
| The Hobbit | 10.99 |
| To Kill a Mockingbird | 11.99 |
| Dune | 14.99 |
| Lord of the Rings | 15.99 |
+-------------------------+-------+
Descending Order (DESC)
-- Sort books by price, most expensive first
SELECT title, price FROM books ORDER BY price DESC;
Output:
+-------------------------+-------+
| title | price |
+-------------------------+-------+
| Lord of the Rings | 15.99 |
| Dune | 14.99 |
| To Kill a Mockingbird | 11.99 |
| The Hobbit | 10.99 |
| Fahrenheit 451 | 10.49 |
| 1984 | 9.99 |
| Brave New World | 9.49 |
| The Great Gatsby | 8.99 |
| The Catcher in the Rye | 8.99 |
| Pride and Prejudice | 7.99 |
+-------------------------+-------+
Sorting Text
Text sorts alphabetically (AβZ for ASC, ZβA for DESC):
-- Alphabetical by title
SELECT title FROM books ORDER BY title ASC;
-- Alphabetical by author
SELECT DISTINCT author FROM books ORDER BY author;
-- Reverse alphabetical by last name
SELECT first_name, last_name FROM customers ORDER BY last_name DESC;
Sorting Dates
Dates sort chronologically β earliest first with ASC, latest first with DESC:
-- Oldest books first
SELECT title, published FROM books ORDER BY published ASC;
-- Newest books first
SELECT title, published FROM books ORDER BY published DESC;
Output (newest first):
+-------------------------+------------+
| title | published |
+-------------------------+------------+
| Dune | 1965-08-01 |
| To Kill a Mockingbird | 1960-07-11 |
| Lord of the Rings | 1954-07-29 |
| Fahrenheit 451 | 1953-10-19 |
| The Catcher in the Rye | 1951-07-16 |
| 1984 | 1949-06-08 |
| The Hobbit | 1937-09-21 |
| Brave New World | 1932-01-01 |
| The Great Gatsby | 1925-04-10 |
| Pride and Prejudice | 1813-01-28 |
+-------------------------+------------+
Sorting with WHERE
ORDER BY works with WHERE β filter first, then sort the results:
-- In-stock books, cheapest first
SELECT title, price FROM books
WHERE in_stock = TRUE
ORDER BY price ASC;
-- Books over $10, newest first
SELECT title, price, published FROM books
WHERE price > 10
ORDER BY published DESC;
Sorting by Multiple Columns
When two rows have the same value in the first sort column, the second column breaks the tie:
-- Sort by price, then by title (for books at the same price)
SELECT title, price FROM books
ORDER BY price ASC, title ASC;
Output (notice the $8.99 books are now alphabetized):
+-------------------------+-------+
| title | price |
+-------------------------+-------+
| Pride and Prejudice | 7.99 |
| The Catcher in the Rye | 8.99 | β alphabetical
| The Great Gatsby | 8.99 | β tie-breaker
| Brave New World | 9.49 |
| 1984 | 9.99 |
| Fahrenheit 451 | 10.49 |
| The Hobbit | 10.99 |
| To Kill a Mockingbird | 11.99 |
| Dune | 14.99 |
| Lord of the Rings | 15.99 |
+-------------------------+-------+
Each column can have its own direction:
-- Sort by in_stock (in-stock first), then by price (cheapest first)
SELECT title, in_stock, price FROM books
ORDER BY in_stock DESC, price ASC;
π‘ Why In-Stock DESC?
Remember that MySQL stores TRUE as 1 and FALSE as 0. DESC puts 1 (TRUE/in-stock) before 0 (FALSE/out-of-stock) β so in-stock books appear first.
-- Customers sorted by city, then last name within each city
SELECT first_name, last_name, city FROM customers
WHERE city IS NOT NULL
ORDER BY city ASC, last_name ASC;
Output:
+------------+-----------+-------------+
| first_name | last_name | city |
+------------+-----------+-------------+
| Bob | Smith | Chicago |
| Grace | Wilson | Chicago |
| Dave | Brown | Houston |
| Carol | Williams | Los Angeles |
| Alice | Johnson | New York |
| Frank | Miller | New York |
+------------+-----------+-------------+
Sorting by Expressions
You can sort by calculated values, not just column names:
-- Sort by price per page (value for money!)
SELECT
title,
price,
pages,
ROUND(price / pages, 4) AS price_per_page
FROM books
WHERE pages > 0
ORDER BY price_per_page ASC;
-- Sort by column position (not recommended, but possible)
SELECT title, author, price FROM books
ORDER BY 3 DESC; -- sorts by the 3rd column (price)
β οΈ Avoid Sorting by Column Number
Sorting by position (ORDER BY 3) works but is fragile β if someone adds or reorders columns in the SELECT list, the sort breaks silently. Always use column names.
How NULL Values Sort
MySQL treats NULL as the lowest possible value:
ORDER BY col ASCβ NULLs appear firstORDER BY col DESCβ NULLs appear last
-- NULLs first (ascending)
SELECT first_name, city FROM customers ORDER BY city ASC;
Output:
+------------+-------------+
| first_name | city |
+------------+-------------+
| Eve | NULL | β NULLs first in ASC
| Hank | NULL |
| Bob | Chicago |
| Grace | Chicago |
| Dave | Houston |
| Carol | Los Angeles |
| Alice | New York |
| Frank | New York |
+------------+-------------+
Controlling NULL Position
If you want NULLs at the end of an ascending sort, use a trick with IS NULL:
-- Ascending, but push NULLs to the bottom
SELECT first_name, city FROM customers
ORDER BY city IS NULL ASC, city ASC;
Output:
+------------+-------------+
| first_name | city |
+------------+-------------+
| Bob | Chicago |
| Grace | Chicago |
| Dave | Houston |
| Carol | Los Angeles |
| Alice | New York |
| Frank | New York |
| Eve | NULL | β NULLs at the end now
| Hank | NULL |
+------------+-------------+
π‘ How This Works
city IS NULL evaluates to 1 (TRUE) for NULL cities and 0 (FALSE) for non-NULL cities. Sorting this ASC puts 0 (has a city) before 1 (NULL city). Then the second sort column city ASC sorts the non-NULL cities alphabetically.
LIMIT β Controlling Row Count
LIMIT restricts how many rows are returned. It's essential for performance (don't fetch a million rows when you need 10) and for building application features like "top N" lists.
Basic LIMIT
-- The 3 cheapest books
SELECT title, price FROM books
ORDER BY price ASC
LIMIT 3;
Output:
+-------------------------+-------+
| title | price |
+-------------------------+-------+
| Pride and Prejudice | 7.99 |
| The Great Gatsby | 8.99 |
| The Catcher in the Rye | 8.99 |
+-------------------------+-------+
-- The 5 most expensive books
SELECT title, price FROM books
ORDER BY price DESC
LIMIT 5;
-- The 3 longest books
SELECT title, pages FROM books
ORDER BY pages DESC
LIMIT 3;
-- The newest book
SELECT title, published FROM books
ORDER BY published DESC
LIMIT 1;
β Common Patterns with LIMIT
LIMIT 1 is especially useful for finding the single "most" or "least" of something:
-- Most expensive book
SELECT title, price FROM books ORDER BY price DESC LIMIT 1;
-- Oldest book
SELECT title, published FROM books ORDER BY published ASC LIMIT 1;
-- Most recently created customer
SELECT * FROM customers ORDER BY created_at DESC LIMIT 1;
LIMIT with WHERE
-- Top 3 cheapest in-stock books
SELECT title, price FROM books
WHERE in_stock = TRUE
ORDER BY price ASC
LIMIT 3;
-- The 2 most expensive books under $15
SELECT title, price FROM books
WHERE price < 15
ORDER BY price DESC
LIMIT 2;
β οΈ LIMIT Without ORDER BY
LIMIT without ORDER BY returns an arbitrary subset of rows β MySQL doesn't guarantee which ones. This is rarely useful. Always pair LIMIT with ORDER BY unless you genuinely don't care which rows you get.
OFFSET β Skipping Rows
OFFSET tells MySQL to skip a number of rows before starting to return results. Combined with LIMIT, it enables pagination.
-- Skip the first 3 rows, then return the next 3
SELECT title, price FROM books
ORDER BY price ASC
LIMIT 3 OFFSET 3;
Output (rows 4β6 by price):
+-----------------+-------+
| title | price |
+-----------------+-------+
| Brave New World | 9.49 |
| 1984 | 9.99 |
| Fahrenheit 451 | 10.49 |
+-----------------+-------+
Alternative Syntax
MySQL also supports a comma syntax: LIMIT offset, count:
-- Same as LIMIT 3 OFFSET 3:
SELECT title, price FROM books
ORDER BY price ASC
LIMIT 3, 3;
-- The first number is the offset, the second is the count
π‘ Which Syntax to Use?
LIMIT 3 OFFSET 3 is clearer and more readable than LIMIT 3, 3. The comma syntax is older and easier to misread (which number is offset and which is count?). Prefer the LIMIT ... OFFSET ... form.
Pagination Patterns
Pagination is how applications display large datasets across multiple pages β like search results, product listings, or email inboxes. The formula is:
-- Page N with P items per page:
SELECT columns FROM table
ORDER BY sort_column
LIMIT P OFFSET (N - 1) * P;
Example: 3 Books Per Page
-- Page 1 (first 3 books)
SELECT title, price FROM books ORDER BY title LIMIT 3 OFFSET 0;
-- Page 2 (next 3 books)
SELECT title, price FROM books ORDER BY title LIMIT 3 OFFSET 3;
-- Page 3 (next 3 books)
SELECT title, price FROM books ORDER BY title LIMIT 3 OFFSET 6;
-- Page 4 (remaining book)
SELECT title, price FROM books ORDER BY title LIMIT 3 OFFSET 9;
Page 1 Output:
+-----------------------+-------+
| title | price |
+-----------------------+-------+
| 1984 | 9.99 |
| Brave New World | 9.49 |
| Dune | 14.99 |
+-----------------------+-------+
Page 2 Output:
+-------------------------+-------+
| title | price |
+-------------------------+-------+
| Fahrenheit 451 | 10.49 |
| Lord of the Rings | 15.99 |
| Pride and Prejudice | 7.99 |
+-------------------------+-------+
Getting the Total Count
For pagination UI (showing "Page 2 of 4" or "Showing 4β6 of 10"), you also need the total row count:
-- Total number of books
SELECT COUNT(*) AS total_books FROM books;
-- Total in-stock books (for filtered pagination)
SELECT COUNT(*) AS total_in_stock FROM books WHERE in_stock = TRUE;
β οΈ OFFSET Performance on Large Tables
OFFSET doesn't skip rows efficiently β MySQL still reads and discards them. OFFSET 100000 means MySQL reads 100,000 rows just to throw them away. For large datasets (millions of rows), consider these alternatives:
- Keyset pagination β
WHERE id > last_seen_id LIMIT 10(much faster) - Covering indexes β ensure the sort column is indexed
- Caching β cache page results in the application layer
For our course and most small-to-medium applications, OFFSET works perfectly fine.
Keyset Pagination Preview
For comparison, here's how keyset pagination works β instead of skipping by count, you filter by the last value seen:
-- Page 1: get the first 3 books by ID
SELECT id, title, price FROM books
ORDER BY id ASC
LIMIT 3;
-- Returns IDs 1, 2, 3
-- Page 2: get 3 books AFTER id 3
SELECT id, title, price FROM books
WHERE id > 3
ORDER BY id ASC
LIMIT 3;
-- Returns IDs 4, 5, 6
-- Page 3: get 3 books AFTER id 6
SELECT id, title, price FROM books
WHERE id > 6
ORDER BY id ASC
LIMIT 3;
This avoids the OFFSET penalty entirely and is how most high-traffic APIs implement pagination.
SQL Clause Order
SQL clauses must appear in a specific order. Getting this wrong causes syntax errors. Here's the order for everything we've learned so far:
-- The full pattern:
SELECT title, price
FROM books
WHERE in_stock = TRUE AND price < 15
ORDER BY price ASC
LIMIT 5
OFFSET 0;
β Memory Aid
Think of it as a pipeline: Select what you want β From where β Which rows β Order them β Limit how many. Or just remember: S-F-W-O-L.
β οΈ Common Mistake
Putting ORDER BY before WHERE is a syntax error:
-- β WRONG β ORDER BY can't come before WHERE
SELECT title FROM books ORDER BY price WHERE price > 10;
-- β
CORRECT
SELECT title FROM books WHERE price > 10 ORDER BY price;
Exercises
ποΈ Exercise 1: Basic Sorting
Write queries to:
- List all books alphabetically by title
- List all books from most expensive to cheapest
- List all customers alphabetically by last name, then first name
- List all books from newest to oldest publication date
β Solution
-- 1
SELECT title FROM books ORDER BY title ASC;
-- 2
SELECT title, price FROM books ORDER BY price DESC;
-- 3
SELECT last_name, first_name FROM customers
ORDER BY last_name ASC, first_name ASC;
-- 4
SELECT title, published FROM books ORDER BY published DESC;
ποΈ Exercise 2: LIMIT & Top N
Write queries to find:
- The 3 most expensive books
- The single oldest book in the database
- The 5 shortest books (fewest pages)
- The 3 cheapest in-stock books
β Solution
-- 1
SELECT title, price FROM books
ORDER BY price DESC LIMIT 3;
-- 2
SELECT title, published FROM books
ORDER BY published ASC LIMIT 1;
-- 3
SELECT title, pages FROM books
ORDER BY pages ASC LIMIT 5;
-- 4
SELECT title, price FROM books
WHERE in_stock = TRUE
ORDER BY price ASC LIMIT 3;
ποΈ Exercise 3: Pagination
Imagine a bookstore website showing 4 books per page, sorted by title alphabetically. Write queries for:
- Page 1 (first 4 books)
- Page 2 (next 4 books)
- Page 3 (remaining books)
- The total number of books (for "Page X of Y")
β Solution
-- Page 1
SELECT title, price FROM books
ORDER BY title LIMIT 4 OFFSET 0;
-- Page 2
SELECT title, price FROM books
ORDER BY title LIMIT 4 OFFSET 4;
-- Page 3
SELECT title, price FROM books
ORDER BY title LIMIT 4 OFFSET 8;
-- Total count
SELECT COUNT(*) AS total_books FROM books;
-- Returns 10 β 10 / 4 = 3 pages (with 2 books on the last page)
ποΈ Exercise 4: Combined Challenge
Write a single query that:
- Shows the title, author, price, and pages
- Only includes in-stock books
- Only includes books with more than 200 pages
- Sorts by pages (most pages first)
- Returns only the top 5 results
β Solution
SELECT title, author, price, pages
FROM books
WHERE in_stock = TRUE
AND pages > 200
ORDER BY pages DESC
LIMIT 5;
π― Quick Quiz
Question 1: What is the default sort direction for ORDER BY?
Question 2: Where do NULL values appear in an ascending sort?
Question 3: What does LIMIT 5 OFFSET 10 do?
Question 4: What is the correct order of SQL clauses?
Summary
π Key Takeaways
- ORDER BY sorts results β
ASC(default) for ascending,DESCfor descending - You can sort by multiple columns β the second column is the tie-breaker
- Each sort column can have its own direction (ASC or DESC)
- NULL values sort as the lowest value β first in ASC, last in DESC
- LIMIT N returns only N rows β essential for "top N" queries
- OFFSET M skips M rows before returning results
- Pagination formula:
LIMIT page_size OFFSET (page - 1) * page_size - Always pair
LIMITwithORDER BYfor predictable results - SQL clause order: SELECT β FROM β WHERE β ORDER BY β LIMIT β OFFSET
- For large tables, keyset pagination (
WHERE id > last_id) outperformsOFFSET
Quick Reference
| Pattern | Syntax |
|---|---|
| Sort ascending | ORDER BY col ASC |
| Sort descending | ORDER BY col DESC |
| Multi-column sort | ORDER BY col1 ASC, col2 DESC |
| NULLs last (ASC) | ORDER BY col IS NULL, col ASC |
| Top N | ORDER BY col LIMIT N |
| Pagination | LIMIT size OFFSET (page-1)*size |
| Keyset pagination | WHERE id > last_id LIMIT size |
π Additional Resources
- MySQL Docs β ORDER BY Optimization
- MySQL Docs β LIMIT Optimization
- MySQL Docs β SELECT Statement
π What's Next?
You can now filter, sort, and paginate β but what about asking questions about your data? "How many books do we have?" "What's the average price?" "Which author has the most books?" In the next lesson, you'll learn aggregate functions (COUNT, SUM, AVG, MIN, MAX) and GROUP BY β the tools that turn raw data into insights.
π Congratulations!
Your queries now produce polished, organized results. Sorted, limited, and paginated β just like a real application!