SQL ORDER BY and LIMIT: Sorting, Top-N and Pagination
On this page 10 sections ▾
Rows in a table have no order. That is not a detail — it is the whole reason ORDER BY exists. A query without it returns rows in whatever sequence was cheapest for the database to produce, which may look sorted today and come back shuffled tomorrow after an index changes.
This page covers how sorting actually works, then pairs it with LIMIT to answer the two questions that follow: give me the top few, and give me page four. Everything runs against the sample database.
Sorting one column
Ascending is the default, so these two are the same query:
SELECT first_name, salary
FROM Employees
ORDER BY salary;
| first_name | salary |
|---|---|
| Omar | 61000 |
| Daniel | 68000 |
| Hiroshi | 74500 |
| Sofia | 79000 |
| Grace | 89500 |
| Isabela | 92000 |
| Leila | 96000 |
| Priya | 105000 |
| Marcus | 118000 |
| Tomas | 131000 |
| Ananya | 142000 |
| Chen | 156000 |
● 12 rows · produced by running this query on the sample database
Add DESC for highest first, which is what you want far more often in reports:
SELECT first_name, salary
FROM Employees
ORDER BY salary DESC;
| first_name | salary |
|---|---|
| Chen | 156000 |
| Ananya | 142000 |
| Tomas | 131000 |
| Marcus | 118000 |
| Priya | 105000 |
| Leila | 96000 |
| Isabela | 92000 |
| Grace | 89500 |
| Sofia | 79000 |
| Hiroshi | 74500 |
| Daniel | 68000 |
| Omar | 61000 |
● 12 rows · produced by running this query on the sample database
ASC and DESC attach to each column separately, not to the whole clause — a point that matters as soon as you sort by more than one thing.
Sorting by several columns
The second column breaks ties in the first, the third breaks ties in the second, and so on:
SELECT first_name, dept_id, salary
FROM Employees
ORDER BY dept_id ASC, salary DESC;
| first_name | dept_id | salary |
|---|---|---|
| Ananya | 10 | 142000 |
| Marcus | 10 | 118000 |
| Leila | 10 | 96000 |
| Sofia | 10 | 79000 |
| Omar | 10 | 61000 |
| Tomas | 20 | 131000 |
| Priya | 20 | 105000 |
| Grace | 20 | 89500 |
| Hiroshi | 20 | 74500 |
| Daniel | 30 | 68000 |
| Chen | 50 | 156000 |
| Isabela | 50 | 92000 |
● 12 rows · produced by running this query on the sample database
Departments in ascending order; within each department, the best paid first. Swap the two and you get a completely different report from the same data — the order of the columns in ORDER BY is the order of priority.
A tie with no tie-breaker is genuinely unpredictable
If you sort only by department and two people share one, their relative order is not defined and can change between runs. Whenever the sort must be stable — and it must be for pagination — end the ORDER BY with something unique, such as a primary key.
Sorting by an expression or an alias
You can sort by anything you can compute, whether or not it is in the SELECT list:
SELECT first_name, salary
FROM Employees
ORDER BY salary * 12 DESC;
| first_name | salary |
|---|---|
| Chen | 156000 |
| Ananya | 142000 |
| Tomas | 131000 |
| Marcus | 118000 |
| Priya | 105000 |
| Leila | 96000 |
| Isabela | 92000 |
| Grace | 89500 |
| Sofia | 79000 |
| Hiroshi | 74500 |
| Daniel | 68000 |
| Omar | 61000 |
● 12 rows · produced by running this query on the sample database
And unlike WHERE, ORDER BY can use a column alias, because it runs after the SELECT list has been evaluated:
SELECT first_name, salary * 12 AS annual_pay
FROM Employees
ORDER BY annual_pay DESC;
| first_name | annual_pay |
|---|---|
| Chen | 1872000 |
| Ananya | 1704000 |
| Tomas | 1572000 |
| Marcus | 1416000 |
| Priya | 1260000 |
| Leila | 1152000 |
| Isabela | 1104000 |
| Grace | 1074000 |
| Sofia | 948000 |
| Hiroshi | 894000 |
| Daniel | 816000 |
| Omar | 732000 |
● 12 rows · produced by running this query on the sample database
That asymmetry surprises people: WHERE annual_pay > 1000000 fails, while ORDER BY annual_pay works. Both follow from the clause order — WHERE is evaluated before SELECT, ORDER BY after it.
Sorting by column position also works, and is best avoided:
SELECT first_name, dept_id
FROM Employees
ORDER BY 2, 1;
| first_name | dept_id |
|---|---|
| Ananya | 10 |
| Leila | 10 |
| Marcus | 10 |
| Omar | 10 |
| Sofia | 10 |
| Grace | 20 |
| Hiroshi | 20 |
| Priya | 20 |
| Tomas | 20 |
| Daniel | 30 |
| Chen | 50 |
| Isabela | 50 |
● 12 rows · produced by running this query on the sample database
ORDER BY 2, 1 means “by the second column, then the first”. It saves typing and breaks silently the moment somebody reorders the SELECT list, which is why most style guides ban it.
Where NULLs end up
NULL is not a value, so it has no natural place in a sort order — and the standard leaves the choice to the database:
SELECT first_name, manager_id
FROM Employees
ORDER BY manager_id;
| first_name | manager_id |
|---|---|
| Ananya | NULL |
| Tomas | NULL |
| Priya | NULL |
| Sofia | NULL |
| Chen | NULL |
| Marcus | 1 |
| Leila | 1 |
| Grace | 4 |
| Hiroshi | 4 |
| Daniel | 7 |
| Omar | 9 |
| Isabela | 11 |
● 12 rows · produced by running this query on the sample database
| Database | NULLs appear | Override |
|---|---|---|
| PostgreSQL | last | NULLS FIRST / NULLS LAST |
| Oracle | last | NULLS FIRST / NULLS LAST |
| MySQL | first | sort by an expression, or use IS NULL |
| SQL Server | first | sort by an expression, or use IS NULL |
| SQLite | first | NULLS FIRST / NULLS LAST (3.30+) |
If the placement matters, do not rely on the default. The portable trick is to sort by a flag first:
SELECT first_name, manager_id
FROM Employees
ORDER BY CASE WHEN manager_id IS NULL THEN 1 ELSE 0 END,
manager_id;
| first_name | manager_id |
|---|---|
| Marcus | 1 |
| Leila | 1 |
| Grace | 4 |
| Hiroshi | 4 |
| Daniel | 7 |
| Omar | 9 |
| Isabela | 11 |
| Ananya | NULL |
| Tomas | NULL |
| Priya | NULL |
| Sofia | NULL |
| Chen | NULL |
● 12 rows · produced by running this query on the sample database
That puts the rows with a value first and the NULLs after them on every database, because you have made the rule explicit instead of inheriting one.
LIMIT: the top few rows
Sort, then take the first n. This is the whole top-N pattern:
SELECT first_name, salary
FROM Employees
ORDER BY salary DESC
LIMIT 3;
| first_name | salary |
|---|---|
| Chen | 156000 |
| Ananya | 142000 |
| Tomas | 131000 |
● 3 rows · produced by running this query on the sample database
LIMIT without ORDER BY is a coin toss
SELECT * FROM Employees LIMIT 3 returns three rows, but which three is not defined. It is a useful way to peek at a table’s shape and a bug in anything that reports a “top 3”.
OFFSET and pagination
OFFSET skips rows before LIMIT starts counting, which is how a page of results is fetched:
SELECT first_name, salary
FROM Employees
ORDER BY salary DESC, emp_id
LIMIT 4 OFFSET 4;
| first_name | salary |
|---|---|
| Priya | 105000 |
| Leila | 96000 |
| Isabela | 92000 |
| Grace | 89500 |
● 4 rows · produced by running this query on the sample database
Rows five to eight — page two, at four rows per page. The general form is LIMIT page_size OFFSET (page_number - 1) * page_size. Note the emp_id at the end of the ORDER BY: without a unique tie-breaker, a row can appear on two pages or on none, because the database is free to order tied rows differently on each query.
The same mechanism answers “the second highest salary” — skip one, take one — which is covered in full on the second highest salary page.
OFFSET gets slower the deeper you go
To return rows 100,001 to 100,020 the database generally has to produce and discard the first 100,000. For deep pagination, remember the last value you saw and filter on it instead — WHERE salary < :last_salary ORDER BY salary DESC LIMIT 20. This is called keyset pagination, and it stays fast at any depth.
The syntax is different on every database
Row limiting is the least portable part of everyday SQL:
| Database | Syntax |
|---|---|
| PostgreSQL, MySQL, SQLite | ORDER BY salary DESC LIMIT 3 |
| SQL Server | SELECT TOP 3 ... ORDER BY salary DESC |
| SQL Server / Oracle 12c+ (standard) | ORDER BY salary DESC FETCH FIRST 3 ROWS ONLY |
| Oracle 11g and earlier | wrap the query and filter on ROWNUM <= 3 |
The engine in this playground accepts both LIMIT and TOP:
SELECT TOP 3 first_name, salary
FROM Employees
ORDER BY salary DESC;
| first_name | salary |
|---|---|
| Chen | 156000 |
| Ananya | 142000 |
| Tomas | 131000 |
● 3 rows · produced by running this query on the sample database
The dialect differences page lists these side by side along with the date and string functions, which are the other places portable SQL stops being portable.
Practice this topic
Reading explains the idea; producing it yourself is what makes it stick. These exercises use exactly what this page covers, and your answer is checked by running it against the same sample database:
- Biggest departments firstMedium
- Big spendersHard
- Orders by monthMedium
- Look at the whole product catalogueEasy
Frequently asked questions
What does ORDER BY do in SQL?
It sorts the rows a query returns. Without it, rows come back in whatever order the database found cheapest to produce, which is not guaranteed and can change over time. ORDER BY column sorts ascending by default; add DESC for descending.
How do I sort by two columns in SQL?
List them separated by commas: ORDER BY dept_id ASC, salary DESC. The first column is the primary sort and each later column breaks ties in the one before it. ASC and DESC apply to each column individually, not to the whole clause.
Can I use a column alias in ORDER BY?
Yes. ORDER BY runs after the SELECT list is evaluated, so the alias already exists. WHERE runs before it, which is why the same alias fails there and you have to repeat the expression.
How do I get the top 10 rows in SQL?
ORDER BY the column that defines "top" and add LIMIT 10 - or SELECT TOP 10 on SQL Server, or FETCH FIRST 10 ROWS ONLY on Oracle 12c and newer. The ORDER BY is essential: LIMIT without it returns an arbitrary ten rows.
What is the difference between LIMIT and OFFSET?
LIMIT says how many rows to return; OFFSET says how many to skip first. LIMIT 10 OFFSET 20 returns rows 21 to 30, which is the third page at ten rows per page.
Where do NULL values appear when sorting?
It depends on the database: PostgreSQL and Oracle put them last when sorting ascending, while MySQL, SQL Server and SQLite put them first. Use NULLS FIRST or NULLS LAST where supported, or sort by a CASE expression that makes the rule explicit and works everywhere.
Related reading
About the author
SQL Practice
SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.