3.1 KiB
3.1 KiB
| name | description |
|---|---|
| design-postgres-tables | Design PostgreSQL table schemas. Use when modeling new data, adding tables, or reviewing/improving existing schema design. |
Design Postgres Tables Skill
Use when: designing new tables, modeling relationships, reviewing schema, or planning migrations.
Column Conventions
- Primary key:
id UUID PRIMARY KEY DEFAULT gen_random_uuid()(prefer UUID over serial for distributed systems; use BIGSERIAL if you need sortable IDs) - Timestamps: always include
created_at TIMESTAMPTZ DEFAULT NOW()andupdated_at TIMESTAMPTZ DEFAULT NOW() - Soft delete:
deleted_at TIMESTAMPTZ DEFAULT NULL(NULL = active) - Foreign keys: name as
<table_singular>_id, e.g.user_id,project_id - Use
TEXToverVARCHAR(n)unless you need DB-enforced length limits - Use
BOOLEANnotSMALLINTfor true/false - Use
NUMERIC(precision, scale)for money, neverFLOAT
Naming Conventions
- Tables:
snake_case, plural:users,project_sessions,api_tokens - Columns:
snake_case, singular - Indexes:
idx_<table>_<column(s)>:idx_users_email - Foreign keys:
fk_<table>_<referenced_table>:fk_orders_users - Unique constraints:
uq_<table>_<column(s)>:uq_users_email
Standard Table Template
CREATE TABLE items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Business columns
name TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'inactive', 'archived')),
-- Relationships
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Metadata
metadata JSONB DEFAULT '{}',
-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
-- Indexes
CREATE INDEX idx_items_user_id ON items(user_id);
CREATE INDEX idx_items_status ON items(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_items_created_at ON items(created_at DESC);
-- Auto-update updated_at
CREATE TRIGGER items_updated_at
BEFORE UPDATE ON items
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
Relationship Patterns
- One-to-many: FK on the "many" side
- Many-to-many: junction table with
<a>_id+<b>_id+ its own PK + timestamps - Polymorphic:
entity_type TEXT + entity_id UUID(add check constraint for valid types)
Index Strategy
- Index all foreign keys
- Index columns used in WHERE clauses with high selectivity
- Partial indexes for filtered queries:
WHERE deleted_at IS NULL - Composite indexes: put equality columns first, range columns last
- JSONB: use GIN index for
@>containment queries
Design Checklist
- Every table has UUID PK + created_at + updated_at
- Foreign keys have explicit ON DELETE behavior (CASCADE vs RESTRICT vs SET NULL)
- Enum-like columns use CHECK constraints or a lookup table
- Money stored as NUMERIC not FLOAT
- Indexes on all FK columns
- No storing arrays as comma-separated strings (use ARRAY[] or junction table)
- JSONB for truly dynamic/schemaless data only — not as a crutch