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

CommandShows
.helpEvery dot-command
.help modeHelp for one command, with its option list
.databasesAttached 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 --indentPretty-printed schema
.fullschemaSchema plus sqlite_stat1 contents
.indexes [table]Indexes, on one table or all
.dbinfoPage size, page count, encoding, and header fields
.stats onMemory and query statistics after each statement
.showEvery 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
ModeOutput
list1|Ada, pipe separated. The default
boxUnicode box-drawn table
tableASCII-art table with +---+ borders
qboxLike box, with quoted strings and typed blobs
csvRFC-4180 CSV, honors .separator
tabsTab separated
jsonA JSON array of objects
lineOne column = value per line, blank line between rows
insert [table]Emits INSERT INTO table VALUES(...); statements
quoteSQL literals, 1,'a',NULL
markdownA pipe table with a `---` separator row
htmlAn HTML <tr><td> fragment
ascii0x1F 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

CommandEffect
.headers on | offColumn names in the output
.width 12 30 8Column widths for column/box/table, 0 auto-sizes
.nullvalue NULLWhat to print for NULL, empty string by default
.separator ,Field separator for list and csv
.echo onPrint each statement before running it
.timer onReal, user, and system time per statement
.changes onPrint the row count changed by each statement
.bail onExit on the first error, essential in scripts
.eqp onRun EXPLAIN QUERY PLAN before every query
.eqp fullPlus 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

CommandEffect
.once fileSend the next query's output to a file
.once -xOpen the next result as a spreadsheet
.output fileSend all output to a file until changed
.outputBack to stdout
.read file.sqlExecute a file of SQL and dot-commands
.import --csv file.csv tableLoad a CSV
.excelShorthand 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

CommandProduces
.dump [table]The whole database as SQL text, wrapped in a transaction
.dump --preserve-rowidsIncludes explicit rowid values
.save file.dbWrite the current database to a file (VACUUM INTO under the hood)
.backup [db] file.dbOnline binary backup, safe on a live database
.restore [db] file.dbRead a file back into the current connection
.clone file.dbCopy into a new file, skipping unreadable rows
.recoverBest-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 scripts

Always 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.

FlagEquivalent
-csv.mode csv
-json.mode json
-box / -table / -line / -list / -markdownThe matching .mode
-header / -noheader.headers on / off
-separator X.separator X
-nullvalue X.nullvalue X
-readonlyOpen read-only
-bail.bail on
-echo.echo on
-init fileUse 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.