SQL (SQLite & PostgreSQL)
SQL is the language for storing and querying data in relational databases. This section teaches SQL itself, then flags — on every page — exactly where SQLite and PostgreSQL disagree, because most real-world SQL pain comes from assuming one behaves like the other.
The relational model in five terms
Section titled “The relational model in five terms”A relational database stores data as a set of tables. That’s it — no nesting, no pointers, no objects.
| Term | What it is |
|---|---|
| Table (relation) | A named, unordered set of rows with a fixed set of columns. |
| Row (tuple/record) | One fact. (3, 'Ursula K. Le Guin', 1929) is one author. |
| Column (attribute) | A named, typed slot present in every row. |
| Primary key | A column (or set of columns) that uniquely identifies a row. |
| Foreign key | A column holding a primary key value from another table — the link. |
Two properties trip people up:
- Rows have no inherent order. A table is a set. If you want ordered output you must write
ORDER BY. A query that looks sorted without one is a coincidence you should not rely on. - Relationships are values, not links. There is no pointer from a book to its author. There is a
books.author_idcolumn holding the same number asauthors.id. You reconstruct the relationship at query time with a join.
NULL is the third thing that trips people up. It means “no value here” — not zero, not empty string. It has its own three-valued logic, covered in querying.
SQL is declarative
Section titled “SQL is declarative”You describe the result you want, not the steps to produce it. This is the single most important mental shift.
SELECT title FROM books WHERE year > 1970 ORDER BY title;You did not say “open the table, scan every row, test the year, collect matches, sort them”. You said what the answer looks like. The database’s query planner decides whether to scan the table, use an index, sort in memory or on disk, and in what order to combine tables. That’s why:
- Adding an index can make a query 1000× faster without changing one character of the query.
- Two queries that look different can compile to the same plan.
EXPLAINexists — it is how you see the plan the engine chose.
The practical consequence: write SQL for clarity and correctness, then measure, then tune. Hand-optimising the text of a query is usually wasted effort.
The clause evaluation order
Section titled “The clause evaluation order”SQL is written in one order and evaluated in another. Knowing the real order explains most “why can’t I use that alias there?” errors.
FROM / JOIN → which rows exist, combinedWHERE → filter individual rowsGROUP BY → collapse rows into groupsHAVING → filter groupsSELECT → compute output expressions, assign aliasesDISTINCT → remove duplicate output rowsORDER BY → sortLIMIT / OFFSET → sliceBecause SELECT runs after WHERE, an alias defined in SELECT is not visible in WHERE. It is visible in ORDER BY (which runs later) — in both SQLite and PostgreSQL.
-- Fails in PostgreSQL: "column \"price_with_tax\" does not exist"SELECT price * 1.2 AS price_with_tax FROM books WHERE price_with_tax > 10;
-- Works in both enginesSELECT price * 1.2 AS price_with_tax FROM books ORDER BY price_with_tax DESC;SQLite vs PostgreSQL: the shape of each
Section titled “SQLite vs PostgreSQL: the shape of each”Both speak SQL. They are architecturally different products.
SQLite is a library, not a server. You link it into your process; it reads and writes one file on disk. There is no daemon, no port, no user accounts, no network protocol. sqlite3 app.db opens the file directly. The whole database — schema, data, indexes — is that single file, which you can copy, email, or commit.
PostgreSQL is a client-server database. A server process owns the data directory; clients connect over TCP or a Unix socket, authenticate, and send queries. Multiple machines can connect at once. It does full MVCC (multi-version concurrency control), so many writers work concurrently without blocking each other.
| SQLite | PostgreSQL | |
|---|---|---|
| Deployment | Embedded library, one file | Server process + data directory |
| Concurrency | Many readers, one writer at a time | Many concurrent readers and writers (MVCC) |
| Typing | Dynamic (type affinity) | Static and strict |
| Types | 5 storage classes | Rich: numeric, timestamptz, uuid, jsonb, arrays, enums, custom |
| Network access | None (it’s a file) | Built in, with roles and TLS |
| Setup cost | Zero | Install, configure, operate |
| Typical size ceiling | Comfortable to tens of GB | Terabytes |
When to use which
Section titled “When to use which”Use SQLite for: application-local storage (desktop, mobile, CLI tools), test suites, caches, data analysis on a file you were handed, embedded devices, and read-heavy websites with a single application server. It is the most deployed database engine in the world for good reason — zero operations.
Use PostgreSQL for: anything with multiple concurrent writers, anything running on more than one application server, anything needing real types (money, timestamps with time zones, JSON with indexes), row-level security, replication, or a data set that will outgrow one machine’s page cache.
Connecting
Section titled “Connecting”SQLite
Section titled “SQLite”The sqlite3 CLI ships with most systems (apt install sqlite3, brew install sqlite). Point it at a file; it is created if it doesn’t exist.
sqlite3 bookstore.dbSQLite version 3.45.1Enter ".help" for usage hints.sqlite>Turn on readable output first — the defaults are terse:
sqlite> .headers onsqlite> .mode box.mode box draws a Unicode table (SQLite 3.33+). Use .mode column on older builds. Commands starting with . are CLI commands, not SQL — they take no semicolon.
An in-memory throwaway database, useful for experiments:
sqlite3 :memory:PostgreSQL
Section titled “PostgreSQL”psql is the official client. A local server usually accepts:
psql -d bookstore # local socket, current OS userpsql -h localhost -U app -d bookstore # TCP, prompts for passwordpsql "postgresql://app:secret@db.example.com:5432/bookstore"psql (16.2)Type "help" for help.
bookstore=#Create the database first if it doesn’t exist:
createdb bookstoreIn psql, commands starting with \ are client commands: \dt lists tables, \d books describes one, \q quits. Full cheat sheet on the comparison page.
Your first table and query
Section titled “Your first table and query”This runs unchanged in both engines.
CREATE TABLE authors ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, birth_year INTEGER);
CREATE TABLE books ( id INTEGER PRIMARY KEY, author_id INTEGER NOT NULL REFERENCES authors(id), title TEXT NOT NULL, year INTEGER, price NUMERIC(6, 2));
INSERT INTO authors (id, name, birth_year) VALUES (1, 'Ursula K. Le Guin', 1929), (2, 'Ted Chiang', 1967), (3, 'Octavia E. Butler', 1947);
INSERT INTO books (id, author_id, title, year, price) VALUES (1, 1, 'The Dispossessed', 1974, 14.99), (2, 1, 'A Wizard of Earthsea', 1968, 11.50), (3, 2, 'Stories of Your Life', 2002, 16.00), (4, 3, 'Kindred', 1979, 13.25);Load and query it:
sqlite3 bookstore.db < schema.sqlpsql -d bookstore -f schema.sqlSELECT a.name, b.title, b.yearFROM books AS bJOIN authors AS a ON a.id = b.author_idWHERE b.year >= 1974ORDER BY b.year; name | title | year-------------------+----------------------+------ Ursula K. Le Guin | The Dispossessed | 1974 Octavia E. Butler | Kindred | 1979 Ted Chiang | Stories of Your Life | 2002Every page in this section builds on this schema.
Section map
Section titled “Section map”- Overview — you are here.
- Data types and DDL —
CREATE TABLE, SQLite’s type affinity vs PostgreSQL’s real type system, constraints,ALTER TABLE. - Querying with SELECT — filtering, ordering,
NULLlogic,CASE, built-in functions. - Joins — inner/outer/cross,
USING, self joins, set operations, anti-joins. - Aggregation and window functions —
GROUP BY,HAVING,OVER (PARTITION BY ...), running totals. - Modifying data and transactions —
INSERT/UPDATE/DELETE, upserts,RETURNING, ACID, isolation levels, savepoints. - Advanced SQL — subqueries, CTEs and recursion, views, indexes,
EXPLAIN, JSON. - SQLite vs PostgreSQL — the full comparison, migration checklist, and CLI cheat sheets.
Key points
Section titled “Key points”- A table is an unordered set of rows; use
ORDER BYwhenever order matters. - SQL is declarative — you describe the result, the planner picks the strategy.
- Clauses evaluate
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT, which explains alias scoping. - SQLite is an embedded single-file library with dynamic typing and one writer at a time.
- PostgreSQL is a client-server engine with static types, rich data types, and MVCC concurrency.
sqlite3 file.dbandpsql -d dbnameare your entry points;.commands and\commands are client features, not SQL.