SQL ORDER BY and LIMIT: Sorting Query Results (2026)
⇅️ SQL Cluster — 3 of 15

SQL ORDER BY and LIMIT: Sorting Query Results (2026)

A "top 10 customers by revenue" report gets built with LIMIT 10 and no ORDER BY — and ships to a VP showing ten arbitrary customers, not the ten highest spenders. ORDER BY and LIMIT look simple, but the interaction between them, NULL sorting, and pagination hides real traps. This guide covers ASC/DESC, multi-column sorting, NULL ordering, why OFFSET pagination slows down on large tables, and syntax differences across databases.

by Khalid Hussain Published Jul 30, 2026 🕐 11 min read

Quick Answer: What Do ORDER BY and LIMIT Do?

▶ Direct Answer — ORDER BY & LIMIT

ORDER BY sorts a query's result rows by one or more columns, ascending by default or descending with DESC. LIMIT caps how many rows come back, and only produces a meaningful "top N" when it's paired with ORDER BY — without sorting first, LIMIT just returns an arbitrary slice. Logically, the database sorts the entire matching result set before LIMIT trims it, even though LIMIT is written at the very end of the query.

ASC
the default sort direction when none is specified
Smallest / earliest first
2nd
to last — where LIMIT sits in logical processing order
Runs after ORDER BY, before nothing
1M
rows scanned internally by OFFSET 1000000, even for a tiny page
The pagination trap

The Top-10 Report That Lied

A familiar mistake in early dashboards: someone builds a "top 10 customers by revenue" query as SELECT customer_id, revenue FROM customers LIMIT 10; — no ORDER BY. It runs without error, returns exactly 10 rows, and gets pasted straight into a report. The problem: those 10 rows are whatever the database happened to scan first, not the ten highest revenue customers. Nobody notices until someone cross-checks the numbers against a different tool and the top customer is missing entirely.

LIMIT trims rows. It has no opinion about which rows those are — that decision belongs entirely to ORDER BY. Skip the sort, and LIMIT just hands you an arbitrary, unstable sample that happens to have the right row count.

ASC and DESC: Setting the Sort Direction

ORDER BY takes a column name and an optional direction. Ascending (ASC) is the default — smallest number first, earliest date first, "A" before "Z."

sqlASC_DESC.SQL
-- smallest total first (ASC is implicit)
SELECT order_id, order_total
FROM orders
ORDER BY order_total;

-- highest total first
SELECT order_id, order_total
FROM orders
ORDER BY order_total DESC;

For a genuine "top N" query — top 10 customers, most recent 20 orders — DESC is almost always what you want, since "top" implies largest or most recent first. Leaving off DESC when you meant it is a quiet, easy-to-miss bug: the query runs fine and returns real rows, just the wrong end of the list.

Sorting by Multiple Columns

List more than one column in ORDER BY, separated by commas, and each column after the first acts as a tie-breaker — it only matters where the columns before it are equal.

sqlMULTI_COLUMN_SORT.SQL
-- group by country alphabetically, then highest spenders first within each country
SELECT customer_id, country, order_total
FROM orders
ORDER BY country ASC, order_total DESC;

Each column can have its own independent direction — mixing ASC and DESC in the same ORDER BY, like above, is completely normal and often exactly what a real report needs: alphabetical grouping with a meaningful sort inside each group.

Sorting by column position

ORDER BY 2 DESC sorts by the second column in the SELECT list instead of naming it — it works, but it's fragile. Reordering the SELECT list silently changes what the query sorts by. Naming columns explicitly in ORDER BY is safer for anything beyond a one-off, throwaway query.

Where NULLs Land When Sorting

NULL doesn't have a numeric or alphabetical value, so databases need a rule for where it goes in a sort — and that default rule is not the same everywhere.

DatabaseDefault ASC positionExplicit control
PostgreSQLNULLs lastNULLS FIRST / NULLS LAST
MySQLNULLs firstWorkaround with IS NULL in ORDER BY
BigQueryNULLs firstNULLS LAST supported
sqlNULLS_EXPLICIT.SQL
-- PostgreSQL / BigQuery: force NULLs to the bottom regardless of default
SELECT customer_id, last_purchase_date
FROM customers
ORDER BY last_purchase_date DESC NULLS LAST;

If a report depends on where missing values end up — for example, customers who never purchased shouldn't dominate the top of a "most recent purchase" list — never rely on the database default. Set NULLS FIRST or NULLS LAST explicitly wherever the syntax supports it.

LIMIT: Capping How Many Rows Come Back

LIMIT restricts the result set to a fixed number of rows, applied after sorting is complete.

sqlLIMIT_BASICS.SQL
-- true top 10 by revenue — ORDER BY makes LIMIT meaningful
SELECT customer_id, revenue
FROM customers
ORDER BY revenue DESC
LIMIT 10;

Even outside a top-N report, LIMIT is worth using habitually during exploration — pairing it with any new or unfamiliar query caps how much data you pull back while you're still checking whether the logic is right, which matters most exactly on the tables where a mistake would otherwise be expensive.

OFFSET and the Pagination Trap

OFFSET skips a number of rows before returning results — the standard way to build "page 2, page 3" pagination.

sqlOFFSET_PAGINATION.SQL
-- page 3, 20 rows per page: skip the first 40, return the next 20
SELECT order_id, order_date
FROM orders
ORDER BY order_date DESC
LIMIT 20 OFFSET 40;

The trap: OFFSET still has to scan and discard every skipped row internally before it can return the requested page. OFFSET 1000000 forces the database to process a million rows just to throw them away and hand you the next 20 — real, measurable cost on a large, deeply-paginated table.

Keyset pagination — the fix for deep pages

Instead of counting rows with OFFSET, filter on the last value you saw: WHERE order_date < '2026-01-15' ORDER BY order_date DESC LIMIT 20. The database can jump straight to that point using an index instead of scanning everything before it — dramatically faster once you're paging deep into a large result set.

LIMIT vs TOP vs FETCH FIRST

The concept of capping rows exists everywhere; the keyword and its position in the query differ by system.

DatabaseSyntaxPosition
PostgreSQL / MySQL / BigQueryLIMIT nEnd of query
SQL ServerTOP nImmediately after SELECT
Oracle / SQL:2008 standardFETCH FIRST n ROWS ONLYEnd of query, after ORDER BY

The standard SQL:2008 syntax, FETCH FIRST n ROWS ONLY, works on PostgreSQL too and is the most portable choice if a codebase genuinely needs to run against multiple database systems. For everyday work on Postgres, MySQL, or BigQuery, LIMIT is simpler and universally understood.

Mistakes That Give Away a Beginner

  • Using LIMIT without ORDER BY for a "top N" query — the exact bug from this article's opening. LIMIT alone gives you an arbitrary N rows, not the top N.
  • Forgetting DESC on a "most/highest/latest" query. ASC is the silent default — a query that runs fine but returns the smallest values when you meant the largest.
  • Relying on the default NULL sort position without checking it for the specific database in use, especially on reports where missing data shouldn't dominate the top of the list.
  • Deep OFFSET pagination on a large, frequently-paged table — works fine in testing on 500 rows, then gets measurably slower once the table and the page number both grow.
  • Sorting by column position (ORDER BY 2) in code meant to last — it silently breaks the moment someone reorders the SELECT list.

Practice: Write These Three Queries Yourself

Using the same orders table (order_id, customer_id, product_name, price, quantity, order_date, status), try writing these:

1

Build a real top-N

Return the 5 highest-priced orders, most expensive first.

2

Sort with a tie-breaker

Return all orders sorted by status alphabetically, and within each status, most recent order_date first.

3

Page through results

Return the second page of 10 orders (rows 11–20) sorted by order_date descending, using LIMIT and OFFSET.

Frequently Asked Questions

What is the default sort order in SQL ORDER BY?

Ascending (ASC) is the default when no direction is specified. ORDER BY order_total sorts smallest to largest, identical to ORDER BY order_total ASC. Use DESC explicitly to sort largest to first.

How do you sort by multiple columns in SQL?

List columns in ORDER BY separated by commas, each with its own optional direction, for example ORDER BY country ASC, order_total DESC. The database sorts by the first column, then uses each following column as a tie-breaker only where earlier columns are equal.

Does LIMIT run before or after ORDER BY in SQL?

LIMIT applies after ORDER BY has fully sorted the result set, even though LIMIT is written last in the query. Without ORDER BY, LIMIT returns an arbitrary, unstable slice of rows rather than a meaningful top-N.

Why is OFFSET pagination slow on large tables?

OFFSET still has to scan and discard every skipped row before returning the requested page, so a query with OFFSET 1000000 must process a million rows internally even though it returns only a handful. Keyset pagination, which filters on the last seen value instead of counting rows, avoids this cost.

Where do NULL values appear when sorting in SQL?

It varies by database. PostgreSQL treats NULLs as larger than any value by default, so they appear last in ascending order and first in descending order, and supports explicit NULLS FIRST or NULLS LAST. MySQL treats NULLs as smaller than any value, so they sort first in ascending order by default.

What is the difference between SQL LIMIT, TOP, and FETCH FIRST?

They accomplish the same goal with different syntax across database systems. PostgreSQL, MySQL, and BigQuery use LIMIT n after the query. SQL Server uses TOP n immediately after SELECT. Oracle and standard SQL:2008 syntax use FETCH FIRST n ROWS ONLY at the end of the query.

Conclusion: Sort First, Then Trim

ORDER BY and LIMIT are simple individually and easy to misuse together. The core rule to keep: LIMIT is only as meaningful as the ORDER BY behind it — without a real sort, LIMIT just samples arbitrary rows that happen to satisfy WHERE. Name your sort direction explicitly, treat NULL ordering as something to set deliberately rather than assume, and reach for keyset pagination instead of a growing OFFSET once you're paging deep into a large table.

Practice the three queries above against a real table, then move to the next article in this cluster, which covers SQL data types — what every column you're sorting and filtering is actually storing under the hood.

🗃️ Continue the SQL Cluster

Next Up: SQL Data Types Explained

Sorting and filtering behave differently depending on what a column actually stores. Continue the fundamentals tier of this cluster.

▶  Read the SQL Data Types Guide