Skip to content

SQLite

Every example in this guide runs on SQLite: a free, public-domain database engine. Its defining trait: one file = one database. bookstore.db is the whole database — tables, indexes, data, all in that file. Copy it, email it, back it up like any file. There is no server to install, no service to start, no password to forget.

The essential dot-commands

Dot-commands are instructions to the sqlite3 program itself, not SQL — that's why they start with a dot and need no semicolon:

sqlite3 bookstore.db
sqlite> .tables

Check (verified):

books  sales
sqlite> .schema books

Check (verified):

CREATE TABLE books (
  id INTEGER PRIMARY KEY,
  title TEXT,
  author TEXT,
  year INTEGER,
  price REAL,
  in_stock INTEGER
);

.schema shows the SQL that built the table — handy when you've forgotten a column name (see Troubleshooting).

Command Does
.tables List tables
.schema <table> Show a table's CREATE statement
.headers on Print column names above results
.mode column Neat aligned columns (what this guide's outputs use)
.quit or Ctrl+D Leave sqlite3

Type affinity: flexible, with one rule

Most databases reject a value that doesn't match the column type. SQLite is lenient: a column declared INTEGER prefers integers (called type affinity) but will store text if you insist:

INSERT INTO books (title, author, year, price, in_stock)
VALUES ('Type Demo', 'Nobody', 'not a number', 9.99, 1);
SELECT title, year, typeof(year) FROM books WHERE title = 'Type Demo';

Check (verified):

title      year          typeof(year)
---------  ------------  ------------
Type Demo  not a number  text

typeof() reveals what actually got stored: text, in an INTEGER column. The flexibility is real — and so is the footgun: a typo like '1984 ' (trailing space) silently becomes text and sorts oddly. Clean it up before moving on:

DELETE FROM books WHERE title = 'Type Demo';

The one hard rule: an INTEGER PRIMARY KEY column must hold integers (or NULL) — anything else is a "datatype mismatch" error.

INTEGER PRIMARY KEY assigns ids for you

Declare a column INTEGER PRIMARY KEY and SQLite auto-assigns the next unused integer on INSERT — that's why the guide's INSERTs never mention id:

INSERT INTO books (title) VALUES ('Rowid Demo');
SELECT last_insert_rowid();

Check (verified):

last_insert_rowid()
-------------------
9

The new row got id 9, and last_insert_rowid() reports it (same session only, like changes()). One caveat: without the AUTOINCREMENT keyword, deleting the highest-id row lets a later insert reuse that id. (In this guide's examples, ids only ever grow because we never delete the top row — but don't rely on "never reused" in real schemas.)

Dates as TEXT sort correctly — if you use YYYY-MM-DD

SQLite has no dedicated date type; the convention is TEXT in YYYY-MM-DD format, which sorts alphabetically and chronologically:

SELECT sale_date FROM sales ORDER BY sale_date DESC LIMIT 3;

Check (verified):

sale_date
----------
2026-10-12
2026-10-10
2026-10-09

Write dates any other way (10/12/2026, 12-Oct-26) and sorting breaks — stick to the format.

PRAGMA foreign_keys: off by default

SQLite parses foreign key declarations but doesn't enforce them unless you ask, per connection:

PRAGMA foreign_keys;

Check (verified):

foreign_keys
------------
0

0 means off — SQLite will happily let you insert a sale for book_id 999, which doesn't exist. Turn it on with PRAGMA foreign_keys = ON; at the start of each session if you want the database to reject orphans. (Our setup.sql doesn't declare foreign keys at all — one less thing to trip over while learning. Real schemas should declare them.)

SQLite is already on your devices

You've been using SQLite for years without knowing it. It ships inside phones, web browsers, and countless desktop and mobile apps — anywhere a program needs a small, reliable database without running a server. The file on your disk and the database in a phone app are the same technology. When this guide says "one file = one database," that's literally the file those apps carry around.

That's also why the SQL you learn here travels: the core language is the same in SQLite, PostgreSQL, MySQL, and the rest. Only the edges differ.

Next: Troubleshooting