Lesson 04 / 18
Sorting and Limiting
Sorting the result set, multi-key sorting, collation's effect on order, where null values land, and the standard versus common syntax for limiting the result to the first rows.
Contents
The previous three lessons determined which columns and which rows would come back. None of them said in which order the rows would come back. The order seen in the outputs came from how the engine read the table; the same query can come back in a different order when a different plan is chosen.
This is not surprising, since a relation in the relational model is an unordered set: order is a property of the query, not of the data. If order is wanted, it has to be written explicitly. This lesson builds the sorting clause and, alongside it, the limiting syntax that only makes sense together with it.
The block below sets up the schema and sample data; every query in this lesson runs against
the library.db file created in this block.
rm -f library.db sqlite3 library.db <<'SQL' CREATE TABLE branch (branch_id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT NOT NULL); CREATE TABLE book (book_id INTEGER PRIMARY KEY, title TEXT NOT NULL, author TEXT NOT NULL, publication_year INTEGER, branch_id INTEGER REFERENCES branch(branch_id)); CREATE TABLE member (member_id INTEGER PRIMARY KEY, first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT, registered_at TEXT NOT NULL); CREATE TABLE loan (loan_id INTEGER PRIMARY KEY, book_id INTEGER NOT NULL REFERENCES book(book_id), member_id INTEGER NOT NULL REFERENCES member(member_id), pickup_date TEXT NOT NULL, return_date TEXT); INSERT INTO branch VALUES (1,'Central','Ankara'),(2,'Bahcelievler','Ankara'), (3,'Kadikoy','Istanbul'),(4,'Konak','Izmir'); INSERT INTO book VALUES (1,'Blindness','José Saramago',1995,1),(2,'The Disconnected','Oğuz Atay',1972,1), (3,'The Book of Sand','Jorge Luis Borges',1975,2),(4,'Yaban','Yakup Kadri',1932,2), (5,'Silent House','Orhan Pamuk',1983,3),(6,'Motherland Hotel','Yusuf Atılgan',NULL,3), (7,'Tehlikeli Oyunlar','Oğuz Atay',1973,NULL); INSERT INTO member VALUES (1,'Alice','Kane','[email protected]','2023-02-14'), (2,'Ben','Ortiz','[email protected]','2023-05-30'),(3,'Clara','Diaz',NULL,'2024-01-09'), (4,'Derek','Voss','[email protected]','2024-03-22'),(5,'Grace','Kim',NULL,'2024-11-05'), (6,'Owen','Park','[email protected]','2025-01-18'); INSERT INTO loan VALUES (1,1,1,'2025-01-10','2025-01-24'),(2,2,1,'2025-02-02','2025-02-20'), (3,1,2,'2025-02-11',NULL),(4,3,3,'2025-03-01','2025-03-15'),(5,4,3,'2025-03-18','2025-04-02'), (6,1,4,'2025-04-05','2025-04-19'),(7,5,4,'2025-04-21',NULL),(8,2,5,'2025-05-02','2025-05-30'), (9,7,1,'2025-05-14','2025-05-28'),(10,3,5,'2025-06-03',NULL),(11,6,2,'2025-06-11','2025-06-25'), (12,4,4,'2025-06-20','2025-07-04'); SQL
Sorting by a Single Key
The ORDER BY clause takes the sort key. If no direction is stated, ascending order is
assumed; ASC and DESC state the direction explicitly.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY publication_year; SQL
title publication_year ----------------- ---------------- Motherland Hotel Yaban 1932 The Disconnected 1972 Tehlikeli Oyunlar 1973 The Book of Sand 1975 Silent House 1983 Blindness 1995
The book with an unknown publication year came first. Where a null value lands in a sort is theoretically undefined — it cannot be said whether an unknown number is greater or less than 1932 — so engines make an assumption, and that assumption depends on the engine. Some place null values first in ascending order, others place them last.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY publication_year DESC; SQL
title publication_year ----------------- ---------------- Blindness 1995 Silent House 1983 The Book of Sand 1975 Tehlikeli Oyunlar 1973 The Disconnected 1972 Yaban 1932 Motherland Hotel
When the direction reversed, the null value switched sides too. There is a standard marker for pinning its place in the query.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY publication_year NULLS LAST; SQL
title publication_year ----------------- ---------------- Yaban 1932 The Disconnected 1972 Tehlikeli Oyunlar 1973 The Book of Sand 1975 Silent House 1983 Blindness 1995 Motherland Hotel
The NULLS FIRST and NULLS LAST markers can move null values to the end even in ascending
order. Support for them depends on the engine; on engines that do not support them, the same
result is obtained by making an expression that tests for null the first sort key.
Collation and Text Order
The result of sorting text depends on collation. If the default collation compares characters by their codes rather than alphabetically, the result diverges from Turkish alphabetical order.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT author FROM book ORDER BY author; SQL
author ----------------- Jorge Luis Borges José Saramago Orhan Pamuk Oğuz Atay Oğuz Atay Yakup Kadri Yusuf Atılgan
Orhan Pamuk came before Oğuz Atay. In the Turkish alphabet, ğ comes right after g, well
before r; the comparison applied here, though, compares the binary representation of the
characters, and ğ’s code point is far higher than r’s, so Orhan came out first.
This is not a bug, but the result of a configuration. If language-aware sorting is wanted, a language-specific collation is defined on the column or the query; which collations exist and how they are named depends on the engine. In lists shown to users, this is a defect that is hard to notice once it has been overlooked.
Multi-Key Sorting
Multiple keys are separated by commas and applied in order: rows that are equal on the first key get sorted by the second key. Each key’s direction is written separately.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, author, publication_year FROM book ORDER BY author, publication_year DESC; SQL
title author publication_year ----------------- ----------------- ---------------- The Book of Sand Jorge Luis Borges 1975 Blindness José Saramago 1995 Silent House Orhan Pamuk 1983 Tehlikeli Oyunlar Oğuz Atay 1973 The Disconnected Oğuz Atay 1972 Yaban Yakup Kadri 1932 Motherland Hotel Yusuf Atılgan
The same author’s two books were sorted from newest to oldest. DESC applies only to its own
key; the author column stayed ascending.
A sort key can also be an expression, and the result column’s ordinal position can be written too.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY 2 DESC; SQL
title publication_year ----------------- ---------------- Blindness 1995 Silent House 1983 The Book of Sand 1975 Tehlikeli Oyunlar 1973 The Disconnected 1972 Yaban 1932 Motherland Hotel
Writing it by ordinal position is short but fragile: when a column gets added to the column list, the sort silently shifts to a different column. Writing the column name or alias is always safer.
Deterministic Sorting
ORDER BY establishes an order only over the keys it is written with. The order among rows
that are equal on those keys is undefined, and it can change when the engine changes its plan.
In the query above, the publication year was the tiebreaker when the author was equal; had
both been equal, the result could have differed from run to run.
When the result needs to be the same on every run — reports written to a log, paginated lists,
compared outputs — a unique column gets added to the end of the sort keys. The primary key is
directly suited to this job. Writing ORDER BY author, book_id defines a single order even
among rows whose author is the same.
Limiting
When only the first few rows of a sorted result are wanted, limiting is written. The standard syntax and the common shortcut differ, and this is the second dialect distinction encountered in the course.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title FROM book ORDER BY title FETCH FIRST 3 ROWS ONLY; SQL
Parse error near line 3: near "FETCH": syntax error
SELECT title FROM book ORDER BY title FETCH FIRST 3 ROWS ONLY;
error here ---^
The standard syntax is OFFSET … ROWS FETCH FIRST … ROWS ONLY, and the engine used here does
not recognize it. The syntax the engine accepts is the shortcut common across a wide range of
engines.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY publication_year DESC LIMIT 3; SQL
title publication_year ---------------- ---------------- Blindness 1995 Silent House 1983 The Book of Sand 1975
The number of rows to skip can be stated too.
sqlite3 library.db <<'SQL' .headers on .mode column SELECT title, publication_year FROM book ORDER BY publication_year DESC LIMIT 3 OFFSET 3; SQL
title publication_year ----------------- ---------------- Tehlikeli Oyunlar 1973 The Disconnected 1972 Yaban 1932
Together, the two queries produced two pages of three rows each. The pagination syntax has two traps. First, writing a limit without writing a sort is meaningless: which rows are the “first three” stays undefined, and two consecutive pages can show the same row twice. Second, pagination by skip count drifts when data changes between pages — a deleted row makes the next page skip its first row. The second of these two traps gets solved with keyset pagination in the Advanced SQL course.
The performance cost of the skip count is silent too: the engine still has to produce the skipped rows before discarding them, so large offset values produce lists that get slower as the page number grows.
Summary
- Sorting is a property of the query, not of the data; without
ORDER BY, row order is undefined. - Where null values land in a sort depends on the engine;
NULLS FIRSTandNULLS LASTpin their place, but support for them also depends on the engine. - Text order depends on collation; the default collation may not give Turkish alphabetical order.
- In multi-key sorting, each key’s direction is written separately, and the keys apply in order.
- The order among rows with equal keys is undefined; a unique column is added to the sort for a deterministic result.
- The standard syntax for limiting and the common shortcut differ; limiting without sorting is meaningless, and offset-based pagination drifts when data changes.
Next Step
Over the last three lessons, a null value showed up in three separate places: it blanked out a computed expression, left a condition’s negation incomplete, and took an engine-dependent place in sorting. These are consequences of a single rule. The next lesson builds that rule — the three-valued logic of an unknown value — introduces the functions that work with null values, and measures the trap that shows up in the negation of a membership condition.
To keep your progress and take notes, Log in
My notes
Log in to take notes.