SQLite Examples
Three reproducible scenarios — Beginner to Advanced — you can run in the sqlite3 shell against a fresh demo.db. | ← Back to the reference manual
sqlite3 CLI installed; every scenario starts from an empty folder.demo.db, make a users table, add a couple of people, and list them back — all inside the sqlite3 shell.
-
Open (and create) the database
Run this in a terminal from an empty folder. Because
demo.dbdoes not exist yet, SQLite creates it and drops you into the interactive shell.sqlite3 demo.db
-
Create a table
At the
sqlite>prompt, define auserstable. TheINTEGER PRIMARY KEYcolumn becomes an alias for the rowid, so ids fill in automatically.CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER );
-
Insert a few rows
Add two people in one statement. We omit
id, letting SQLite assign 1 and 2.INSERT INTO users(name, email, age) VALUES ('Ada', 'ada@x.io', 36), ('Grace', 'grace@x.io', 45); -
Read them back in a readable grid
Turn on a boxed layout with headers, then select everyone. Type
.quitwhen you are done..mode box .headers on SELECT * FROM users; .quit
demo.db in the current folder, and every statement outside an explicit BEGIN committed on its own (autocommit). After .quit the file remains on disk with your two rows in it.
sqlite3 demo.db reopens the same file with your data intact. If you want a clean slate, delete demo.db (and any demo.db-wal / demo.db-shm companions) and start again.
users table from scenario G-1 (same demo.db), you now want to record blog posts, list each post next to its author, speed up the author lookup with an index, and count how many posts each person wrote.
-
Add a related table and some rows
Reopen
demo.dband create aposttable whoseuser_idreferencesusers(id), then insert three posts.sqlite3 demo.db CREATE TABLE post ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, user_id INTEGER REFERENCES users(id) ); INSERT INTO post(title, user_id) VALUES ('Hello world', 1), ('On algorithms', 1), ('Compiler notes', 2); -
Join posts to their authors
An
INNER JOINpairs each post with the matching user row, returning only posts that have an author..mode box .headers on SELECT p.title, u.name FROM post AS p JOIN users AS u ON u.id = p.user_id;
-
Index the join / filter column
Create an index on
post.user_idso lookups by author use a fast indexSEARCHinstead of scanning the table.EXPLAIN QUERY PLANconfirms it.CREATE INDEX idx_post_user ON post(user_id); EXPLAIN QUERY PLAN SELECT * FROM post WHERE user_id = 1;
-
Aggregate: posts per author
Group the join by author and count.
LEFT JOINwould also include authors with zero posts; here we use an inner join for authors who have posted.SELECT u.name, count(p.id) AS posts FROM users AS u JOIN post AS p ON p.user_id = u.id GROUP BY u.id ORDER BY posts DESC;
post.user_id pointing back at each row, so SQLite jumps straight to matching posts rather than reading the whole table. GROUP BY u.id then buckets the joined rows per author before count() tallies each bucket.
REFERENCES clause but does not enforce it unless you run PRAGMA foreign_keys = ON; on the connection first. Enable it if you want inserts with an unknown user_id to be rejected.
demo.db, you need to move a value between two rows so it can never be left half-done, switch the database to WAL mode for better read/write concurrency, and capture a safe on-disk backup you can restore from.
-
Set up an accounts table
Reopen
demo.dband create a smallaccounttable with two funded rows to transfer between.sqlite3 demo.db CREATE TABLE account ( id INTEGER PRIMARY KEY, bal INTEGER NOT NULL ); INSERT INTO account(id, bal) VALUES (1, 500), (2, 0);
-
Move money in one atomic transaction
Wrap both updates in a transaction. Either both take effect at
COMMIT, or — if anything fails — neither does, so the total is never wrong mid-transfer.BEGIN; UPDATE account SET bal = bal - 100 WHERE id = 1; UPDATE account SET bal = bal + 100 WHERE id = 2; COMMIT; SELECT id, bal FROM account; -- 1|400 and 2|100
-
Switch the database to WAL mode
Enable Write-Ahead Logging. It returns
walon success, is persistent across future connections, and createsdemo.db-walanddemo.db-shmcompanion files.PRAGMA journal_mode=WAL; PRAGMA journal_mode; -- confirms: wal
-
Take an online backup
The
.backupdot-command writes a consistent copy of the live database using the Online Backup API — safe to run even while the database is in use. Then leave the shell..backup demo.db.bak .quit
UPDATEs are staged together; COMMIT makes them durable in a single atomic step, so a crash between the debit and credit could never leave money missing. In WAL mode those changes were appended to demo.db-wal and later checkpointed back into the main file, while .backup produced demo.db.bak — a complete, standalone database you can open with sqlite3 demo.db.bak.
ROLLBACK; instead of COMMIT; — a following SELECT shows the balances unchanged, because nothing between BEGIN and ROLLBACK was applied.
Based on SQLite 3.x (current stable 3.53.3) | Docs collected 2026-07-03
← SQLite Manual