Beginner

SQL ORDER BY: Sorting Query Results the Right Way

Result order is not guaranteed unless you ask for it. Learn ASC/DESC, sorting by multiple columns, NULLS FIRST/LAST, sorting by expressions, and pairing ORDER BY with LIMIT.

A SQL result set has no inherent order. The database is free to return rows in whatever sequence is fastest unless you explicitly ask for an order with ORDER BY. If you have ever seen rows "change order" between runs, this is why. Keep the SQL cheat sheet handy as you read.

Why ORDER BY Matters

ORDER BY is the only thing that guarantees the order of your output. It runs near the very end of a query — after WHERE, GROUP BY, and HAVING — so it can sort by any column or computed value in the SELECT.

-- Newest orders first
SELECT id, created_at, total
FROM orders
ORDER BY created_at DESC;

ASC & DESC

Sorting is ascending (ASC) by default — smallest to largest, A to Z, oldest to newest. Add DESC to reverse it. You can set the direction per column.

SELECT name, total FROM orders ORDER BY total;       -- low to high (default)
SELECT name, total FROM orders ORDER BY total DESC;  -- high to low

Sorting by Multiple Columns

List several columns to break ties. The database sorts by the first column, and only uses the second column when the first is equal — like sorting a contact list by last name, then first name.

-- Group by status alphabetically, newest first within each status
SELECT id, status, created_at
FROM orders
ORDER BY status ASC, created_at DESC;

Sorting by Expressions

You can sort by a computed value, an alias, or even a CASE expression for custom orderings that do not follow alphabetical or numeric rules.

-- Sort by a computed net value
SELECT id, total - COALESCE(discount, 0) AS net
FROM orders
ORDER BY net DESC;

-- Force a custom status order with CASE
SELECT id, status FROM orders
ORDER BY CASE status
  WHEN 'pending' THEN 1
  WHEN 'paid'    THEN 2
  ELSE 3
END;

Where NULLs Sort

In PostgreSQL, NULLs sort as if they were the largest value: last in ascending order, first in descending. Override that explicitly with NULLS FIRST or NULLS LAST when it matters.

-- Put orders without a ship date at the bottom
SELECT id, shipped_at
FROM orders
ORDER BY shipped_at DESC NULLS LAST;

ORDER BY with LIMIT

LIMIT only makes sense alongside ORDER BY. Together they answer "top N" questions — the highest-value orders, the most recent signups. Without ORDER BY, LIMIT returns an arbitrary handful of rows.

-- Top 5 highest-value orders
SELECT id, total
FROM orders
ORDER BY total DESC
LIMIT 5;

Common Mistakes & Tips

  • Assuming rows come back sorted without ORDER BY — order is never guaranteed otherwise
  • Using LIMIT without ORDER BY — you get an unpredictable subset
  • Forgetting NULLs sort last (ASC) / first (DESC) in PostgreSQL — use NULLS FIRST/LAST to control it
  • Sorting by column position (ORDER BY 2) then editing the SELECT list and breaking the sort
  • Trying to get top-N-per-group with ORDER BY alone — use ROW_NUMBER instead

ORDER BY shapes how every report and leaderboard reads. Practice sorting on real datasets to get a feel for multi-column and custom orderings.