If you're pulling from a raw transaction tab into a board-ready summary, ORDER BY is where you control what appears at the top.
Basic ORDER BY Syntax in Google Sheets QUERY
The full pattern:
=QUERY(range, "SELECT col1, col2 ORDER BY col2 DESC", headers)
A real P&L example - top 8 cost centers by Q3 2026 spend, pulled from the raw data tab:
=QUERY('GL Detail'!A:E,
"SELECT B, SUM(D)
WHERE A >= date '2026-07-01'
AND A < date '2026-10-01'
GROUP BY B
ORDER BY SUM(D) DESC
LABEL SUM(D) 'Q3 Spend'",
1)
A few rules that catch people off guard:
- ORDER BY references the source column letter (A, B, C...), not the output column position. If you SELECT B and E, you still write
ORDER BY E, notORDER BY 2. - ASC is the default. You only need to write it if you want to be explicit.
- ORDER BY goes after GROUP BY, before LABEL and LIMIT.
Google's Visualization API Query Language documentation confirms this ordering: "The ORDER BY clause specifies that the rows in the results table should be sorted by values of the specified columns."
Multi-Column ORDER BY: Sorting by More Than One Field
You can chain columns with a comma. The sort applies left to right - first sort wins for ties.
Quarterly board pack scenario: ARR by segment, sorted by segment name then revenue descending within each segment, pulling from 8 segments across the Assumptions and Revenue tabs:
=QUERY(
{Assumptions!B2:B500, 'Revenue'!C2:C500, 'Revenue'!D2:D500},
"SELECT Col1, Col2, SUM(Col3)
WHERE Col2 IS NOT NULL
GROUP BY Col1, Col2
ORDER BY Col1 ASC, SUM(Col3) DESC
LABEL Col1 'Region', Col2 'Segment', SUM(Col3) 'ARR'",
1)
This surfaces the biggest ARR contributors within each region first - exactly what the CFO wants at the top of slide 3.
Handling NULLs in ORDER BY
QUERY sorts NULLs to the bottom by default on ASC and to the top on DESC. That's almost always wrong when you're presenting data.
In a pipeline model with 40 rows with ARR entered and 6 rows where ARR is blank, a naive ORDER BY C DESC puts those 6 blank rows first. Fix it with a WHERE clause before the sort:
=QUERY('Pipeline'!A:D,
"SELECT A, B, C
WHERE C IS NOT NULL
ORDER BY C DESC",
1)
If you need the NULLs visible but pushed to the bottom regardless of sort direction, QUERY alone won't do it cleanly. The workaround is wrapping with SORT() after the fact - but that breaks when the QUERY result size changes. A cleaner approach is to use IFERROR or a helper column that substitutes 0 for blank before feeding the range into QUERY.
Dynamic ORDER BY: Changing Sort Column with a Cell Reference
QUERY clauses are just strings. Concatenate a cell reference to make the sort column dynamic.
=QUERY('Revenue'!A:F,
"SELECT A, B, C, D
ORDER BY " & Assumptions!$B$1 & " DESC",
1)
Where Assumptions!$B$1 contains C or D - the column letter to sort by. A dropdown in B1 makes the sort column switchable without editing the formula. Useful in scenario decks where the viewer might want to sort by ARR, by growth rate, or by gross margin depending on the narrative.
The concatenation approach also works for toggling ASC/DESC:
=QUERY('Revenue'!A:F,
"SELECT A, B, C
ORDER BY C " & Assumptions!$B$2,
1)
Where B2 is a dropdown containing "ASC" or "DESC".
ORDER BY Across Multiple Tabs: The Array Literal Pattern
When your source data spans tabs - say, revenue in one tab, segment metadata in another - you stack them into an array literal first. As of May 2026, VSTACK (available since early 2023) is cleaner than curly braces for tall stacks, but the QUERY syntax is identical either way.
Cross-tab example: contribution margin by SKU, pulling from a 5,000-row transaction detail tab and joining to a product lookup:
=QUERY(
ARRAYFORMULA({
'Transactions'!A2:A5001,
'Transactions'!C2:C5001,
IFERROR(VLOOKUP('Transactions'!B2:B5001, 'Products'!A:C, 3, 0), "Unknown")
}),
"SELECT Col1, Col3, SUM(Col2)
WHERE Col3 <> 'Unknown'
GROUP BY Col1, Col3
ORDER BY SUM(Col2) DESC
LABEL Col1 'Date', Col3 'Category', SUM(Col2) 'Revenue'",
0)
When you use array literals, columns become Col1, Col2, Col3... instead of A, B, C. This trips up a lot of people the first time. The header count parameter also changes - use 0 instead of 1 since the array has no header row.
QUERY ORDER BY vs SORT() vs SORTN(): When to Use Each
All 3 functions sort. They're not interchangeable.
| Function | Best for | Supports grouping? | Dynamic criteria? | Returns top-N only? |
|---|---|---|---|---|
QUERY ORDER BY | Sorting aggregated or filtered results | Yes (GROUP BY) | Via concatenation | No (use LIMIT) |
SORT() | Sorting a static range in place | No | Yes (col index) | No |
SORTN() | Returning top-N rows | No | Yes | Yes |
The practical split for FP&A work: use QUERY ORDER BY when you're also filtering or aggregating (most of the time in a model). Use SORT() when you just want to reorder a clean range without any transformation. Use SORTN() when the output is a leaderboard - top 10 deals by ARR, top 5 markets by contribution margin.
SORTN with a tie-breaking sort on a $1.2M ARR threshold:
=SORTN(
FILTER('Pipeline'!A:D, 'Pipeline'!D:D >= 1200000),
10, 0, 4, FALSE
)
That gets you the top 10 rows with ARR >= $1.2M, sorted by column 4 descending - without QUERY syntax at all.
LIMIT Combined with ORDER BY: Building True Ranked Tables
ORDER BY without LIMIT returns everything sorted. Add LIMIT when you need a fixed-size output - useful for dashboard tiles where the layout breaks if row count varies.
Top 5 revenue lines for a board pack summary tile:
=QUERY(
SUMIFS('P&L'!C:C, 'P&L'!B:B, ">=" & Assumptions!$B$3, 'P&L'!A:A, Dashboard!$A$2:$A$20),
"SELECT Col1, Col2
ORDER BY Col2 DESC
LIMIT 5",
0)
One thing to watch: LIMIT doesn't paginate. If you need rows 6-10, QUERY doesn't support OFFSET natively. You'd need a second query or LARGE()-based logic for that. According to Google's Query Language documentation, "The LIMIT clause limits the number of rows returned" with no offset parameter exposed in the standard Sheets implementation.
Building QUERY ORDER BY Formulas from Plain Language
Writing these from scratch when you're mid-model-build is slow. The syntax errors are annoying - a missing quote, a wrong column letter, and you get a #VALUE! that takes 2 minutes to debug.
ModelMonkey (the AI assistant built into Google Sheets) handles QUERY formula construction well. Describe what you want - "sort transactions by date descending, filter to Q3, group by cost center" - and it writes the formula with correct column references pulled from your actual sheet headers. It works in both Google Sheets and Excel. Try ModelMonkey free for 14 days.