The previous lessons showed how to pick the rows you want. Often the next step is presenting them in a useful order — biggest first, newest first — and stopping once you have enough. SOQL has two clauses for that, both of them short.
ORDER BY comes after WHERE (and after GROUP BY once you learn it) and tells SOQL how to order the rows it returns. The default is ascending order; DESC flips it:
List<Account> top = [SELECT Name, AnnualRevenue FROM Account
WHERE AnnualRevenue != null
ORDER BY AnnualRevenue DESC];
The previous query returns Accounts sorted from highest revenue down to lowest. Without the DESC, the same query returns them smallest first — both are valid answers, just different questions.
You can sort on more than one field. SOQL applies the first field, then breaks ties with the second, then with the third, and so on:
List<Account> byCountry = [SELECT Name, BillingCountry, AnnualRevenue FROM Account
ORDER BY BillingCountry ASC, AnnualRevenue DESC];
The previous query groups Accounts by country (ASC means A–Z), and within each country puts the highest-revenue Account first. The pattern — one coarse key, one fine key — is how you build leaderboards and listings that read naturally.
When the result of the query is a single aggregate row instead of a list of records, ORDER BY is restricted: every field in ORDER BY must also appear in GROUP BY or inside an aggregate. For plain field-and-row queries, this restriction does not apply and ORDER BY works on any selected field.
LIMIT caps the size of the returned list. It always comes at the end of the query:
List<Account> topFive = [SELECT Name, AnnualRevenue FROM Account
WHERE AnnualRevenue != null
ORDER BY AnnualRevenue DESC
LIMIT 5];
LIMIT on its own is undefined — without an ORDER BY you are picking five rows at random, which is rarely what you want. Always pair the two: sort first, then take the top N.
The combination is the leaderboard pattern. It is also the cheapest way to ask "is there at least one row that matches?" — SELECT Id FROM ... LIMIT 1 runs in a fraction of the time of a full count, and the question is often all you actually need answered.
LIMIT accepts a bind variable, the same colon-syntax you already use in WHERE:
Integer n = 10; List<Account> top = [SELECT Name FROM Account ORDER BY AnnualRevenue DESC LIMIT :n];
When the cap comes from a parameter, dynamic-limit queries stay safe from SOQL injection — the bind keeps the value as data, not as query text.
LIMIT 0 is a valid edge case: the query returns an empty list, not an error. Dynamic code that builds a list of top-N names should always handle the empty case before reading result[0].
ORDER BY treats null as smaller than every real value in ascending order, so the first rows you see are the populated ones and the blanks drag behind. With DESC the rows flip, but the nulls go to the end in both directions. Filter null out in WHERE when the order matters for the displayed list, like ranking or top-N reports.
A frequent slip is LIMIT without ORDER BY. The query runs, returns five rows, and they look like nothing in particular — the answer is technically correct but useless.
A second slip is sorting on a field that is not in the SELECT list. SOQL accepts it, but you end up with rows you cannot interpret (you know the sort order but cannot see the value driving it). Select every field you want to order on.
A third slip is hardcoding LIMIT instead of binding it. LIMIT 1 works when the caller always asks for 1, but the moment the caller asks for 3, the query still returns one row and your list is the wrong size. Use LIMIT :limitValue so the parameter actually drives the cap.
A fourth slip is forgetting that LIMIT 0 returns an empty list, not an error and not a null. result[0] on an empty list throws, so dynamic code that takes the top row should always check isEmpty() first.
Sorting and pagination are the shape of every "show me the data" UI. Reports, dashboards, leaderboards, batch job monitors, and search results all assume the query can return rows in a defined order and stop early. Without ORDER BY and LIMIT you are stuck reading every matching row just to throw most of them away.