SQLite Cheatsheet
CLI and Dot-Commands
Use this SQLite reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Opening a database
sqlite3 app.db # opens, creating the file only on the first write sqlite3 # a transient in-memory database sqlite3 :memory: # the same, explicitly sqlite3 'file:app.db?mode=ro' # read-only via URI sqlite3 'file:app.db?immutable=1' # read-only AND promise nobody is writing sqlite3 app.db .quit # run a dot-command and exit sqlite3 app.db "SELECT count(*) FROM users;" # one SQL statement and exit
Merely running sqlite3 app.db does not create the file. SQLite waits until something
actually writes, so a typo in the filename gives you an empty database instead of an error.
Check with .databases, which prints the resolved absolute path.
sqlite3 :memory: "SELECT sqlite_version();" # 3.51.0
Dot-commands are interpreted by the CLI, not by SQLite, so they take no trailing semicolon, must start at the beginning of a line, and do not work through a library or an ORM.
Discovery
| Command | Shows |
|---|---|
.help | Every dot-command |
.help mode | Help for one command, with its option list |
.databases | Attached databases, their file paths, and read/write status |
.tables [pattern] | Table and view names, including from attached databases |
.schema [table] | The CREATE statements, all of them or just one |
.schema --indent | Pretty-printed schema |
.fullschema | Schema plus sqlite_stat1 contents |
.indexes [table] | Indexes, on one table or all |
.dbinfo | Page size, page count, encoding, and header fields |
.stats on | Memory and query statistics after each statement |
.show | Every current CLI setting |
sqlite3 app.db ".databases" ".tables" ".schema users" ".indexes users" # main: /Users/you/app.db r/w # users posts # CREATE TABLE users(id INTEGER PRIMARY KEY, email TEXT UNIQUE);
The equivalent in pure SQL, which is what you use from an application, is the schema table plus the pragma functions.
SELECT name, type FROM sqlite_schema WHERE type IN ('table','view'); SELECT * FROM pragma_table_info('users'); SELECT * FROM pragma_index_list('users');
sqlite_schema is the modern name and sqlite_master is the historical alias. Both work.
Output modes
.mode box -- unicode box drawing, the nicest for reading .headers on
| Mode | Output | ||
|---|---|---|---|
list | 1|Ada, pipe separated. The default | ||
box | Unicode box-drawn table | ||
table | ASCII-art table with +---+ borders | ||
qbox | Like box, with quoted strings and typed blobs | ||
csv | RFC-4180 CSV, honors .separator | ||
tabs | Tab separated | ||
json | A JSON array of objects | ||
line | One column = value per line, blank line between rows | ||
insert [table] | Emits INSERT INTO table VALUES(...); statements | ||
quote | SQL literals, 1,'a',NULL | ||
markdown | A pipe table with a ` | --- | ` separator row |
html | An HTML <tr><td> fragment | ||
ascii | 0x1F and 0x1E delimited, safe for arbitrary text |
sqlite3 :memory: ".mode markdown" ".headers on" "SELECT 1 AS id, 'Ada' AS name;" # | id | name | # |----|------| # | 1 | Ada | sqlite3 :memory: ".mode insert people" "SELECT 1 AS id, 'Ada' AS name;" # INSERT INTO people VALUES(1,'Ada'); sqlite3 :memory: ".mode quote" "SELECT 1, 'a', NULL;" # 1,'a',NULL
Headers are off and the mode is list by default, so an unconfigured sqlite3 db "SELECT
..." gives you bare pipe-separated values. That is ideal for cut and awful for reading.
Formatting
| Command | Effect |
|---|---|
.headers on | off | Column names in the output |
.width 12 30 8 | Column widths for column/box/table, 0 auto-sizes |
.nullvalue NULL | What to print for NULL, empty string by default |
.separator , | Field separator for list and csv |
.echo on | Print each statement before running it |
.timer on | Real, user, and system time per statement |
.changes on | Print the row count changed by each statement |
.bail on | Exit on the first error, essential in scripts |
.eqp on | Run EXPLAIN QUERY PLAN before every query |
.eqp full | Plus the full EXPLAIN bytecode |
sqlite3 app.db ".timer on" ".changes on" "UPDATE users SET name='Ada' WHERE id=1;" # changes: 1 total_changes: 1 # Run Time: real 0.001 user 0.000 sys 0.000
.bail on is the one to reach for in every script. Without it the CLI reports an error, keeps
going, and exits 0, so a broken migration looks like a success.
Input and output redirection
| Command | Effect |
|---|---|
.once file | Send the next query's output to a file |
.once -x | Open the next result as a spreadsheet |
.output file | Send all output to a file until changed |
.output | Back to stdout |
.read file.sql | Execute a file of SQL and dot-commands |
.import --csv file.csv table | Load a CSV |
.excel | Shorthand for .once -x |
# Export one query to CSV sqlite3 app.db ".mode csv" ".headers on" ".once users.csv" "SELECT * FROM users;" # Load a CSV, creating the table from the header row sqlite3 app.db ".import --csv --skip 0 users.csv users" # Load into an existing table, skipping the header line sqlite3 app.db ".import --csv --skip 1 users.csv users" # Run a migration file, stopping on the first error sqlite3 app.db ".bail on" ".read migrations/001_init.sql"
.import creates the table from the header row when the table does not exist, and appends
rows when it does. --skip 1 is what you want in the second case, since otherwise the header
becomes a data row. Every imported value arrives as TEXT, so cast on the way in or clean
up afterwards.
-- Import to a staging table, then cast into the real one .import --csv raw.csv staging INSERT INTO users(id, email) SELECT CAST(id AS INTEGER), email FROM staging; DROP TABLE staging;
Dumping, saving, backing up
| Command | Produces |
|---|---|
.dump [table] | The whole database as SQL text, wrapped in a transaction |
.dump --preserve-rowids | Includes explicit rowid values |
.save file.db | Write the current database to a file (VACUUM INTO under the hood) |
.backup [db] file.db | Online binary backup, safe on a live database |
.restore [db] file.db | Read a file back into the current connection |
.clone file.db | Copy into a new file, skipping unreadable rows |
.recover | Best-effort SQL extraction from a corrupt file |
sqlite3 app.db ".dump" > dump.sql # portable, text, diffable sqlite3 app.db ".dump users" > users.sql # one table sqlite3 new.db ".read dump.sql" # restore sqlite3 app.db ".backup backup.db" # binary, safe while writers are active sqlite3 app.db "VACUUM INTO 'backup.db';" # 3.27+, the modern equivalent sqlite3 broken.db ".recover" > salvage.sql # after corruption
.dump begins with PRAGMA foreign_keys=OFF; and a BEGIN TRANSACTION, which is what lets
it restore tables in any order. Never cp a live database, see the backup topic for why.
Extensions and startup config
sqlite3 app.db ".load ./vector0" # load a loadable extension, .so/.dylib/.dll
.load needs a build with extension loading enabled, which the stock macOS sqlite3 does
not always have. Homebrew's brew install sqlite gives you one that does, at
/opt/homebrew/opt/sqlite/bin/sqlite3.
~/.sqliterc runs before anything else, on every invocation.
-- ~/.sqliterc .mode box .headers on .nullvalue NULL .timer on .changes on
sqlite3 -init /dev/null app.db # ignore ~/.sqliterc, for reproducible scriptsAlways pass -init /dev/null from a script. A colleague's .sqliterc setting .mode box
will silently break your parsing, and this is the standard defense.
One-liners
Command-line flags cover the common dot-commands, so you can skip -cmd for these.
| Flag | Equivalent |
|---|---|
-csv | .mode csv |
-json | .mode json |
-box / -table / -line / -list / -markdown | The matching .mode |
-header / -noheader | .headers on / off |
-separator X | .separator X |
-nullvalue X | .nullvalue X |
-readonly | Open read-only |
-bail | .bail on |
-echo | .echo on |
-init file | Use file instead of ~/.sqliterc |
-cmd "..." | Run any dot-command before the SQL, repeatable |
sqlite3 -header -csv app.db "SELECT * FROM users;" > users.csv sqlite3 -json app.db "SELECT id, email FROM users LIMIT 10;" sqlite3 -line app.db "SELECT * FROM users WHERE id=1;" sqlite3 -cmd ".mode box" -cmd ".headers on" app.db "SELECT * FROM users LIMIT 3;" sqlite3 -readonly app.db "SELECT count(*) FROM users;" # Pipe SQL in from stdin echo "SELECT count(*) FROM users;" | sqlite3 app.db # A scriptable, quiet, fail-fast invocation sqlite3 -init /dev/null -bail -noheader -list app.db "SELECT id FROM users;"
-cmd runs before the SQL argument, which is the whole reason it exists. A dot-command
passed as a positional argument runs in sequence with the others, so
sqlite3 db ".mode box" "SELECT 1;" also works and is shorter.