Skip to main content
New tool CRON Expression Builder — preview next run times before you schedule Apex. Open the builder →
Apex

SOQL ORDER BY: Sort, NULLS FIRST/LAST & Multi-Column Examples

Every SOQL ORDER BY pattern in one place — ASC/DESC, multi-column priority, NULLS FIRST vs NULLS LAST, sorting by formula and reference fields, and the limits the docs don't tell you.

The short answer

To sort by a custom field in ascending order with null records at the end, append ORDER BY CustomField__c ASC NULLS LAST (or Relationship__r.CustomField__c ASC NULLS LAST for parent lookups) to your SOQL query. Explicit NULLS LAST is required because ascending SOQL sorts place null values first by default.

To order Salesforce SOQL query results by a custom field in ascending order with blank values at the bottom, use the syntax ORDER BY CustomField__c ASC NULLS LAST. Because SOQL defaults to NULLS FIRST when sorting ascending, you must explicitly specify NULLS LAST to override the placement. You can sort on direct custom fields or parent lookup fields using __r notation, chaining up to 32 sort keys per query.

SOQL ORDER BY: the basic syntax

The default direction is ascending, the default null placement depends on direction:

// Ascending (default), NULLS FIRST by default
SELECT Id, Name, Amount FROM Opportunity ORDER BY Amount

// Descending, NULLS LAST by default
SELECT Id, Name, Amount FROM Opportunity ORDER BY Amount DESC

// Explicit null placement
SELECT Id, Name, CloseDate FROM Opportunity ORDER BY CloseDate ASC NULLS LAST

Three things to remember about the basics:

  1. ASC is implicit. ORDER BY Amount and ORDER BY Amount ASC are identical.
  2. NULLS FIRST/LAST defaults flip with direction. Ascending puts nulls first; descending puts them last. Override only when you need to.
  3. Sort direction applies per column. ORDER BY A DESC, B sorts A descending and B ascending — the DESC doesn't carry over.

Multi-column sorting (up to 32 keys)

When you need a tiebreaker, comma-separate columns. Each gets its own optional ASC/DESC:

// Group by Industry first, then by largest deal within each group
SELECT Id, Name, Industry, Amount
FROM Account
ORDER BY Industry ASC, Amount DESC

The first column is the primary sort key; subsequent columns only matter when the previous columns tie. Soql allows up to 32 sort keys per query, but in real code you rarely need more than three. If you find yourself reaching for a fourth, the data probably wants reshaping in Apex instead.

Sorting by parent (lookup) fields

You can sort by any field on a related parent object using the relationship name:

// Contacts sorted by their Account's industry, then by Account name
SELECT Id, FirstName, LastName, Account.Industry, Account.Name
FROM Contact
ORDER BY Account.Industry, Account.Name

Note the relationship name — Account not AccountId — because you're sorting on a field of the related record, not on the foreign key column itself. For custom relationships, swap the suffix: Project__r.Status__c (use __r, not __c).

NULLS FIRST vs NULLS LAST in practice

Use NULLS FIRST/LAST whenever the column is optional and the null position changes meaning. Two examples that come up constantly:

// Show pending opportunities (no close date set) at the top
SELECT Id, Name, CloseDate
FROM Opportunity
WHERE StageName = 'Negotiation/Review'
ORDER BY CloseDate ASC NULLS FIRST

// Show contacts who *have* a recent activity ahead of those who don't
SELECT Id, Name, LastActivityDate
FROM Contact
ORDER BY LastActivityDate DESC NULLS LAST

The second case — "real activity first, no activity at the bottom" — is the most common reason to override the default.

Performance: when ORDER BY is fast vs slow

ORDER BY is cheap when it lines up with an index, expensive when it doesn't. Salesforce auto-indexes:

  • Id, Name, OwnerId, CreatedDate, LastModifiedDate, SystemModstamp
  • All foreign-key (lookup/master-detail) fields
  • Custom fields marked External ID or Unique
  • Standard fields with high selectivity (varies by org)

If you're sorting on something not in that list — say, a custom Status picklist — the optimizer falls back to a full table scan. For large data volumes, that means timeout. Two mitigations: use a selective WHERE clause to shrink the result first, or have an admin add a custom index via Salesforce Support.

There's also a 32,000-row hard limit on sorted results in synchronous Apex. Past that, the query throws SOQL_OFFSET_TOO_LARGE if you also use OFFSET, or simply truncates. For batches over 32k rows, switch to a query with Apex cursors or Database.QueryLocator in a Batch class.

ORDER BY in aggregate queries

When you GROUP BY, the ORDER BY columns must be either in the GROUP BY clause or aggregated:

// Top 10 industries by deal count
SELECT Industry, COUNT(Id) totalCount
FROM Account
GROUP BY Industry
ORDER BY COUNT(Id) DESC
LIMIT 10

You can also use the aggregate alias: ORDER BY totalCount DESC works the same. Don't try to ORDER BY a column you didn't group on — you'll get MALFORMED_QUERY: field 'X' must be in GROUP BY clause.

Quick reference cheat sheet

Goal Pattern
Default ascending ORDER BY field
Descending ORDER BY field DESC
Multi-column ORDER BY a, b DESC, c
Show nulls first/last ORDER BY field ASC NULLS LAST
Sort by parent field ORDER BY Account.Name
Sort aggregate ORDER BY COUNT(Id) DESC
Pagination friendly ORDER BY Id (Id is always indexed)

Common mistakes

  • Forgetting to break ties. ORDER BY CreatedDate DESC returns rows in some order when many records share the same timestamp. Add Id as a secondary sort for deterministic pagination.
  • Sorting on a formula field. Formula fields aren't stored — they're calculated at query time, so ORDER BY MyFormula__c forces evaluation per row. For frequent sorts, materialize the value into a regular field via Flow.
  • Confusing relationship and field names. It's Account.Name (relationship.field), never AccountId.Name. Salesforce CLI's sf data query shows the right names interactively if you're unsure.

Sorting in SOQL is one of those features that rewards a few minutes of learning the rules upfront. Get the indexing right, choose your null placement deliberately, and chain your tiebreakers — and you'll never have a sort-related production support ticket again.

Frequently asked questions

What is the SOQL ORDER BY syntax?

SOQL ORDER BY uses the same shape as SQL: SELECT fields FROM Object ORDER BY field [ASC|DESC] [NULLS FIRST|NULLS LAST]. You can chain up to 32 sort columns: ORDER BY Account.Industry, Amount DESC, CloseDate. ASC is the default direction; NULLS FIRST is the default null placement for ASC, NULLS LAST for DESC.

How do I sort SOQL results in descending order?

Append DESC to the column name: SELECT Id, Name, Amount FROM Opportunity ORDER BY Amount DESC. To break ties with a secondary sort, list more fields: ORDER BY Amount DESC, CloseDate ASC. The DESC keyword applies only to the immediately preceding column.

What does NULLS FIRST and NULLS LAST mean in SOQL?

Controls where rows with a null value in the sort column appear: NULLS FIRST puts them at the top, NULLS LAST puts them at the bottom. Useful when sorting on optional fields like CloseDate or LastActivityDate where you want pending records (null) handled deliberately. Default: NULLS FIRST for ASC, NULLS LAST for DESC.

Can I ORDER BY multiple columns in SOQL?

Yes — comma-separate them with optional ASC/DESC per column: ORDER BY Account.Name ASC, Amount DESC, Id. The first column is the primary sort key; subsequent columns break ties. Soql supports up to 32 sort columns per query, but practically you rarely need more than 3.

Can I sort SOQL results by a parent object's field?

Yes, using dot notation: SELECT Id, Account.Name FROM Contact ORDER BY Account.Industry, Account.Name. The relationship name (Account, OpportunityLineItems, etc.) is required — you can't reference the lookup ID directly for sorting.

Why is my SOQL ORDER BY query slow?

Three common reasons: (1) sort column isn't indexed (Salesforce auto-indexes some fields like Id, Name, OwnerId, RecordType, foreign keys, custom fields with External Id or Unique = true) — non-indexed sorts force a full scan; (2) you're sorting a large result without a selective WHERE clause; (3) ORDER BY a formula field that itself contains uncached calculations. Use indexed fields whenever possible.

Can I use ORDER BY with aggregate SOQL queries?

Yes — and you must use the alias or the aggregate function: SELECT Industry, COUNT(Id) cnt FROM Account GROUP BY Industry ORDER BY COUNT(Id) DESC. You cannot ORDER BY a non-aggregated field that isn't in the GROUP BY clause.

Newsletter

One email every Tuesday

New guides, tool updates, and the release-note changes that break things.

No spam. Unsubscribe in one click.

Comments

Loading comments...

Leave a Comment