Sorting is one of the most common operations in data analysis. Whether you need to find the cheapest fuel, rank stations by volume, or simply present results in a readable order, SQL’s ORDER BY clause is the tool for the job.

Unlike Python where pandas uses sort_values() and Polars uses sort(), SQL has a single, universal syntax that works across SQLite, PostgreSQL, MySQL, SQL Server, and every other relational database. Learn ORDER BY once and it works everywhere.

This article covers six patterns, each demonstrated on an interactive dataset you can edit and run directly in your browser:

  1. ORDER BY ASC — sort ascending (lowest to highest)
  2. ORDER BY DESC — sort descending (highest to lowest)
  3. NULLS LAST — control where NULL values appear
  4. Multi-column sorting — sort by two or more columns
  5. ORDER BY + LIMIT — find the top N values
  6. Combining WHERE, ORDER BY, and LIMIT — find the bottom N values

Sorting ascending with ORDER BY ASC

The most basic sort orders rows from smallest to largest. ASC stands for ascending, and it is the default direction — you can omit it and ORDER BY column will sort ascending automatically. We include it here for clarity:

SQL — editable
Figure 1: All rows sorted by price from lowest to highest.

The result starts with the cheapest fuel (1.85 per litre at BP Southbank) and ends with the most expensive (2.15 at Shell Fortitude Valley). Notice that the row with a NULL price appears first — in SQLite, NULLs are treated as smaller than any other value by default.

In pandas, the equivalent would be df.sort_values('price_per_litre'). In Polars, it would be df.sort('price_per_litre'). Both default to ascending order, just like SQL.

Sorting descending with ORDER BY DESC

To reverse the order — highest to lowest — use DESC (descending):

SQL — editable
Figure 2: All rows sorted by price from highest to lowest.

Now the most expensive fuel appears first (Premium at 2.15) and the cheapest appears last. The NULL price row drops to the bottom because SQLite places NULLs last in descending order by default.

In pandas: df.sort_values('price_per_litre', ascending=False). In Polars: df.sort('price_per_litre', descending=True).

Handling NULLs with NULLS LAST

When your data has missing values, you often want explicit control over where NULLs appear. SQLite (3.30+), PostgreSQL, and Oracle support NULLS FIRST and NULLS LAST to override the default placement:

SQL — editable
Figure 3: Sorted by litres descending, with NULLs pushed to the bottom.

Without NULLS LAST, the two rows with NULL litres would appear at the bottom in a DESC sort anyway (SQLite default). But if you switch to ASC, NULLs would appear first by default. Adding NULLS LAST guarantees they stay at the bottom regardless of sort direction.

In pandas, you control NULL placement with na_position='last' or na_position='first' in sort_values(). In Polars, use nulls_last=True as a parameter in sort().

Multi-column sorting

You can sort by multiple columns by listing them in ORDER BY, separated by commas. Each column can have its own direction. SQL sorts by the first column, then breaks ties using the second column:

SQL — editable
Figure 4: Sorted by station A-Z, then by price highest-first within each station.

Rows are first sorted alphabetically by station (ASC). Within each station, rows are then sorted by price_per_litre from highest to lowest (DESC). This is useful for reports where you want a logical grouping with a ranked order inside each group.

In pandas: df.sort_values(['station', 'price_per_litre'], ascending=[True, False]). In Polars: df.sort(['station', 'price_per_litre'], descending=[False, True]).

Finding top values with ORDER BY + LIMIT

Combining ORDER BY DESC with LIMIT gives you the top N rows — the SQL equivalent of pandas nlargest() or Polars top_k():

SQL — editable
Figure 5: The 5 most expensive fuel transactions.

ORDER BY price_per_litre DESC sorts all 15 rows from most expensive to cheapest, then LIMIT 5 keeps only the first 5 rows. The result is the five most expensive fuel purchases in the dataset.

In pandas: df.nlargest(5, 'price_per_litre'). In Polars: df.top_k(5, by='price_per_litre'). Note that LIMIT is SQLite/PostgreSQL/MySQL syntax. SQL Server uses SELECT TOP 5 instead, and Oracle uses FETCH FIRST 5 ROWS ONLY.

Finding bottom values with WHERE + ORDER BY + LIMIT

To find the smallest values, combine ORDER BY ASC with LIMIT. We also add a WHERE clause to exclude NULLs, since they would otherwise appear first in an ascending sort:

SQL — editable
Figure 6: The 3 smallest fuel purchases (excluding NULLs).

The query first filters out NULL litres with WHERE litres IS NOT NULL, sorts the remaining rows from smallest to largest, and returns only the bottom 3. This is the SQL equivalent of pandas nsmallest(3, 'litres') or Polars df.filter(pl.col('litres').is_not_null()).sort('litres').head(3).

This pattern — WHERE to clean, ORDER BY to sort, LIMIT to cap — is one of the most commonly used query structures in practice.

SQL vs pandas vs Polars: sorting compared

If you are coming from Python, here is how the key operations map:

The SQL approach is declarative: you describe the desired order and the database engine handles the sorting algorithm. Try editing the code blocks above — change the column, flip ASC/DESC, adjust the LIMIT, or add a WHERE clause to explore different sorting patterns.

References

Suhith Illesinghe
Curiosity is the first step to make a difference. I hope to inspire others to explore, build and champion collaborative growth.