SQLite Cheat Sheet

sqlite3 is the command-line shell that ships with SQLite. This sheet lists the sqlite3 commands and SQL you use most, from dot commands and CSV import to upserts, dates, JSON and PRAGMAs, with a worked example under every table.

Updated Oct 12, 2026Tested against SQLite 3.54.0, Turso CLI 1.0.34

Also works with Turso.

Every example below uses one small shop database, , with three tables: , and .

Opening and Exiting sqlite3

opens a file and creates it if it does not exist. Build a connection string for your app with the SQLite connection string generator.

CommandWhat it does
Open a database file, creating it if missing
Start an in-memory database, gone when you exit
Open without write access
Read-only through a URI filename
Run one statement and exit
Run a SQL file and exit
Stop at the first error instead of carrying on
Run a dot command before the session starts
Read a startup file instead of
Print the SQLite version
Close the current database and open another
Reopen read-only from inside the shell
List every dot command
Full help for one command
Exit the shell ( and Ctrl-D work too)
Exit with a return code, useful in scripts

In 1bench: open a SQLite file on a remote server over SSH, either live in place or as a local snapshot you sync back. See the SQLite GUI.

Example: run a migration script that stops on the first error and returns a non-zero exit code for CI.

bash
sqlite3 -bail shop.db < migrations/004_add_orders_index.sql
echo "exit code: $?"   # 1 if any statement failed

Dot Commands

Dot commands start with a period, take no semicolon and only work in the shell.

CommandWhat it does
Show tables and views
Show tables matching a LIKE pattern
Print the CREATE statement for a table
Print the CREATE statement for everything
Schema plus the sqlite_stat tables, indented
Describe a table: columns, types, NOT NULL, defaults, key
Same column list as a query you can filter
Every table, index, view and trigger
List indexes you created
Include the automatic UNIQUE and PRIMARY KEY indexes
Attached databases and their file paths
Print column names above results
Show NULL instead of an empty string
Print run time after every statement
Print the number of rows each statement changed
Current settings: mode, headers, separators, output
Bind a value for in later queries
Run a shell command without leaving sqlite3
Page size, encoding, schema version and other header info

Example: find the tables in an unfamiliar database file and describe the one you need.

bash
sqlite3 shop.db '.tables'
sqlite3 shop.db '.schema orders'
sqlite3 -box shop.db "SELECT name, type, \"notnull\", pk FROM pragma_table_info('orders');"

Output Modes

Interactive sessions print boxes by default since 3.53. Scripts and pipes get the plain format, so set a mode when the output feeds another tool.

CommandWhat it does
Show the current mode
List every available mode
Unicode box table with headers
ASCII table, safe to paste anywhere
Aligned columns, no borders
Markdown table
Comma-separated values, quoted where needed
Tab-separated values
One JSON array of row objects
One line per field, good for wide rows
Rows as INSERT statements for another table
Pipe-separated, the batch default
Set column widths for column, table and box modes
Send all further output to a file
Send output back to the terminal
Send only the next result to a file
Open the next result in your spreadsheet app
JSON output from the command line
Box output from the command line

Example: write the paid orders to a JSON file from a script.

bash
sqlite3 shop.db <<'SQL'
.mode json
.once paid_orders.json
SELECT id, user_id, total, created_at FROM orders WHERE status = 'paid';
SQL

Tables

already auto-assigns ids. Add only when deleted ids must never come back.

CommandWhat it does
Create a table only if missing
Ids that are never reused after a delete
Reject values that do not match the column type
Copy a query result into a new table
Add a column; NOT NULL needs a default
Rename a column, indexes and views follow
Rename a table
Remove a column
Add NOT NULL to an existing column (3.53+)
Remove NOT NULL (3.53+)
Add a CHECK constraint (3.53+)
Remove a named constraint (3.53+)
DestructiveEmpty a table; SQLite has no TRUNCATE
DestructiveDelete a table and its data

Example: the and tables every example on this page uses.

sql
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  name TEXT,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  status TEXT NOT NULL DEFAULT 'pending',
  total REAL NOT NULL,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Writing Data

Wrap bulk writes in and . Each statement outside a transaction is its own commit and syncs to disk.

CommandWhat it does
Insert one row
Insert several rows in one statement
Return the new id and defaults
Insert the result of a query
Upsert: update the row that already exists
Skip rows that would break a constraint
Older spelling of DO NOTHING
Delete the clashing row, then insert (see Gotchas)
Update and return the changed rows
Update from another table
DestructiveDelete rows and list what went
Rows changed by the last statement
Rowid of the last INSERT on this connection
Group writes into one transaction
Undo everything since BEGIN

Example: upsert a user by email, keep the original id and created_at, and return the row.

sql
INSERT INTO users (email, name)
VALUES ('[email protected]', 'Ada Lovelace')
ON CONFLICT(email) DO UPDATE SET name = excluded.name
RETURNING id, email, name, created_at;

Select and Filtering

SQLite has no TOP. Use , with for paging.

CommandWhat it does
Match any value in a list
Newest first, third page of 10
Pattern match, case-insensitive for ASCII
Unix-style pattern, case-sensitive
Inclusive range
Find NULLs; never matches
Case-insensitive equality
Unique values
Count and total per group
Filter groups after aggregating
First value that is not NULL
Conditional value
Short form of a two-way CASE
Join values into one string per group
Join strings with a separator, skips NULLs
Text before the @
Number rows within each group
Running total per user
Name a subquery with a CTE
Pick a random row

Example: each user's most recent order, using a window function.

sql
SELECT id, user_id, status, total, created_at
FROM (
  SELECT *, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
  FROM orders
)
WHERE rn = 1;

Joins

SQLite supports INNER, LEFT, RIGHT, FULL OUTER and CROSS joins. RIGHT and FULL need 3.39 or later.

CommandWhat it does
Inner join, only users with orders
Every user, NULLs where there is no order
Users with no orders at all
Every order, NULLs where the user is missing
Every row from both sides
Every combination of rows
Self join, pairs of orders by the same user
Semi-join: users with at least one order
Combine two results, duplicates removed

Example: order count and lifetime spend per user, including users who never ordered.

sql
SELECT u.email,
       count(o.id)                         AS orders,
       round(coalesce(sum(o.total), 0), 2) AS spend
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid'
GROUP BY u.id
ORDER BY spend DESC;

Import, Export and Backup

creates the table from the CSV header when the table does not exist yet.

CommandWhat it does
Import a CSV into a new table, header row becomes columns
Import into an existing table, skip the header
then Import tab-separated data into a new table
Export a query to CSV in one line
Export the next query to CSV from the shell
Run a SQL file inside the session
Whole database as SQL text
One table as SQL text
Dump to a file for version control or migration
Rebuild a database from a dump
Schema only, no data
Copy the open database safely, even while in use
DestructiveReplace the open database with a backup
Write a compacted copy to a new file
Rebuild the file and reclaim free pages; locks it while running
Open a second database file in the same session
Copy rows between attached databases
Close an attached database
Pull what it can out of a corrupt file as SQL
Print the SQL that turns one database into the other

In 1bench: export a SQLite table to SQL, CSV, JSON or NDJSON, and import those formats back with an option to empty the table first. See the SQLite GUI.

Example: load a CSV export into a staging table, then upsert it into in one transaction.

bash
sqlite3 -bail shop.db <<'SQL'
CREATE TEMP TABLE staging (email TEXT, name TEXT);
.import --csv --skip 1 new_users.csv staging
BEGIN;
INSERT INTO users (email, name) SELECT email, name FROM staging WHERE true
ON CONFLICT(email) DO UPDATE SET name = excluded.name;
COMMIT;
SQL

PRAGMAs

Most PRAGMAs apply to the current connection only. and are stored in the file.

CommandWhat it does
Enforce foreign keys; off by default, per connection
Readers stop blocking the writer; persists in the file
Wait up to 5 seconds on a lock instead of failing
Fewer fsyncs, safe with WAL
Full corruption check, returns ok when healthy
Faster check that skips index contents
List rows that break a foreign key
Foreign keys defined on a table
Indexes on a table
Store a schema version number for migrations
File size is page_size times page_count
Free pages a VACUUM would reclaim
Fold the WAL into the main file and shrink it
Refresh planner stats where needed; run before closing
Features this build was compiled with

In 1bench: the SQLite GUI overview shows the file size with page count and page size, table and index counts, encoding, journal mode and SQLite version, without running these PRAGMAs by hand.

Example: the settings most apps run on every new connection.

sql
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;

Dates and Times

SQLite has no date type. Store ISO 8601 text or Unix seconds, and the functions below read both. is UTC.

CommandWhat it does
Current UTC time,
Current local time
Today's date
UTC timestamp, handy as a column default
Current Unix time in seconds
Text date to Unix seconds
Unix seconds to a timestamp
Unix milliseconds to a timestamp with fractions
Year from a date, as text
Year and month, for grouping
Seven days ago
First day of this month
Last day of this month (3.54+)
Next Monday, or today if it is Monday
Days between two dates
Difference as years, months, days and time
Rows from the last 30 days

Example: revenue per month from the table.

sql
SELECT strftime('%Y-%m', created_at) AS month,
       count(*)                    AS orders,
       round(sum(total), 2)        AS revenue
FROM orders
WHERE status = 'paid'
GROUP BY month
ORDER BY month;

JSON

JSON is stored in TEXT columns and the functions are built in. returns a SQL value, returns JSON. Check a payload with the JSON formatter.

CommandWhat it does
Read one field as a SQL value
Read a nested object as JSON
Read a nested field
First array element
Last array element
Function form of
One row per array element
One row per node, walks the whole document
Number of elements in an array
Build an object from columns
Aggregate rows into a JSON array
Set a value, creating the path if needed
Append to an array
Remove a key
1 if the text is valid JSON
Indented JSON for reading
Binary JSONB form, faster to query, stored as BLOB

Example: index a JSON field and count events by type, so the filter uses the index.

sql
CREATE INDEX idx_events_type ON events (payload ->> '$.type');

SELECT payload ->> '$.type' AS type, count(*) AS n
FROM events
WHERE payload ->> '$.type' IN ('login', 'purchase')
GROUP BY type;

Indexes and Query Plan

Check the plan before adding an index. SCAN reads every row, SEARCH uses an index.

CommandWhat it does
Composite index, leftmost column first
Unique index on an expression
Partial index over a subset of rows
Remove an index
Show SCAN or SEARCH for each table
Print the plan before every query
Suggest indexes for the next query
Gather stats so the planner picks better indexes
Stats that ANALYZE stored
Rebuild an index
Space used per table and index (replaces sqlite3_analyzer)

Example: confirm a lookup goes from a full scan to an index search.

sql
EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id = 1 AND status = 'paid';
-- `--SCAN orders

CREATE INDEX idx_orders_user_status ON orders (user_id, status);

EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id = 1 AND status = 'paid';
-- `--SEARCH orders USING INDEX idx_orders_user_status (user_id=? AND status=?)

Turso and libSQL

Turso hosts libSQL, a fork of SQLite, so the SQL above runs unchanged. The CLI manages databases and opens a shell with the common dot commands.

CommandWhat it does
Install the Turso CLI on macOS
Install script for macOS and Linux
Log in through the browser
Log in on a machine with no browser
Print your platform API token
Create a group to hold databases
Create an empty database
Create a database from a local SQLite file
Upload a SQLite file; it must be in WAL mode
Print the URL for your app
Create an auth token for one database
Read-only token that expires in 7 days
Open a SQL shell on the closest replica
Run one statement and exit
Download a snapshot to a local file
DestructiveDelete a database and its replicas
Local libSQL server on port 8080, no account needed

Example: move a local SQLite file to Turso and get the URL and token for your app.

bash
sqlite3 shop.db 'PRAGMA journal_mode = WAL;'
turso db import shop.db
turso db show shop --url        # libsql://... URL for the client
turso db tokens create shop     # pass as the auth token

Gotchas

  • Foreign keys are off by default and the setting does not persist. Run on every connection, or and REFERENCES checks silently do nothing.
  • deletes the clashing row and inserts a new one. Columns you leave out reset to their defaults, and with foreign keys on, removes the child rows. In testing, replacing a user wiped all of their orders. Use instead.
  • Column types are suggestions unless the table is STRICT. An INTEGER column happily stores as text. Store money as integer cents too, since REAL sums give results like 62.489999999999995.
  • into a table that already exists treats the header line as data. Add .
  • cannot add a UNIQUE or PRIMARY KEY column ("Cannot add a UNIQUE column"). Add the plain column, then on it.
  • "database is locked" means another connection holds the write lock. Turn on WAL, set and keep write transactions short.

More on SQLite

Work with SQLite without memorizing it

1bench is a desktop GUI for SQLite. Connect, browse your data and run queries in one window, with the CLI still there when you want it.

Open SQLite in 1bench

SQLite FAQ

Can SQLite handle multiple connections?
Yes for reads, one at a time for writes. Any number of connections can read the same file, but only one can write at a given moment, and the others get "database is locked" unless they wait. Run `PRAGMA journal_mode = WAL` once so readers no longer block the writer, and set `PRAGMA busy_timeout = 5000` on every connection so a second writer waits up to 5 seconds instead of failing at once. Keep write transactions short.
Does SQLite have a date type?
No. Dates are stored as TEXT (`'2026-10-12 14:00:00'`), as INTEGER Unix seconds, or as REAL Julian day numbers, and the date functions accept all three. Pick one format per column and stick to it. ISO 8601 text sorts and compares correctly as plain strings, which is why `created_at >= date('now', '-30 days')` works, while a mix of formats in one column silently breaks comparisons.
Does SQLite have a boolean type?
No. Booleans are integers: `TRUE` and `FALSE` are keywords for 1 and 0, and `typeof(TRUE)` returns integer. A column declared BOOLEAN gets numeric affinity, so it will also accept `'yes'` or 2 unless you add `CHECK (active IN (0, 1))` or use a STRICT table with an INTEGER column.
Is SQLite case sensitive?
It depends on the operator. `=` compares text case-sensitively, so `'abc' = 'ABC'` is false unless you add `COLLATE NOCASE`. `LIKE` ignores case, but only for ASCII letters: `'E' LIKE 'e'` is true and `'É' LIKE 'é'` is false. `GLOB` is always case-sensitive. Table and column names are not case sensitive, so `FROM Users` and `FROM users` hit the same table.
Does SQLite support stored procedures?
No. There is no CREATE PROCEDURE or CREATE FUNCTION in SQL. Use views for reusable queries, triggers for logic that should run on INSERT, UPDATE or DELETE, and CTEs (including `WITH RECURSIVE`) for multi-step queries. Anything procedural lives in your application code, which runs in the same process as SQLite, so there is no network round trip to save.