SQLite schema.
Table
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL CHECK(length(name) > 0),
age INTEGER CHECK(age >= 0),
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
) STRICT;
Indexes
CREATE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX uniq_users_phone ON users(phone) WHERE phone IS NOT NULL;
CREATE INDEX idx_users_status_created ON users(status, created_at);
Partial index
CREATE INDEX idx_active ON users(email) WHERE active = 1;
Smaller, faster for filtered queries.
Expression index
CREATE INDEX idx_lower_email ON users(lower(email));
SELECT * FROM users WHERE lower(email) = 'x'; -- uses index
Covering index
CREATE INDEX idx_cover ON users(status, name, email);
SELECT status, name, email FROM users WHERE status = 'x'; -- no table lookup
Foreign keys
PRAGMA foreign_keys = ON; -- per connection
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title TEXT
);
ALTER TABLE
ALTER TABLE users ADD COLUMN phone TEXT;
ALTER TABLE users RENAME COLUMN old TO new;
ALTER TABLE users RENAME TO users_new;
ALTER TABLE users DROP COLUMN bio; -- 3.35+
For complex changes: create new, copy, drop, rename.
Views
CREATE VIEW active_users AS
SELECT * FROM users WHERE active = 1;
Triggers
CREATE TRIGGER update_timestamp
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id;
END;
Generated columns
CREATE TABLE orders (
items_json TEXT,
total REAL GENERATED ALWAYS AS (json_extract(items_json, '$.total')) STORED
);
CHECK constraint
CREATE TABLE users (
age INTEGER CHECK(age >= 0 AND age < 150),
status TEXT CHECK(status IN ('active', 'inactive'))
);
Without rowid
CREATE TABLE kv (
key TEXT PRIMARY KEY,
value TEXT
) WITHOUT ROWID;
For tables where PK is the natural ID.
Schema versioning
PRAGMA user_version;
PRAGMA user_version = 1;
Use for migration tracking.
ANALYZE
ANALYZE;
Updates query planner stats.
Common mistakes
- Forgetting
PRAGMA foreign_keys = ON. - Missing UNIQUE constraint → duplicate emails.
- No index on FK column.
- Too many indexes (insert cost).
- Storing JSON in BLOB instead of TEXT.
Read this next
If you want my SQLite migration patterns, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .