Learn-skills.dev sqlite
SQLite database commands, queries, and embedded database patterns. Use when user mentions "sqlite", "sqlite3", "embedded database", "local database", "lightweight db", "sqlite schema", "sqlite backup", "mobile database", or working with .db/.sqlite files.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/1mangesh1/dev-skills-collection/sqlite" ~/.claude/skills/neversight-learn-skills-dev-sqlite && rm -rf "$T"
manifest:
data/skills-md/1mangesh1/dev-skills-collection/sqlite/SKILL.mdsource content
SQLite
sqlite3 CLI Basics
sqlite3 myapp.db # open or create a database sqlite3 :memory: # in-memory database sqlite3 myapp.db ".tables" # one-shot command sqlite3 myapp.db < schema.sql # run SQL from file
Common dot-commands inside the shell:
.tables -- list all tables .schema -- show CREATE statements for all tables .schema users -- show CREATE for specific table .headers on -- show column headers in output .mode column -- aligned columns (also: csv, json, table, line, tabs) .width 20 30 10 -- set column widths for column mode .databases -- list attached databases .indexes users -- list indexes for a table .quit -- exit
Create Tables, Insert, Update, Delete
CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, -- alias for rowid, auto-increments name TEXT NOT NULL, email TEXT UNIQUE, created_at TEXT DEFAULT (datetime('now')) ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), total REAL NOT NULL, status TEXT DEFAULT 'pending', created_at TEXT DEFAULT (datetime('now')) ); INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); INSERT INTO orders (user_id, total, status) VALUES (1, 49.99, 'completed'), (1, 25.00, 'pending'), (2, 120.00, 'completed'); UPDATE users SET email = 'newalice@example.com' WHERE id = 1; DELETE FROM orders WHERE status = 'pending' AND created_at < datetime('now', '-30 days');
RETURNING clause available in 3.35.0+. Check with
sqlite3 --version.
Queries
Joins, Subqueries, GROUP BY
SELECT o.id, u.name, o.total FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'completed'; SELECT u.name, COALESCE(SUM(o.total), 0) AS lifetime_value FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.name; SELECT * FROM users WHERE id IN ( SELECT user_id FROM orders GROUP BY user_id HAVING SUM(total) > 100 ); SELECT status, COUNT(*) AS cnt, ROUND(AVG(total), 2) AS avg_total FROM orders GROUP BY status HAVING cnt > 1 ORDER BY avg_total DESC;
CTEs and Window Functions
WITH monthly AS ( SELECT strftime('%Y-%m', created_at) AS month, SUM(total) AS revenue FROM orders WHERE status = 'completed' GROUP BY 1 ) SELECT month, revenue, revenue - LAG(revenue) OVER (ORDER BY month) AS change FROM monthly; SELECT user_id, total, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY total DESC) AS rn, SUM(total) OVER (PARTITION BY user_id) AS user_total FROM orders;
Output Modes
sqlite3 -header -csv myapp.db "SELECT * FROM users;" > users.csv sqlite3 -json myapp.db "SELECT * FROM users;" sqlite3 -header -column myapp.db "SELECT * FROM users;"
Inside the shell:
.mode csv -- comma-separated .mode json -- JSON array of objects .mode column -- aligned columns .mode table -- ASCII table borders (3.36+) .mode line -- one value per line .separator "\t" -- custom separator for list mode
Import and Export CSV
# Export sqlite3 -header -csv myapp.db "SELECT * FROM users;" > users.csv # Import into existing table sqlite3 myapp.db <<'EOF' .mode csv .import users.csv users EOF
For large imports, wrap in a transaction for speed:
BEGIN; .mode csv .import large_file.csv target_table COMMIT;
Indexes and EXPLAIN QUERY PLAN
CREATE INDEX idx_orders_user ON orders(user_id); CREATE INDEX idx_orders_status_date ON orders(status, created_at); CREATE UNIQUE INDEX idx_users_email ON users(email); EXPLAIN QUERY PLAN SELECT * FROM orders WHERE user_id = 1;
Key things in the output:
- SCAN TABLE -- no usable index, reads every row
- SEARCH TABLE ... USING INDEX -- index is being used
- USING COVERING INDEX -- query answered entirely from index
- USE TEMP B-TREE -- sort or GROUP BY without index
WAL Mode and Concurrency
PRAGMA journal_mode=WAL; -- returns "wal" on success PRAGMA busy_timeout=5000; -- wait up to 5s instead of failing immediately
- One writer at a time, but readers are not blocked
- Creates
and-wal
files alongside the database-shm - Force checkpoint:
PRAGMA wal_checkpoint(TRUNCATE); - Set once; persists across connections
Backup
.backup main backup.db -- online backup (safe during use) .dump -- full SQL text dump .dump users -- dump a single table
sqlite3 myapp.db .dump > full_dump.sql # dump to file sqlite3 restored.db < full_dump.sql # restore from dump
VACUUM rebuilds the database file, reclaiming space:
VACUUM; -- rebuild in place VACUUM INTO 'compact.db'; -- compacted copy (3.27+)
JSON Support
Available in SQLite 3.38+ (built-in) or via the JSON1 extension:
CREATE TABLE events (id INTEGER PRIMARY KEY, data TEXT NOT NULL); INSERT INTO events (data) VALUES ('{"type":"click","page":"/home","tags":["mobile","v2"]}'); -- Extract values SELECT json_extract(data, '$.type') AS event_type FROM events; SELECT data->>'$.page' AS page FROM events; -- ->> operator (3.38+) -- Iterate over arrays SELECT e.id, j.value AS tag FROM events e, json_each(json_extract(e.data, '$.tags')) j; -- Modify JSON UPDATE events SET data = json_set(data, '$.processed', true) WHERE id = 1; -- Build JSON in queries SELECT json_object('id', id, 'name', name) FROM users; SELECT json_group_array(json_object('id', id, 'name', name)) FROM users;
Full-Text Search (FTS5)
CREATE VIRTUAL TABLE articles_fts USING fts5(title, body, content=articles, content_rowid=id); -- Populate from existing table INSERT INTO articles_fts(rowid, title, body) SELECT id, title, body FROM articles; -- Search with boolean operators SELECT *, rank FROM articles_fts WHERE articles_fts MATCH 'database AND performance' ORDER BY rank; -- Highlight matches SELECT highlight(articles_fts, 1, '<b>', '</b>') FROM articles_fts WHERE articles_fts MATCH 'sqlite'; -- Keep in sync with triggers CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN INSERT INTO articles_fts(rowid, title, body) VALUES (new.id, new.title, new.body); END; CREATE TRIGGER articles_ad AFTER DELETE ON articles BEGIN INSERT INTO articles_fts(articles_fts, rowid, title, body) VALUES ('delete', old.id, old.title, old.body); END;
Date and Time Functions
SQLite stores dates as TEXT, REAL, or INTEGER. Built-in functions handle ISO-8601 strings:
SELECT datetime('now'); -- 2025-01-15 08:30:00 SELECT date('now', '-7 days'); -- 7 days ago SELECT strftime('%Y-%m', 'now'); -- current year-month SELECT strftime('%s', 'now'); -- unix timestamp SELECT datetime('2025-01-15', '+3 months', '-1 day'); -- date arithmetic SELECT julianday('now') - julianday(created_at) AS days_old FROM users;
Attach Multiple Databases
ATTACH DATABASE 'archive.db' AS archive; SELECT * FROM main.users u JOIN archive.orders o ON o.user_id = u.id; INSERT INTO archive.orders SELECT * FROM main.orders WHERE created_at < '2024-01-01'; DELETE FROM main.orders WHERE created_at < '2024-01-01'; DETACH DATABASE archive;
Pragmas
-- Performance PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; -- faster writes (safe with WAL) PRAGMA cache_size=-64000; -- 64MB page cache (negative = KB) PRAGMA mmap_size=268435456; -- memory-map up to 256MB PRAGMA temp_store=MEMORY; -- temp tables in memory -- Integrity PRAGMA foreign_keys=ON; -- enforce foreign keys (off by default!) PRAGMA integrity_check; -- full database integrity scan -- Info PRAGMA table_info(users); -- column details PRAGMA index_list(users); -- indexes on a table PRAGMA compile_options; -- build-time options (check for JSON, FTS5)
Common Patterns
Config Key-Value Store
CREATE TABLE config ( key TEXT PRIMARY KEY, value TEXT, updated_at TEXT DEFAULT (datetime('now')) ); INSERT OR REPLACE INTO config (key, value) VALUES ('theme', 'dark'); SELECT value FROM config WHERE key = 'theme';
Local Cache with Expiry
CREATE TABLE cache (key TEXT PRIMARY KEY, value TEXT, expires_at TEXT); INSERT OR REPLACE INTO cache (key, value, expires_at) VALUES ('api:/users', '{"data":[...]}', datetime('now', '+1 hour')); SELECT value FROM cache WHERE key = 'api:/users' AND expires_at > datetime('now'); DELETE FROM cache WHERE expires_at <= datetime('now');
CLI Tool Storage
CREATE TABLE history ( id INTEGER PRIMARY KEY, command TEXT NOT NULL, args TEXT, exit_code INTEGER, ran_at TEXT DEFAULT (datetime('now')) ); SELECT command, args, ran_at FROM history ORDER BY ran_at DESC LIMIT 20; SELECT command, COUNT(*) AS cnt FROM history GROUP BY command ORDER BY cnt DESC;
Test Fixtures
sqlite3 test.db < schema.sql && sqlite3 test.db < seed.sql # In-memory: Python sqlite3.connect(":memory:") / Node new Database(":memory:")
BEGIN; INSERT INTO users (id, name, email) VALUES (1, 'Test User', 'test@example.com'), (2, 'Admin', 'admin@example.com'); INSERT INTO orders (user_id, total, status) VALUES (1, 99.99, 'completed'), (2, 50.00, 'pending'); COMMIT;