ORDER BY with NULLs first or last
Short answer: ORDER BY col NULLS LAST (or NULLS FIRST) on Postgres / Oracle / DuckDB / Snowflake / Redshift / SQLite / BigQuery. MySQL and SQL Server don't support the NULLS clause natively (any version). Work around it with ORDER BY col IS NULL, col (MySQL) or ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col (SQL Server). Default behavior is engine-dependent: Postgres and Oracle put nulls last by default for ASC; MySQL and SQL Server put them first.
Default behavior by engine #
| Engine | Default for ASC | Default for DESC |
|---|---|---|
| Postgres / DuckDB / Oracle / Snowflake / Redshift | NULLs last | NULLs first |
| MySQL / SQLite | NULLs first | NULLs last |
| SQL Server | NULLs first | NULLs last |
Always be explicit when nulls matter; engine differences are a cross-team confusion source.
Canonical SQL #
-- Modern engines.
SELECT * FROM users ORDER BY last_seen_at DESC NULLS LAST;
SELECT * FROM users ORDER BY signup_date ASC NULLS FIRST;
-- MySQL workaround (no NULLS clause).
SELECT * FROM users ORDER BY last_seen_at IS NULL, last_seen_at DESC;
-- "IS NULL" returns 0 for non-null and 1 for null; sorting by it puts nulls last.
-- SQL Server workaround (any version — no native NULLS clause).
SELECT * FROM users ORDER BY CASE WHEN last_seen_at IS NULL THEN 1 ELSE 0 END, last_seen_at DESC;Example #
Common patterns #
-- "Show users by last login, with users who never logged in last."
SELECT * FROM users ORDER BY last_login_at DESC NULLS LAST;
-- Pagination: sort key must be deterministic — use a tie-breaker.
SELECT * FROM orders
ORDER BY created_at DESC NULLS LAST, id DESC -- id breaks ties + NULL ordering
LIMIT 50 OFFSET 0;
-- "Show items needing review (NULL fields) at the top."
SELECT * FROM tickets ORDER BY priority ASC NULLS FIRST, created_at;
-- Sort by computed field with explicit NULL handling.
SELECT *,
(revenue / NULLIF(visitors, 0)) AS rev_per_visitor
FROM campaigns
ORDER BY rev_per_visitor DESC NULLS LAST;Dialect comparison #
| Engine | Syntax | Gotcha |
|---|---|---|
| PostgreSQL / DuckDB | ORDER BY col NULLS LAST | ASC defaults to NULLs last, DESC to NULLs first |
| MySQL | ORDER BY col IS NULL, col | no native NULLS clause |
| SQL Server | ORDER BY CASE WHEN col IS NULL THEN 1 ELSE 0 END, col | no native NULLS clause (any version) |
| BigQuery | ORDER BY col NULLS LAST | — |
| Snowflake | ORDER BY col NULLS LAST | ASC defaults to NULLs last, DESC to NULLs first |
| Oracle | ORDER BY col NULLS LAST | ASC defaults to NULLs last, DESC to NULLs first |
| SQLite | ORDER BY col NULLS LAST | default puts NULLs first for ASC |
Always be explicit about NULLS LAST / NULLS FIRST when nulls are present in the sort column. Default behavior differs across engines, and a query that ranks the right way in dev may rank wrong in prod after a database migration. The two extra keywords cost nothing and document intent. For pagination, also include a deterministic tie-breaker (ORDER BY sort_col NULLS LAST, id). Without it, two rows with the same sort value can land on different pages between requests. Detail: ORDER BY.