Appearance
CREATE TABLE
This is where you stop poking sample data and start designing the shape your UI will live on.
Minimal table
sql
CREATE TABLE companies (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
website TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);Comments for the code above:
INTEGER PRIMARY KEYin SQLite doubles as the rowid - auto-increments when you omitidon insertNOT NULL- requiredDEFAULT (datetime('now'))- fill created_at if the insert doesn't provide it
Common constraints
sql
CREATE TABLE contacts (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
company_id INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
CHECK (length(name) > 0)
);| Constraint | Meaning |
|---|---|
PRIMARY KEY | unique identity |
UNIQUE | no duplicate values (NULLs are special-cased) |
NOT NULL | required |
DEFAULT … | fill if omitted |
CHECK (…) | custom rule |
REFERENCES other(id) | foreign key (next chapter) |
ALTER / DROP
sql
ALTER TABLE contacts ADD COLUMN phone TEXT;
DROP TABLE contacts; -- gone. forever. (unless you have backups)SQLite's ALTER TABLE is more limited than Postgres - you can add columns, rename, etc., but big reshapes sometimes mean "new table + copy + swap". Migrations in frameworks hide that pain.
Naming habits I like
- tables: plural
snake_case→contacts,deal_stages - columns:
snake_case→company_id,created_at - boolean-ish:
is_active, or timestamps likedeleted_atfor soft delete - money: store cents as
INTEGERor useREALcarefully - know your product rules
From TypeScript type → table
ts
type Company = {
id: number
name: string
website: string | null
createdAt: string
}sql
CREATE TABLE companies (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
website TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);Same fields. SQL just spells the nullability and defaults out loud.
Next: linking tables together - Keys & relations.