SQL ORDER to Sort Query Results
Every query you have written so far returns rows in whatever order the database happens to store them. Sometimes the results appear in the order you inserted the data. Sometimes they do not. The truth is, without an explicit sorting instruction, SQL makes no guarantees about row order. The same query can return rows in a different sequence today than it did yesterday.
This is where the ORDER BY clause comes in. It gives you complete control over how your results are arranged: alphabetically by name, from cheapest to most expensive, from newest to oldest, or by any combination of columns you need. It is one of the most frequently used clauses in SQL, and understanding it fully will make every report, every listing page, and every data export you produce look exactly the way you want.
This guide covers everything you need to know about ORDER BY: ascending and descending sort directions, sorting by multiple columns, using column positions as shortcuts, and the often-overlooked behavior of NULL values in sorted results. Every example uses the ShopSmart sample database with full outputs so you can follow along in your own environment (we defined it in a previous guide here).
How ORDER BY Works
The ORDER BY clause is added after the WHERE clause (or after FROM if there is no WHERE) and tells the database how to arrange the rows in your results.
SELECT columns
FROM table
WHERE condition -- optional
ORDER BY column;
Without ORDER BY, the database returns rows in an undefined order. It might look consistent during development, but it is not guaranteed. Adding ORDER BY gives you a predictable, reliable sequence every time.
Here is a quick demonstration. First, without any sorting:
SELECT name, price
FROM products;
Output (order is not guaranteed):
| name | price |
|---|---|
| Wireless Mouse | 29.99 |
| Mechanical Keyboard | 89.99 |
| USB-C Hub | 45.00 |
| SQL for Beginners | 34.99 |
| Data Science Handbook | 42.50 |
| Coffee Maker Pro | 129.99 |
| Yoga Mat Premium | 38.00 |
| Running Shoes X1 | 110.00 |
| Bluetooth Speaker | 65.00 |
| Stainless Water Bottle | 24.99 |
Now, with ORDER BY:
SELECT name, price
FROM products
ORDER BY price;
Output (sorted by price, lowest first):
| name | price |
|---|---|
| Stainless Water Bottle | 24.99 |
| Wireless Mouse | 29.99 |
| SQL for Beginners | 34.99 |
| Yoga Mat Premium | 38.00 |
| Data Science Handbook | 42.50 |
| USB-C Hub | 45.00 |
| Bluetooth Speaker | 65.00 |
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| Coffee Maker Pro | 129.99 |
The rows are now arranged in a clear, predictable sequence from cheapest to most expensive. No matter how many times you run this query, the order will always be the same.
Use ORDER BY whenever the order of rows matters to the consumer of the data. This includes search results, product listings, reports, leaderboards, activity feeds, transaction histories, and any display where users expect a logical arrangement. If you are just checking whether data exists or counting rows, sorting is unnecessary.
Ascending Order (ASC)
Ascending order arranges values from smallest to largest (for numbers), from A to Z (for text), and from earliest to latest (for dates). It is the default sort direction in SQL.
-- These two queries produce identical results
SELECT name, price
FROM products
ORDER BY price ASC;
SELECT name, price
FROM products
ORDER BY price;
Since ASC is the default, you do not need to type it. Both queries sort products from lowest to highest price. However, many developers include ASC explicitly for clarity, especially when the query also uses DESC on other columns.
Ascending Sort on Text
When sorting text columns in ascending order, rows are arranged alphabetically (A to Z):
SELECT name, price
FROM products
ORDER BY name ASC;
Output:
| name | price |
|---|---|
| Bluetooth Speaker | 65.00 |
| Coffee Maker Pro | 129.99 |
| Data Science Handbook | 42.50 |
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| SQL for Beginners | 34.99 |
| Stainless Water Bottle | 24.99 |
| USB-C Hub | 45.00 |
| Wireless Mouse | 29.99 |
| Yoga Mat Premium | 38.00 |
Products are listed in alphabetical order by name. Note that uppercase and lowercase letters, special characters, and numbers may affect the sort order depending on your database's collation settings, but for standard English text the result is predictable alphabetical sorting.
Ascending Sort on Dates
When sorting dates in ascending order, rows are arranged from earliest to latest (oldest first):
SELECT first_name, last_name, signup_date
FROM customers
ORDER BY signup_date ASC;
Output:
| first_name | last_name | signup_date |
|---|---|---|
| Alice | Johnson | 2023-01-15 |
| Bob | Martinez | 2023-03-22 |
| Carol | Singh | 2023-06-10 |
| David | Chen | 2023-08-05 |
| Eva | Brown | 2024-01-18 |
| Frank | Wilson | 2024-02-28 |
The customers who signed up first appear at the top.
Descending Order (DESC)
Descending order is the reverse of ascending: largest to smallest (numbers), Z to A (text), and latest to earliest (dates). You activate it by adding the DESC keyword after the column name.
SELECT name, price
FROM products
ORDER BY price DESC;
Output:
| name | price |
|---|---|
| Coffee Maker Pro | 129.99 |
| Running Shoes X1 | 110.00 |
| Mechanical Keyboard | 89.99 |
| Bluetooth Speaker | 65.00 |
| USB-C Hub | 45.00 |
| Data Science Handbook | 42.50 |
| Yoga Mat Premium | 38.00 |
| SQL for Beginners | 34.99 |
| Wireless Mouse | 29.99 |
| Stainless Water Bottle | 24.99 |
The most expensive product appears first, and the cheapest appears last.
Descending Sort on Dates
Descending dates show the most recent first, which is the most common sort order for things like order histories, activity logs, and news feeds:
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY order_date DESC;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 8 | 2024-04-10 | 199.98 | pending |
| 7 | 2024-04-01 | 63.00 | pending |
| 6 | 2024-03-20 | 34.99 | pending |
| 5 | 2024-03-12 | 155.00 | shipped |
| 4 | 2024-03-05 | 129.99 | shipped |
| 3 | 2024-02-20 | 77.49 | completed |
| 2 | 2024-01-15 | 89.99 | completed |
| 1 | 2024-01-10 | 119.98 | completed |
The newest orders appear at the top. This is exactly how an order management dashboard would display recent activity.
Descending Sort on Text
Descending text sorting arranges values from Z to A:
SELECT first_name, last_name
FROM customers
ORDER BY last_name DESC;
Output:
| first_name | last_name |
|---|---|
| Frank | Wilson |
| Carol | Singh |
| Bob | Martinez |
| Alice | Johnson |
| David | Chen |
| Eva | Brown |
ASC= Ascending = smallest first = A to Z = oldest first (the default, optional to type)DESC= Descending = largest first = Z to A = newest first (must be explicitly specified)
Sorting by Multiple Columns
Single-column sorting works well until two or more rows have the same value in the sorted column. What order should those tied rows appear in? Without a secondary sort, the database decides arbitrarily.
Sorting by multiple columns solves this by defining a tiebreaker hierarchy. When rows share the same value in the first sort column, the second sort column determines their order. If they also tie on the second column, the third column breaks that tie, and so on.
SELECT columns
FROM table
ORDER BY column1, column2, column3;
A Practical Example
Let us sort customers by city. Multiple customers live in the same city, so we need a tiebreaker:
-- Sort by city only: Tied rows have unpredictable order
SELECT first_name, last_name, city
FROM customers
ORDER BY city;
Output:
| first_name | last_name | city |
|---|---|---|
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
Chicago and New York each have two customers. Within those groups, the order is undefined. Let us add last_name as a secondary sort:
-- Sort by city, then by last name within each city
SELECT first_name, last_name, city
FROM customers
ORDER BY city, last_name;
Output:
| first_name | last_name | city |
|---|---|---|
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| David | Chen | New York |
| Alice | Johnson | New York |
| Eva | Brown | Seattle |
Now within New York, Chen comes before Johnson (alphabetical). Within Chicago, Singh comes before Wilson. Every row has a deterministic position.
Mixing ASC and DESC
Each column in an ORDER BY clause can have its own sort direction. This is extremely useful for real-world sorting requirements:
-- Sort by status alphabetically, then by total amount (highest first) within each status
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY status ASC, total_amount DESC;
Output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 1 | 2024-01-10 | 119.98 | completed |
| 2 | 2024-01-15 | 89.99 | completed |
| 3 | 2024-02-20 | 77.49 | completed |
| 8 | 2024-04-10 | 199.98 | pending |
| 7 | 2024-04-01 | 63.00 | pending |
| 6 | 2024-03-20 | 34.99 | pending |
| 5 | 2024-03-12 | 155.00 | shipped |
| 4 | 2024-03-05 | 129.99 | shipped |
The statuses are sorted alphabetically (completed, pending, shipped). Within each status group, orders are sorted by total amount from highest to lowest.
Three-Column Sort Example
-- Sort products by availability (available first), then category, then price (cheapest first)
SELECT name, category_id, price, is_available
FROM products
ORDER BY is_available DESC, category_id ASC, price ASC;
Output:
| name | category_id | price | is_available |
|---|---|---|---|
| Wireless Mouse | 1 | 29.99 | true |
| USB-C Hub | 1 | 45.00 | true |
| Mechanical Keyboard | 1 | 89.99 | true |
| SQL for Beginners | 2 | 34.99 | true |
| Data Science Handbook | 2 | 42.50 | true |
| Coffee Maker Pro | 3 | 129.99 | true |
| Stainless Water Bottle | 4 | 24.99 | true |
| Yoga Mat Premium | 4 | 38.00 | true |
| Running Shoes X1 | 4 | 110.00 | true |
| Bluetooth Speaker | 1 | 65.00 | false |
Available products appear first (is_available DESC puts true before false). Within available products, they are grouped by category (ascending). Within each category, they are sorted by price (cheapest first). The unavailable Bluetooth Speaker sinks to the bottom.
Imagine organizing physical folders. First, you sort by department (primary sort). Within each department, you sort by last name (secondary sort). Within people who share the same last name, you sort by first name (tertiary sort). Each additional sort column only comes into play when the previous columns have identical values.
Sorting by Column Position (Ordinal)
Instead of writing column names in the ORDER BY clause, you can use numbers that refer to the position of columns in your SELECT list. The first column is 1, the second is 2, and so on.
-- Sort by the second column in the SELECT list (price)
SELECT name, price
FROM products
ORDER BY 2;
Output:
| name | price |
|---|---|
| Stainless Water Bottle | 24.99 |
| Wireless Mouse | 29.99 |
| SQL for Beginners | 34.99 |
| Yoga Mat Premium | 38.00 |
| Data Science Handbook | 42.50 |
| USB-C Hub | 45.00 |
| Bluetooth Speaker | 65.00 |
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| Coffee Maker Pro | 129.99 |
ORDER BY 2 sorts by price because price is the second column in the SELECT list.
Multiple Ordinal Positions
You can combine ordinal positions just like column names, and mix directions:
-- Sort by category_id (3rd column, ascending), then price (2nd column, descending)
SELECT name, price, category_id
FROM products
ORDER BY 3 ASC, 2 DESC;
Output:
| name | price | category_id |
|---|---|---|
| Mechanical Keyboard | 89.99 | 1 |
| Bluetooth Speaker | 65.00 | 1 |
| USB-C Hub | 45.00 | 1 |
| Wireless Mouse | 29.99 | 1 |
| Data Science Handbook | 42.50 | 2 |
| SQL for Beginners | 34.99 | 2 |
| Coffee Maker Pro | 129.99 | 3 |
| Running Shoes X1 | 110.00 | 4 |
| Yoga Mat Premium | 38.00 | 4 |
| Stainless Water Bottle | 24.99 | 4 |
When Ordinal Sorting Is Useful
Ordinal sorting is convenient in a few specific situations:
- When sorting by a calculated expression that does not have a simple column name
- During quick, ad-hoc queries in an interactive session
- When the column name is very long and you want to avoid retyping it
-- Sorting by a calculated column using ordinal position
SELECT name,
price,
stock_quantity,
price * stock_quantity AS total_value
FROM products
ORDER BY 4 DESC;
Output:
| name | price | stock_quantity | total_value |
|---|---|---|---|
| Mechanical Keyboard | 89.99 | 75 | 6749.25 |
| Running Shoes X1 | 110.00 | 60 | 6600.00 |
| USB-C Hub | 45.00 | 200 | 9000.00 |
| Stainless Water Bottle | 24.99 | 180 | 4498.20 |
| Wireless Mouse | 29.99 | 150 | 4498.50 |
| Yoga Mat Premium | 38.00 | 100 | 3800.00 |
| Coffee Maker Pro | 129.99 | 25 | 3249.75 |
| SQL for Beginners | 34.99 | 50 | 1749.50 |
| Data Science Handbook | 42.50 | 30 | 1275.00 |
| Bluetooth Speaker | 65.00 | 0 | 0.00 |
Here, ORDER BY 4 DESC sorts by the total_value calculated column, which is much more convenient than repeating the expression price * stock_quantity in the ORDER BY.
While ordinal sorting is convenient for quick interactive queries, it creates fragile, hard-to-maintain code when used in application logic or saved queries.
-- Fragile: What does ORDER BY 3 mean? You have to count columns to find out.
SELECT name, price, stock_quantity, is_available
FROM products
ORDER BY 3 DESC;
-- Clear: The intent is immediately obvious
SELECT name, price, stock_quantity, is_available
FROM products
ORDER BY stock_quantity DESC;
The bigger problem is that if someone changes the column list in the SELECT clause (adds, removes, or rearranges columns), the ordinal positions shift and the query silently sorts by the wrong column without producing any error.
-- Original query: ORDER BY 3 sorts by stock_quantity
SELECT name, price, stock_quantity
FROM products
ORDER BY 3;
-- After adding a column: ORDER BY 3 now sorts by category_id!
SELECT name, price, category_id, stock_quantity
FROM products
ORDER BY 3;
-- Bug: Silently sorting by the wrong column
Use ordinal positions only for ad-hoc, throwaway queries. Always use column names in any code that will be saved, shared, or maintained.
Combining ORDER BY with WHERE
ORDER BY and WHERE work together seamlessly. The WHERE clause filters the rows first, and then ORDER BY sorts whatever rows remain.
SELECT name, price, stock_quantity
FROM products
WHERE is_available = true
AND price < 50
ORDER BY price ASC;
Output:
| name | price | stock_quantity |
|---|---|---|
| Stainless Water Bottle | 24.99 | 180 |
| Wireless Mouse | 29.99 | 150 |
| SQL for Beginners | 34.99 | 50 |
| Yoga Mat Premium | 38.00 | 100 |
| Data Science Handbook | 42.50 | 30 |
| USB-C Hub | 45.00 | 200 |
The database first filtered out unavailable products and those priced $50 or more. Then it sorted the remaining rows by price from cheapest to most expensive.
-- Most recent pending orders first
SELECT id, order_date, total_amount
FROM orders
WHERE status = 'pending'
ORDER BY order_date DESC;
Output:
| id | order_date | total_amount |
|---|---|---|
| 8 | 2024-04-10 | 199.98 |
| 7 | 2024-04-01 | 63.00 |
| 6 | 2024-03-20 | 34.99 |
SQL clauses must appear in a specific sequence. The database will throw a syntax error if you put them out of order.
-- Correct order
SELECT columns
FROM table
WHERE condition
ORDER BY column;
-- Wrong: ORDER BY before WHERE causes a syntax error
SELECT columns
FROM table
ORDER BY column
WHERE condition; -- ERROR!
Remember the order: SELECT → FROM → WHERE → ORDER BY. More clauses will be added to this sequence as you learn SQL, but these four always follow this relative order.
NULL Behavior in Sorting
When a column contains NULL values, you need to understand where those rows end up in sorted results. Unfortunately, different databases handle this differently.
Let us first add a customer with a NULL city to demonstrate (if you have not already from the previous guide):
-- Add a customer with no city (if not already present)
INSERT INTO customers VALUES
(7, 'Grace', 'Taylor', 'grace@email.com', NULL, '2024-04-01');
Now let us sort customers by city:
SELECT first_name, last_name, city
FROM customers
ORDER BY city ASC;
Default NULL Positioning by Database
| Database | NULL Position in ASC | NULL Position in DESC |
|---|---|---|
| PostgreSQL | Last | First |
| MySQL | First | Last |
| SQLite | First | Last |
| SQL Server | First | Last |
PostgreSQL output (NULLs last in ASC):
| first_name | last_name | city |
|---|---|---|
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
| Grace | Taylor | NULL |
MySQL/SQLite/SQL Server output (NULLs first in ASC):
| first_name | last_name | city |
|---|---|---|
| Grace | Taylor | NULL |
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
This inconsistency can cause unexpected behavior if you switch databases or if your application assumes a specific NULL position.
Controlling NULL Position with NULLS FIRST / NULLS LAST
PostgreSQL and SQLite (version 3.30+) support explicit NULL positioning using NULLS FIRST and NULLS LAST:
-- Force NULLs to appear first (PostgreSQL, SQLite 3.30+)
SELECT first_name, last_name, city
FROM customers
ORDER BY city ASC NULLS FIRST;
Output:
| first_name | last_name | city |
|---|---|---|
| Grace | Taylor | NULL |
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
-- Force NULLs to appear last (PostgreSQL, SQLite 3.30+)
SELECT first_name, last_name, city
FROM customers
ORDER BY city ASC NULLS LAST;
Output:
| first_name | last_name | city |
|---|---|---|
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
| Grace | Taylor | NULL |
Controlling NULL Position in MySQL and SQL Server
MySQL and SQL Server do not support NULLS FIRST / NULLS LAST syntax. Instead, you can use a CASE expression or other workarounds to control NULL placement:
-- MySQL / SQL Server: Push NULLs to the end in ascending sort
SELECT first_name, last_name, city
FROM customers
ORDER BY
CASE WHEN city IS NULL THEN 1 ELSE 0 END,
city ASC;
Output:
| first_name | last_name | city |
|---|---|---|
| Carol | Singh | Chicago |
| Frank | Wilson | Chicago |
| Bob | Martinez | Los Angeles |
| Alice | Johnson | New York |
| David | Chen | New York |
| Eva | Brown | Seattle |
| Grace | Taylor | NULL |
The CASE expression assigns 0 to non-NULL rows and 1 to NULL rows. Since 0 comes before 1, all non-NULL rows appear first. Then within those groups, the city ASC sort arranges them alphabetically.
If a column you are sorting by can contain NULL values, think about where you want those rows to appear. The default behavior varies between databases, so relying on the default can produce different results in different environments. Being explicit about NULL placement makes your queries portable and predictable.
Sorting by Columns Not in the SELECT List
A detail worth knowing: you can sort by a column even if it is not in your SELECT list. The database uses the column for ordering but does not include it in the visible output.
-- Sort by stock_quantity, but don't display it
SELECT name, price
FROM products
ORDER BY stock_quantity DESC;
Output:
| name | price |
|---|---|
| USB-C Hub | 45.00 |
| Stainless Water Bottle | 24.99 |
| Wireless Mouse | 29.99 |
| Yoga Mat Premium | 38.00 |
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| SQL for Beginners | 34.99 |
| Data Science Handbook | 42.50 |
| Coffee Maker Pro | 129.99 |
| Bluetooth Speaker | 65.00 |
The results are sorted by stock_quantity (highest first), but the stock_quantity column itself is not visible. USB-C Hub has 200 units (most), and Bluetooth Speaker has 0 (least).
This is useful when the sort column is relevant for ordering but not for display. However, it can make results confusing to read since the viewer cannot see why the rows are in that particular order. Use this technique thoughtfully.
Sorting by Expressions
You can sort by calculated expressions, not just raw column values. This allows powerful dynamic sorting:
-- Sort by total inventory value (price × stock)
SELECT name,
price,
stock_quantity,
price * stock_quantity AS inventory_value
FROM products
ORDER BY price * stock_quantity DESC;
Output:
| name | price | stock_quantity | inventory_value |
|---|---|---|---|
| USB-C Hub | 45.00 | 200 | 9000.00 |
| Mechanical Keyboard | 89.99 | 75 | 6749.25 |
| Running Shoes X1 | 110.00 | 60 | 6600.00 |
| Wireless Mouse | 29.99 | 150 | 4498.50 |
| Stainless Water Bottle | 24.99 | 180 | 4498.20 |
| Yoga Mat Premium | 38.00 | 100 | 3800.00 |
| Coffee Maker Pro | 129.99 | 25 | 3249.75 |
| SQL for Beginners | 34.99 | 50 | 1749.50 |
| Data Science Handbook | 42.50 | 30 | 1275.00 |
| Bluetooth Speaker | 65.00 | 0 | 0.00 |
You can also sort by the alias name in most databases:
-- Using the alias in ORDER BY (works in MySQL, PostgreSQL, SQLite)
SELECT name,
price * stock_quantity AS inventory_value
FROM products
ORDER BY inventory_value DESC;
Most databases (MySQL, PostgreSQL, SQLite) allow you to use a column alias in the ORDER BY clause. However, SQL Server does not always support this and may require you to repeat the full expression. When in doubt, repeat the expression or use an ordinal position.
Practical Exercises
Test your sorting skills with these exercises. Write each query yourself before checking the expected output.
Exercise 1
List all products sorted alphabetically by name.
SELECT name, price
FROM products
ORDER BY name ASC;
Expected output:
| name | price |
|---|---|
| Bluetooth Speaker | 65.00 |
| Coffee Maker Pro | 129.99 |
| Data Science Handbook | 42.50 |
| Mechanical Keyboard | 89.99 |
| Running Shoes X1 | 110.00 |
| SQL for Beginners | 34.99 |
| Stainless Water Bottle | 24.99 |
| USB-C Hub | 45.00 |
| Wireless Mouse | 29.99 |
| Yoga Mat Premium | 38.00 |
Exercise 2
List all orders sorted by total amount, with the largest orders first.
SELECT id, order_date, total_amount, status
FROM orders
ORDER BY total_amount DESC;
Expected output:
| id | order_date | total_amount | status |
|---|---|---|---|
| 8 | 2024-04-10 | 199.98 | pending |
| 5 | 2024-03-12 | 155.00 | shipped |
| 4 | 2024-03-05 | 129.99 | shipped |
| 1 | 2024-01-10 | 119.98 | completed |
| 2 | 2024-01-15 | 89.99 | completed |
| 3 | 2024-02-20 | 77.49 | completed |
| 7 | 2024-04-01 | 63.00 | pending |
| 6 | 2024-03-20 | 34.99 | pending |
Exercise 3
List available products sorted by category (ascending), then by price (descending) within each category.
SELECT name, category_id, price
FROM products
WHERE is_available = true
ORDER BY category_id ASC, price DESC;
Expected output:
| name | category_id | price |
|---|---|---|
| Mechanical Keyboard | 1 | 89.99 |
| USB-C Hub | 1 | 45.00 |
| Wireless Mouse | 1 | 29.99 |
| Data Science Handbook | 2 | 42.50 |
| SQL for Beginners | 2 | 34.99 |
| Coffee Maker Pro | 3 | 129.99 |
| Running Shoes X1 | 4 | 110.00 |
| Yoga Mat Premium | 4 | 38.00 |
| Stainless Water Bottle | 4 | 24.99 |
Exercise 4
List customers sorted by city (ascending, with NULLs last), then by signup date (most recent first) within each city.
-- PostgreSQL / SQLite 3.30+
SELECT first_name, last_name, city, signup_date
FROM customers
ORDER BY city ASC NULLS LAST, signup_date DESC;
Expected output:
| first_name | last_name | city | signup_date |
|---|---|---|---|
| Frank | Wilson | Chicago | 2024-02-28 |
| Carol | Singh | Chicago | 2023-06-10 |
| Bob | Martinez | Los Angeles | 2023-03-22 |
| David | Chen | New York | 2023-08-05 |
| Alice | Johnson | New York | 2023-01-15 |
| Eva | Brown | Seattle | 2024-01-18 |
| Grace | Taylor | NULL | 2024-04-01 |
Exercise 5
Find pending orders and sort them by total amount from smallest to largest.
SELECT id, order_date, total_amount
FROM orders
WHERE status = 'pending'
ORDER BY total_amount ASC;
Expected output:
| id | order_date | total_amount |
|---|---|---|
| 6 | 2024-03-20 | 34.99 |
| 7 | 2024-04-01 | 63.00 |
| 8 | 2024-04-10 | 199.98 |
Common Errors and Pitfalls
Sorting Before Filtering
-- Wrong: ORDER BY cannot come before WHERE
SELECT name, price
FROM products
ORDER BY price
WHERE price > 50;
-- ERROR: syntax error at or near "WHERE"
-- Correct: WHERE always comes before ORDER BY
SELECT name, price
FROM products
WHERE price > 50
ORDER BY price;
Using DESC Without a Column
-- Wrong: DESC needs to follow a specific column
SELECT name, price
FROM products
ORDER BY DESC price;
-- ERROR: syntax error
-- Correct: DESC comes after the column name
SELECT name, price
FROM products
ORDER BY price DESC;
Assuming Default Order Is Reliable
-- Dangerous assumption: "The rows always come back in insertion order"
SELECT name, price
FROM products;
-- The order might change after updates, deletes, vacuuming, or index changes
-- Safe: Always use ORDER BY when order matters
SELECT name, price
FROM products
ORDER BY id;
-- Now the order is guaranteed and predictable
This is worth emphasizing: SQL databases make absolutely no guarantee about the order of rows returned by a query without ORDER BY. Even if your results appear in a consistent order during development, that order can change at any time due to database maintenance, index creation, parallel query execution, or data modifications. If your application depends on rows being in a specific order, always use ORDER BY.
Key Takeaways
The ORDER BY clause gives you complete control over how query results are arranged. Here is what you should remember:
ORDER BYsorts query results and is placed afterWHEREASC(ascending) is the default sort direction: smallest first, A to Z, oldest firstDESC(descending) reverses the order: largest first, Z to A, newest first- Multiple columns create a sort hierarchy where each column breaks ties from the previous one
- Each column in a multi-column sort can have its own direction (
ASCorDESC) - Ordinal positions (
ORDER BY 2) reference the SELECT list by number, but should be avoided in production code because they are fragile and hard to read - NULL values sort differently across databases. Use
NULLS FIRST/NULLS LAST(PostgreSQL, SQLite) orCASEexpressions (MySQL, SQL Server) to control their placement - You can sort by columns not in the SELECT list and by calculated expressions
- Never rely on default row order. Without
ORDER BY, row order is undefined and unpredictable - The clause order is: SELECT → FROM → WHERE → ORDER BY
With SELECT, FROM, WHERE, logical operators, and now ORDER BY, you have the tools to write queries that retrieve exactly the data you want, filtered to your specifications, and presented in exactly the order you need. The next step is learning how to limit the number of rows returned, which combines powerfully with sorting to build features like "top 10 lists" and paginated results.