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:
ORDER BY ASC— sort ascending (lowest to highest)ORDER BY DESC— sort descending (highest to lowest)NULLS LAST— control where NULL values appear- Multi-column sorting — sort by two or more columns
ORDER BY + LIMIT— find the top N values- Combining
WHERE,ORDER BY, andLIMIT— 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:
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):
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:
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:
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():
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:
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:
df.sort_values('col')(pandas) →SELECT * FROM t ORDER BY col ASCdf.sort('col')(Polars) →SELECT * FROM t ORDER BY col ASCdf.sort_values('col', ascending=False)(pandas) →SELECT * FROM t ORDER BY col DESCdf.sort('col', descending=True)(Polars) →SELECT * FROM t ORDER BY col DESCdf.nlargest(5, 'col')(pandas) →SELECT * FROM t ORDER BY col DESC LIMIT 5df.top_k(5, by='col')(Polars) →SELECT * FROM t ORDER BY col DESC LIMIT 5df.sort_values('col', na_position='last')(pandas) →SELECT * FROM t ORDER BY col ASC NULLS LAST
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
- SQLite documentation: SELECT — ORDER BY
- Pandas equivalent: How to Sort Data in Pandas
- Polars equivalent: How to Sort Data with Polars
- sql.js: SQLite compiled to WebAssembly