Sorting Results — ORDER BY
Control exactly how your results come back — ascending, descending, multiple columns, NULL handling, and sorting by expressions
// Part 01
Why Result Order Matters
Without ORDER BY, a SQL query returns rows in no guaranteed order. The database returns them in whatever order is most efficient for its internal storage and execution plan. On the same table, two identical queries run at different times can return rows in different orders. This is not a bug — it is how relational databases work by design.
For exploration, random order is fine. But for any result that a person reads, a dashboard displays, or an application processes — order matters enormously. A finance report with the largest transactions at the top. A customer list alphabetically sorted for a dropdown. The most recent orders first for an operations dashboard. The top 10 highest-margin products for a procurement decision. All of these require ORDER BY.
// Part 02
ASC and DESC — Ascending and Descending
ORDER BY sorts in ascending order by default — smallest to largest for numbers, A to Z for text, oldest to newest for dates. To reverse the order, add DESC (descending). To be explicit about ascending, add ASC — though it is optional since it is the default.
Numbers — ascending and descending
Text — alphabetical sorting
Dates — chronological and reverse chronological
// Part 03
Multi-Column Sorting — Primary and Secondary Sort
ORDER BY can sort by multiple columns separated by commas. The database sorts by the first column, then within rows that have the same value in the first column, it sorts by the second column, and so on. This is called a compound sort or multi-key sort.
Think of it like a phone book: sorted by last name first, then by first name within the same last name. If two people share a last name, their first names determine the order between them.
Each column in a multi-column ORDER BY can independently be ASC or DESC. In the examples above, ORDER BY category ASC, unit_price DESC means: sort categories A-Z, then within each category sort prices from highest to lowest. The directions are completely independent per column.
// Part 04
Sorting by Expressions and Calculated Columns
ORDER BY does not have to sort by a raw column value. You can sort by an expression — a calculation, a function result, or a column alias defined in SELECT.
Sorting by a calculated value
Sorting by a SELECT alias
Unlike WHERE (which runs before SELECT), ORDER BY runs after SELECT — so you can reference a SELECT alias in ORDER BY. This makes queries cleaner when sorting by a computed column.
Sorting by column position number
SQL also allows sorting by column position — the number of the column in your SELECT list. ORDER BY 1 means "sort by the first column in SELECT." This is a shorthand that works but is generally discouraged in professional code because it breaks silently when columns are reordered.
// Part 05
NULL Values in ORDER BY — Where Do They Land?
NULL values require special attention when sorting. SQL databases differ in how they handle NULLs in ORDER BY — and the default behaviour is not always what you expect.
Default NULL sort order
In PostgreSQL and DuckDB (used in the playground): NULLs sort last in ASC and first in DESC. In MySQL: NULLs sort first in ASC and last in DESC — the opposite.
Controlling NULL position — NULLS FIRST and NULLS LAST
PostgreSQL (and DuckDB) support explicit control over where NULLs land using NULLS FIRST and NULLS LAST at the end of an ORDER BY column.
// Part 06
ORDER BY with WHERE — Filtering Before Sorting
ORDER BY and WHERE work together seamlessly. WHERE filters first (execution order: FROM → WHERE → SELECT → ORDER BY), then ORDER BY sorts only the rows that survived the filter. This is efficient — you are sorting a potentially much smaller set of rows after filtering.
// Part 07
ORDER BY and Performance
Sorting is not free — it has a cost that grows with the number of rows being sorted. Understanding this cost helps you write queries that stay fast even on large tables.
How the database sorts
When the database executes ORDER BY, it must compare rows to determine their relative order. For small result sets, this happens in memory (fast). For large result sets that do not fit in memory, the database writes temporary files to disk and performs a disk-based sort — significantly slower. This is one reason why LIMIT is often paired with ORDER BY: sort then take the top 10 is much faster than sort all 500 million rows and take the top 10.
Indexes can eliminate sorting
If the column you are sorting by has an index, and the index is ordered in the same direction as your ORDER BY, the database can read the index in order rather than sorting at all. This is called an index scan in order and avoids the sort step entirely. For frequently run queries that sort by the same column, an index on that column can make ORDER BY essentially free.
Never rely on ORDER BY without LIMIT for large tables
Sorting 500 million rows to present them in order is extremely expensive. In production, ORDER BY on a large table without LIMIT should always be paired with a WHERE clause that filters the rows down first, or be used only in reporting contexts where the user explicitly requested a sorted export.
🎯 Pro Tip
In production SQL, ORDER BY is almost always combined with LIMIT — "give me the top 10 highest-value orders" or "the 5 most recent customers." Sorting the entire result set to return all rows in order is expensive. Sorting to find the top N rows is efficient when combined with LIMIT. You will learn LIMIT in Module 09 — use the two together from that point forward.
// Part 08
Practical ORDER BY Patterns — Real Business Examples
These are the ORDER BY patterns you will write most frequently in real work — each tied to a specific business context.
Top N pattern — find the highest or lowest
Most recent records pattern
Alphabetical list pattern
Ranked within group pattern
Salary leaderboard pattern
// Part 09
What This Looks Like at Work
You are an analyst at Nykaa, the beauty and fashion e-commerce platform. It is Monday morning. The merchandising team has three requests waiting in your inbox before the weekly review meeting at 10 AM.
🎯 Pro Tip
When delivering data to another team, always ask: "Does the order matter for how you will use this?" For CRM imports, dropdown menus, ranked reports, and dashboards — order almost always matters. For raw data dumps that will be processed programmatically — order usually does not matter. Asking saves you the round-trip of "can you re-sort this?"
// Part 10
Interview Prep — 5 Questions With Complete Answers
The default sort order of ORDER BY is ascending — ASC. For numeric columns, ascending means smallest to largest. For text columns, ascending means A to Z (lexicographic order based on the column's collation). For date and timestamp columns, ascending means oldest to newest. Writing ORDER BY column is identical to writing ORDER BY column ASC — the ASC keyword is optional but explicit and improves readability.
To reverse the sort order, add DESC (descending) after the column name: ORDER BY unit_price DESC sorts from highest to lowest price. ORDER BY order_date DESC sorts from newest to oldest — the standard for any "most recent first" dashboard. ORDER BY last_name DESC sorts Z to A.
In a multi-column ORDER BY, each column has its own independent direction. ORDER BY category ASC, unit_price DESC sorts categories A to Z, and within each category sorts prices from highest to lowest. The ASC/DESC keyword applies to the immediately preceding column only.
NULL handling in ORDER BY differs between database systems. In PostgreSQL and DuckDB, NULLs sort last in ascending order (they appear after all non-null values) and first in descending order (they appear before all non-null values). MySQL does the opposite: NULLs sort first in ascending order and last in descending order.
In PostgreSQL and DuckDB, you can explicitly control NULL position using NULLS FIRST or NULLS LAST modifiers: ORDER BY delivery_date ASC NULLS LAST places nulls at the end even in ascending order. ORDER BY delivery_date DESC NULLS LAST places nulls at the end even in descending order — useful when you want real dates first and undelivered orders (NULL delivery_date) at the bottom regardless of sort direction.
MySQL does not support NULLS FIRST / NULLS LAST syntax. The workaround is to sort by an IS NULL expression: ORDER BY (delivery_date IS NULL) ASC, delivery_date ASC. The IS NULL expression returns 0 for non-null values and 1 for null values, so sorting ASC on it puts non-nulls (0) before nulls (1). This technique works across all databases and is useful when writing cross-database compatible SQL.
Yes, you can use a SELECT alias in ORDER BY, and this is one of the important differences between ORDER BY and WHERE. The reason is SQL's logical execution order: FROM runs first, then WHERE, then GROUP BY, then HAVING, then SELECT, then ORDER BY last. Because ORDER BY executes after SELECT, the aliases defined in SELECT already exist by the time ORDER BY is evaluated. This makes ORDER BY alias_name perfectly valid.
For example: SELECT unit_price - cost_price AS margin FROM products ORDER BY margin DESC works correctly. The alias 'margin' is defined in SELECT, and ORDER BY can reference it because it runs after SELECT. This is cleaner than repeating the full expression: ORDER BY (unit_price - cost_price) DESC — both produce identical results, but the alias version is more readable especially for complex expressions.
By contrast, you cannot use a SELECT alias in WHERE because WHERE runs before SELECT — the alias does not exist yet. WHERE margin > 100 would throw an error. You must repeat the full expression: WHERE (unit_price - cost_price) > 100. This asymmetry between WHERE (cannot use aliases) and ORDER BY (can use aliases) confuses many beginners, but it follows directly from the logical execution order.
When you ORDER BY multiple columns separated by commas, the database sorts by the first column. Among rows that have identical values in the first column — ties — it sorts by the second column. Among remaining ties, it sorts by the third column, and so on. Each column independently can be ASC or DESC.
A practical example: ORDER BY category ASC, unit_price DESC on a products table. The database first sorts all products alphabetically by category — Beverages, Dairy, Fruits, Household, Packaged Food, Personal Care, Staples, Vegetables. Within each category, products are sorted from most expensive to cheapest. A Beverages product at ₹270 appears before a Beverages product at ₹115, but both appear before any Dairy product.
Multi-column ORDER BY is essential for any report where items are grouped by one dimension and ranked within those groups. Common real-world uses: ORDER BY store_id ASC, revenue DESC (each store's top products), ORDER BY department ASC, salary DESC (highest earner in each department), ORDER BY order_date DESC, order_id DESC (most recent orders, ties broken by ID). The second column only ever affects the relative order of rows that tied on the first column — if no ties exist on the first column, the second column has no effect.
No. When multiple rows have identical values in all ORDER BY columns — a complete tie — the database can return them in any order, and that order may change between query executions. SQL does not guarantee a deterministic result for tied rows unless you provide enough sort columns to uniquely distinguish every row.
This matters for pagination. If you use ORDER BY order_date DESC LIMIT 10 to get the first page of results, and then ORDER BY order_date DESC LIMIT 10 OFFSET 10 for the second page, any orders placed on the same date might appear on the wrong page or appear on both pages if the database reorders the ties between the two queries. The result is a silent data integrity issue in pagination systems.
The fix is always to include a unique column as the final tiebreaker: ORDER BY order_date DESC, order_id DESC. Since order_id is the primary key and unique for every row, there are no ties and the order is completely deterministic. Every paginated query should end with the primary key as the final sort column to guarantee consistent, non-overlapping pages. This practice is called a stable sort and is standard in any production API that supports pagination.
// Part 11
Errors You Will Hit — And Exactly Why They Happen
🎯 Key Takeaways
- ✓Without ORDER BY, SQL returns rows in no guaranteed order — the same query can return rows in different order on different executions. Never rely on implicit ordering.
- ✓ASC (ascending) is the default: smallest to largest for numbers, A to Z for text, oldest to newest for dates. DESC reverses this. Both keywords apply per column independently.
- ✓Multi-column ORDER BY: sort by the first column, then break ties using the second column, then the third. Each column independently can be ASC or DESC.
- ✓ORDER BY runs after SELECT in the execution order — so you can reference SELECT aliases in ORDER BY but not in WHERE.
- ✓Sorting by expressions and calculated columns works: ORDER BY (unit_price - cost_price) DESC or ORDER BY margin DESC (using the alias).
- ✓NULL handling differs by database: PostgreSQL/DuckDB sorts NULLs last in ASC and first in DESC. MySQL does the opposite. Use NULLS FIRST / NULLS LAST (PostgreSQL) or the IS NULL workaround (MySQL) to control NULL position explicitly.
- ✓Always add a unique tiebreaker column (usually the primary key) as the final ORDER BY column when ties are possible — especially in paginated queries where inconsistent ordering causes duplicate or missing rows.
- ✓ORDER BY without LIMIT on a large table is expensive — the database must sort all rows before returning any. Combine ORDER BY with WHERE (filter first) and LIMIT (take top N) for production performance.
- ✓Indexes on sort columns can eliminate the sort step entirely — the database reads the index in order rather than sorting. For frequently sorted columns on large tables, an index makes ORDER BY essentially free.
- ✓The standard production pattern: WHERE (filter rows) → ORDER BY (sort filtered rows) → LIMIT (take top N). These three clauses together are the foundation of almost every paginated list, leaderboard, and ranked report.
What comes next
In Module 09, you add LIMIT and OFFSET — controlling exactly how many rows come back and implementing pagination. Combined with ORDER BY from this module, LIMIT is how you build every "top 10," "most recent," and "next page" feature.
Module 09 → Limiting Results — LIMIT / OFFSETDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.