Postgres Table Design

Applies solid schema design practices when Claude builds Postgres tables.

Development / Databasedevelopmentdatabase
by AgentVoltv1.0.0Published 1y ago1 install
Free to sign up · every skill included with AgentVolt Pro

Before & after

Same prompt, same agent — the only difference is the skill.

PromptDesign a PostgreSQL schema for a user activity tracking system
Beforegeneric AI agent
When designing a Postgres schema for activity tracking, you want to think about the volume of data you'll be storing and how you'll query it. You'll probably have a users table, then an activity events table that stores things like event type, timestamp, and any relevant metadata. 

Some things to consider: Do you need indexes on timestamp columns for date range queries? Should you store all the event metadata in a JSON column, or break it out into separate columns? Think about normalization—are you duplicating any data? Also consider constraints like foreign keys to ensure data integrity.

It might be worth thinking about archival too—if you're storing millions of events, you don't want to scan the entire activity table on every query. Does that help, or would you like me to dive deeper into any specific area?
Afterwith Postgres Table Design
POSTGRES SCHEMA: User Activity Tracking

CORE TABLES:

1. USERS (Master dimension)
   Column: id [UUID PRIMARY KEY]
   Column: email [VARCHAR(255) NOT NULL UNIQUE]
   Column: created_at [TIMESTAMPTZ NOT NULL DEFAULT now()]
   Constraint: UNIQUE(email)
   Index: idx_users_created_at (for monthly cohort analysis)

2. ACTIVITY_EVENTS (Fact table—write-heavy)
   Column: id [BIGSERIAL PRIMARY KEY]
   Column: user_id [UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE]
   Column: event_type [VARCHAR(50) NOT NULL] -- e.g., 'login', 'purchase', 'view'
   Column: event_time [TIMESTAMPTZ NOT NULL DEFAULT now()]
   Column: metadata [JSONB DEFAULT '{}'] -- flexible attributes
   Column: session_id [UUID] -- group events into sessions
   
   Constraints:
   • FOREIGN KEY(user_id) ensures referential integrity
   • ON DELETE CASCADE removes events if user deleted (adjust if audit required)
   
   Indexes (CRITICAL for query performance):
   • PRIMARY KEY: idx_events_id (default)
   • idx_events_user_id (filter by user)
   • idx_events_event_time (range queries on date)
   • idx_events_user_time (composite: SELECT * FROM activity WHERE user_id = X AND event_time > NOW() - '7 days')
   • idx_events_event_type (cardinality ~20, speeds up reports by event type)
   • idx_events_metadata_gin (GIN index on JSONB for flexible queries on metadata fields)
   
   Partitioning (If >10M rows/month):
   • Partition by RANGE(event_time) quarterly: activity_q1_2025, activity_q2_2025
   • Keeps index size manageable, speeds up archival (DROP PARTITION vs DELETE)

3. EVENT_TYPES (Reference table—normalization)
   Column: id [SMALLINT PRIMARY KEY]
   Column: type_name [VARCHAR(50) UNIQUE NOT NULL]
   Column: description [TEXT]
   
   Why: VARCHAR(50) in activity_events is safe, but if cardinality is truly <100, consider foreign key to event_types for space efficiency
   Trade-off: Adds join cost, saves ~10% storage

QUERY PATTERNS OPTIMIZED:

Pattern 1: "Get all events for a user in past 7 days"
   SELECT * FROM activity_events 
   WHERE user_id = $1 AND event_time > now() - '7 days'::interval
   • Uses: idx_events_user_time
   • Cost: ~10ms (no full table scan)

Pattern 2: "Count events by type today"
   SELECT event_type, COUNT(*) FROM activity_events
   WHERE event_time >= DATE_TRUNC('day', now())
   GROUP BY event_type
   • Uses: idx_events_event_time, idx_events_event_type
   • Cost: ~100ms (depends on today's volume)

Pattern 3: "Find users with property X in metadata"
   SELECT DISTINCT user_id FROM activity_events
   WHERE metadata->>'campaign_id' = 'camp_123'
   • Uses: idx_events_metadata_gin
   • Cost: ~500ms (slower, JSONB queries less optimized than columns)

NORMALIZATION TRADE-OFFS:

Option A (Current—denormalized):
   • Pros: Fast writes, simple schema, flexible metadata
   • Cons: Some data duplication if user attributes change

Option B (Fully normalized):
   • Add columns to activity_events: user_segment, user_country (denormalize)
   • Pros: Faster analysis queries (no join needed)
   • Cons: Must update activity_events if user segment changes retroactively (hard)
   • Recommendation: Use Option A, denormalize ONLY for immutable user attributes at event time

ARCHIVAL STRATEGY:
   • After 12 months: Move activity_q1_2024 to archive schema (or cold storage)
   • Keep indexes only on last 12 months
   • Reduces active index footprint from 40GB to 10GB

TRAP TO AVOID:
   • Don't put user_email in activity_events (denormalization sin). If email changes, you now have stale data.
   • Don't rely on metadata alone—JSON queries are 5x slower than indexed columns.
   • Don't forget ON DELETE CASCADE—orphaned activity rows break reporting.

FINAL SCHEMA (DDL):
   CREATE TABLE users (
     id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
     email VARCHAR(255) NOT NULL UNIQUE,
     created_at TIMESTAMPTZ NOT NULL DEFAULT now()
   );

   CREATE TABLE activity_events (
     id BIGSERIAL PRIMARY KEY,
     user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
     event_type VARCHAR(50) NOT NULL,
     event_time TIMESTAMPTZ NOT NULL DEFAULT now(),
     metadata JSONB DEFAULT '{}',
     session_id UUID
   ) PARTITION BY RANGE(event_time);

   CREATE INDEX idx_events_user_time ON activity_events(user_id, event_time DESC);
   CREATE INDEX idx_events_event_type ON activity_events(event_type);
   CREATE INDEX idx_events_metadata_gin ON activity_events USING GIN(metadata);

Estimated footprint: 100M events/month = 15GB + indexes, 12-month retention.

About this skill


name: postgres-table-design description: Use when Applies solid schema design practices when Claude builds Postgres tables.

Postgres Table Design

Postgres Table Design, from gives Claude a checklist for solid relational schema design, covering indexing, normalization, and constraint choices, to avoid the common database mistakes that are expensive to fix once an app has real data.

What you get

  • Schema design guidance covering indexes, constraints, and normalization.

Customize your output

  • Adjust for a different database engine or existing schema conventions.

Example output

A Postgres schema or migration that follows solid design practices.

Best for

Developers using Claude to design or extend a Postgres database.

SKILL.md preview

SKILL.md
---
name: postgres-table-design
description: Use this skill when Claude is designing or reviewing Postgres tables, to apply solid indexing, normalization, and constraint practices.
version: 1.0.0
category: Development / Database
author: AgentVolt
license: proprietary
tags:
  - development
  - database
  - standard
---

# Postgres Table Design

Applies a checklist of relational schema design practices — indexing, normalization, constraint choices — when building or reviewing Postgres tables, so cheap mistakes stay cheap.

## When to use

… (sign up to view the full skill)
Sign up to view, copy, and install the full skill

More development skills

View all Development skills →