Readineer
PATTERN 3SQL Patterns / Foundations

SQL ORDER BY and LIMIT: sorting and returning top results correctly

Learn how to sort query results with ORDER BY, control direction with ASC and DESC, limit rows with LIMIT or TOP, and make Top-N queries deterministic when values tie.

8 min read14 sections2 predictions
01Foundation

Query results have no guaranteed order

A table may contain rows in a way that appears naturally ordered. For example, consider the following orders table:

Orders
order_idcustomerproductquantityunit_pricestatus
1001AshaWireless Mouse2750paid
1002BenMechanical Keyboard13200pending
1003CarlaLaptop Sleeve31200paid
1004DevOffice Chair18500cancelled
1005AshaWireless Mouse1750paid
1006BenMonitor Stand21800paid

If we run SELECT order_id, customer, product FROM orders; the result might appear in order_id order. It is tempting to assume that SQL will always return the rows this way. It will not.

Unless a query explicitly uses ORDER BY, the order of returned rows is not guaranteed. A database may return rows differently depending on its execution plan, indexes, storage layout, or other implementation details.

Key idea
So if the order of rows matters to the requirement, request that order explicitly.
02Sorting rows

Sorting rows with ORDER BY

The ORDER BY clause determines how rows should be arranged in the result. Suppose we want orders sorted alphabetically by customer:

Query
SELECT
    order_id,
    customer,
    product
FROM orders
ORDER BY customer;
Result
order_idcustomerproduct
1001AshaWireless Mouse
1005AshaWireless Mouse
1002BenMechanical Keyboard
1006BenMonitor Stand
1003CarlaLaptop Sleeve
1004DevOffice Chair

A useful way to read this is: return the orders, then arrange the result by customer.

ASC: ascending order

ASC means ascending. For numbers, this normally means smaller values first.

Query
SELECT
    order_id,
    customer,
    unit_price
FROM orders
ORDER BY unit_price ASC;
Result
order_idcustomerunit_price
1001Asha750
1005Asha750
1003Carla1200
1006Ben1800
1002Ben3200
1004Dev8500

The values increase from the lowest price to the highest. For text, ascending order usually corresponds to the ordering rules defined by the database’s collation. ASC is the default direction, so ORDER BY unit_price and ORDER BY unit_price ASC express the same ordering. Even though ASC can be omitted, writing it explicitly can sometimes make a multi-column sort easier to read.

DESC: descending order

DESC reverses the direction. Suppose we want the most expensive unit prices first:

Query
SELECT
    order_id,
    customer,
    unit_price
FROM orders
ORDER BY unit_price DESC;
Result
order_idcustomerunit_price
1004Dev8500
1002Ben3200
1006Ben1800
1003Carla1200
1001Asha750
1005Asha750
ASC  → lower to higher
DESC → higher to lower

For business questions, the direction usually comes directly from the wording. “Cheapest products first” suggests ORDER BY unit_price ASC, while “most expensive products first” suggests ORDER BY unit_price DESC.

03Resolving ties

Sorting by more than one column

What happens when several rows have the same value in the first sort column? ORDER BY customer tells SQL how Asha should compare with Ben. It does not tell SQL how Asha’s two rows should be ordered relative to each other. We can provide a second sort condition:

customer ASC, order_id ASC
SELECT
    order_id,
    customer
FROM orders
ORDER BY
    customer ASC,
    order_id ASC;
Result
order_idcustomer
1001Asha
1005Asha
1002Ben
1006Ben
1003Carla
1004Dev

SQL first sorts by customer, and when two rows have the same customer, it sorts those rows by order_id. Official PostgreSQL documentation describes later ORDER BY expressions as the values used to sort rows that are equal according to earlier expressions. You can also mix directions:

customer ASC, order_id DESC
ORDER BY
    customer ASC,
    order_id DESC;
Result
order_idcustomer
1005Asha
1001Asha
1006Ben
1002Ben
1003Carla
1004Dev

This means: sort customers alphabetically, and within each customer, put the larger order_id first. Each sort column has its own direction.

04Sorting derived values

Sorting calculated values

ORDER BY is not limited to values stored directly in the table. Recall that the total amount of an order can be calculated as quantity * unit_price. We can give this value an alias and then sort by it:

Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY order_amount DESC;
Result
order_idcustomerorder_amount
1004Dev8500
1003Carla3600
1006Ben3600
1002Ben3200
1001Asha1500
1005Asha750

Notice that orders 1003 and 1006 both have an amount of 3600. We will return to that tie shortly.

05Limiting rows

Limiting the number of rows with LIMIT

Sometimes we do not need every row in the result. Suppose we want only the three highest-value orders. Using SQL dialects that support LIMIT, we can write:

Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY order_amount DESC
LIMIT 3;
Result
order_idcustomerorder_amount
1004Dev8500
1003Carla3600
1006Ben3600

ORDER BY determines the sequence. LIMIT 3 then restricts the result to at most three rows. PostgreSQL documents LIMIT as returning only a portion of the rows produced by the rest of the query. This combination is commonly used for questions such as top 10 highest-value orders, five cheapest products, ten largest transactions, or three lowest scores.

LIMIT without ORDER BY does not mean Top-N

Consider SELECT order_id, customer FROM orders LIMIT 3;. This means “return at most three rows.” It does not mean “return the first three orders,” nor “the oldest three,” nor “the three lowest order IDs.” There is no requested ordering, so the database is free to return any qualifying subset. PostgreSQL explicitly recommends using ORDER BY with LIMIT when a predictable subset matters.

If the requirement is “return the three lowest order IDs,” write ORDER BY order_id ASC LIMIT 3. The requirement now defines both which rows come first and how many rows should be returned.

SQL Server: using TOP

SQL Server commonly uses TOP instead of the LIMIT syntax shown above. The equivalent SQL Server query for the three highest-value orders is:

Query
SELECT TOP (3)
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY order_amount DESC;

SQL Server places TOP directly after SELECT, while ORDER BY still determines which rows count as the top rows. Microsoft recommends using ORDER BY with TOP when you need to predictably identify which rows TOP affects. So the ideas are the same even though the syntax differs: LIMIT 3 or TOP (3) restricts the number of returned rows, and ORDER BY determines which rows belong at the beginning of that result.

06The featured failure

The Top-N query that is not deterministic

Consider this requirement: return the two highest-value orders. We write the query below. At first glance, this looks completely correct. Let us examine the sorted values:

sorted by order_amount DESC
order_idcustomerorder_amount
1004Dev8500
1003Carla3600
1006Ben3600
1002Ben3200
1001Asha1500
1005Asha750

The highest value is clear: 1004 | Dev | 8500. But the next highest amount is 3600, and two orders have that value: 1003 | Carla | 3600 and 1006 | Ben | 3600. The query says ORDER BY order_amount DESC, but it gives SQL no rule for deciding which 3600 row should come first. Then LIMIT 2 cuts the result after the second row.

PAUSE & PREDICTWhich result is guaranteed?
Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY order_amount DESC
LIMIT 2;
Your prediction
07The fix

Add a tie-breaker

To make a Top-N query deterministic, define what should happen when the main sort values tie. Suppose our rule is: higher order amounts first, and if two orders have the same amount, use the smaller order_id first. We can write:

Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY
    order_amount DESC,
    order_id ASC
LIMIT 2;
Result
order_idcustomerorder_amount
1004Dev8500
1003Carla3600

The two 3600 rows no longer tie because order_id resolves the tie. Since order_id uniquely identifies an order, the complete ordering is unique. This is a deterministic Top-N query. The same principle applies whether you use LIMIT, TOP, or another row-limiting syntax.

The tie-breaker should come from the requirement

Do not automatically add ORDER BY amount DESC, order_id ASC to every Top-N query simply because order_id is available. Ask what the business actually wants. Suppose the requirement is “return the two largest orders; if values tie, prefer the newest order.” Then the correct secondary sort should represent recency, and if ordered_at can also tie, you may still need a final unique column:

Query
ORDER BY
    order_amount DESC,
    ordered_at DESC,
    order_id ASC
Key idea
Sort first by business priority, then add enough tie-breakers to make the final ordering unique.
08A different requirement

Top-N with ties is a different requirement

Sometimes you do not want to break a tie. Consider “return the two highest order amounts, but include every order tied at the cutoff.” The second-highest amount is 3600, and two orders share it. If the business requirement says both tied orders should be included, the correct result contains three rows even though the requested rank boundary is two. Some database systems provide syntax for this behaviour; for example, SQL Server supports TOP (...) WITH TIES when used with ORDER BY.

SQL Server
SELECT TOP (2) WITH TIES
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY order_amount DESC;
This can return
order_idcustomerorder_amount
1004Dev8500
1003Carla3600
1006Ben3600

The important distinction is between two different requirements: “return exactly two rows” and “return the top two values, including all ties.” Those are not always the same problem.

09Gotchas

Sorting NULL values, and two more traps

The previous article introduced NULL. Sorting nullable columns introduces another question: should missing values appear first or last? Suppose discount_amount contains 150, NULL, 300, NULL, NULL, 200. A query such as ORDER BY discount_amount ASC does not behave identically across every SQL database. For example, PostgreSQL and SQL Server have different default placement rules for NULL values. PostgreSQL also supports explicit NULLS FIRST and NULLS LAST syntax:

Query
ORDER BY discount_amount ASC NULLS LAST;
ORDER BY discount_amount DESC NULLS FIRST;
Key idea
If the placement of NULL values matters, make that rule explicit using the syntax supported by your database rather than depending on a default you may not remember correctly.

Sorting one column does not order ties

Consider ORDER BY status ASC. Suppose several orders have status = paid. SQL guarantees where the paid group appears relative to other statuses according to the database’s text ordering. It does not guarantee the order of rows inside that group. If that matters, add another sort, such as ORDER BY status ASC, order_id ASC. This idea becomes especially important when the result is limited, paginated, exported repeatedly, compared between runs, or used by another application. The more stable the output needs to be, the more important a complete ordering becomes.

ORDER BY position numbers are hard to maintain

Some databases allow queries such as ORDER BY 3 DESC, where 3 means the third selected column. The query may be valid, but it is less clear than ORDER BY unit_price DESC. If someone changes the column order later, the meaning of ORDER BY 3 can also change. Microsoft’s SQL Server documentation specifically recommends avoiding positional integers for this reason. For readable production SQL, prefer column names or meaningful aliases.

10Recognition cues

How to spot sorting and limiting problems

Look for these signs:

A query has LIMIT or TOP but no ORDER BY.
The requirement says “highest,” “lowest,” “latest,” “earliest,” or “first,” but the query does not sort.
A Top-N query orders by a column that contains duplicate values.
The same query sometimes returns different rows around the cutoff.
Pagination shows a row on one page during one run and another page during another.
A query sorts by one column, but several rows share the same value and their internal order matters.
A report relies on the order rows happened to arrive from the database.
ORDER BY 1 or ORDER BY 3 makes it difficult to understand what controls the sort.
NULL values unexpectedly appear at the beginning or end of a sorted result.

When you see one of these, inspect the complete ORDER BY clause.

11A practical mental model

Break an ordered requirement into three questions

1 · What determines priority?

“Highest-value orders first” → ORDER BY order_amount DESC.

2 · What happens when values tie?

“If amounts tie, smaller order ID first” → … order_id ASC.

3 · How many rows should be returned?

LIMIT 5.

Query
SELECT
    order_id,
    customer,
    quantity * unit_price AS order_amount
FROM orders
ORDER BY
    order_amount DESC,
    order_id ASC
LIMIT 5;
Key idea
ORDER BY determines which rows come first. LIMIT or TOP determines how many of those rows are returned.
12Check your understanding

One more prediction

Consider these order amounts:

orders
order_idorder_amount
10011500
10023200
10033600
10048500
1005750
10063600

Requirement: return exactly two orders with the highest amounts; if amounts tie, return the smaller order ID first. Which query correctly expresses the requirement?

ONE MORE PREDICTIONWhich query correctly expresses the requirement?
Query
-- Option A
SELECT order_id, order_amount FROM orders
LIMIT 2;

-- Option B
SELECT order_id, order_amount FROM orders
ORDER BY order_amount DESC
LIMIT 2;

-- Option C
SELECT order_id, order_amount FROM orders
ORDER BY
    order_amount DESC,
    order_id ASC
LIMIT 2;

-- Option D
SELECT order_id, order_amount FROM orders
ORDER BY order_id ASC
LIMIT 2;
Your prediction
13Quick check

Lock these in

What does ORDER BY do?

It specifies how rows should be arranged in the query result.

What is the default sort direction?

ASC.

What does DESC do?

It sorts in the opposite direction, commonly from higher values to lower values.

Why use multiple ORDER BY columns?

Later columns resolve ties created by earlier columns.

Is LIMIT without ORDER BY a Top-N query?

Not in a meaningful business sense. It limits the number of rows without defining which rows should come first.

What makes a Top-N query deterministic?

An ORDER BY clause that ultimately establishes a unique ordering for the rows.

14Summary

Summary

ORDER BY controls the order of rows: ORDER BY unit_price ASC puts lower values first, and ORDER BY unit_price DESC puts higher values first. When the first sort column contains ties, additional columns can resolve them. LIMIT restricts the number of rows in databases that support that syntax, and SQL Server commonly uses TOP (3) for the same general purpose. But limiting rows is meaningful only after you have defined which rows should come first.

Key idea
A reliable Top-N query needs an ordering that fully expresses how rows should be ranked, including what should happen when values tie. ORDER BY order_amount DESC LIMIT 2 may still be ambiguous if several orders have the same amount at the cutoff; adding a business-appropriate tie-breaker makes the result deterministic. Do not rely on the order rows happen to arrive in. If order matters, specify it. If only some rows should be returned, specify how they are ranked first.

Next, we will move from individual rows to summarising many rows together using GROUP BY, COUNT, SUM, AVG, MIN, MAX, and HAVING.

Go deeper

Ask Colearn about this pattern

Not sure why your Top-N keeps returning a different row, or where your NULL values land? Ask about the concept, or paste a simplified version of your query.

Ask Colearn

3 of 3 free questions left

Instant answers, grounded in the same verified material the diagnostic grades against.

Why do my LIMIT results change between runs?

LIMIT with no ORDER BY returns whatever rows the scan produced, which can change when the plan or data changes.

Always pair LIMIT with an ORDER BY that fully determines the order.

from the unit →
Why does my top-10 cut off rows that tie for tenth?

A plain top-N slices exactly at the row count, cutting through any rows tied at the boundary arbitrarily.

Keep ties explicitly (with a with-ties option or by ranking) when the peers should be included.

from the unit →
Why does OFFSET pagination skip or repeat rows?

Offset paging re-runs the query for each page, so rows inserted or deleted between requests shift the offsets, skipping or duplicating items.

Use keyset pagination (page by the last seen key) for a stable feed.

from the unit →
Do NULLs sort first or last?

It depends on the database — some sort missing values first, some last.

Say it explicitly with a nulls-first or nulls-last clause when the position matters.

from the unit →

That’s the free taste

Two ways to go deeper.

Don’t paste credentials, personal data, or confidential production records.

Related patterns

P01Pattern · 8 minFiltering rows with WHEREHow AND runs before OR, and how a filter returns the wrong rows without erroring.P02Pattern · 8 minNULL & UNKNOWNWhy a comparison to a missing value is UNKNOWN, and how it silently drops rows.P04Pattern · 6 minAggregation & GROUP BYWhy COUNT skips the blanks and a total quietly reads low.
Up next · Pattern 4
Ready to keep going?

Next, we move from individual rows to summarising many at once with GROUP BY, COUNT, SUM, AVG, and HAVING.

Continue to P4 →Browse all patterns