| name | sql-schema-generator |
| description | Generate SQL database schema files with tables, relationships, indexes, and constraints for PostgreSQL, MySQL, or SQLite. Triggers on "create SQL schema", "generate database schema", "SQL tables for", "database design for". |
SQL Schema Generator
Generate complete, normalized SQL database schemas with tables, relationships, indexes, and constraints.
Output Requirements
File Output: .sql files
Naming Convention: schema.sql, 001_create_tables.sql, {feature}_schema.sql
Dialects: PostgreSQL (default), MySQL, SQLite
When Invoked
Immediately generate a complete SQL schema file. Default to PostgreSQL syntax unless otherwise specified.
SQL Best Practices
Naming Conventions
- Tables: plural, snake_case (
users,order_items) - Columns: singular, snake_case (
user_id,created_at) - Primary keys:
idor{table}_id - Foreign keys:
{referenced_table_singular}_id - Indexes:
idx_{table}_{column(s)} - Constraints:
{table}_{type}_{column}(e.g.,users_email_unique)
Data Types (PostgreSQL)
| Use Case | Type |
|---|---|
| Primary Key | BIGSERIAL or UUID |
| Foreign Key | BIGINT or UUID |
| Short text | VARCHAR(n) |
| Long text | TEXT |
| Boolean | BOOLEAN |
| Integer | INTEGER, BIGINT |
| Decimal/Money | NUMERIC(precision, scale) |
| Date | DATE |
| Timestamp | TIMESTAMPTZ |
| JSON | JSONB |
| Enum | Custom TYPE |
Standard Columns
Every table should consider:
id- Primary keycreated_at- Record creation timestampupdated_at- Last modification timestampdeleted_at- Soft delete timestamp (if applicable)
Schema Templates
User Authentication System
-- Enable UUID extension (PostgreSQL)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
avatar_url TEXT,
email_verified_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT users_email_unique UNIQUE (email)
);
-- Index for email lookups
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
-- Sessions table
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL,
ip_address INET,
user_agent TEXT,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT sessions_token_unique UNIQUE (token_hash)
);
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_sessions_expires_at ON sessions(expires_at);
-- Password reset tokens
CREATE TABLE password_resets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT password_resets_token_unique UNIQUE (token_hash)
);
CREATE INDEX idx_password_resets_user_id ON password_resets(user_id);
E-commerce Schema
-- Customers
CREATE TABLE customers (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Addresses
CREATE TABLE addresses (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
type VARCHAR(20) DEFAULT 'shipping', -- shipping, billing
line1 VARCHAR(255) NOT NULL,
line2 VARCHAR(255),
city VARCHAR(100) NOT NULL,
state VARCHAR(100),
postal_code VARCHAR(20) NOT NULL,
country CHAR(2) NOT NULL, -- ISO 3166-1 alpha-2
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_addresses_customer_id ON addresses(customer_id);
-- Categories
CREATE TABLE categories (
id BIGSERIAL PRIMARY KEY,
parent_id BIGINT REFERENCES categories(id) ON DELETE SET NULL,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
image_url TEXT,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_categories_parent_id ON categories(parent_id);
CREATE INDEX idx_categories_slug ON categories(slug);
-- Products
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
price NUMERIC(10, 2) NOT NULL,
compare_at_price NUMERIC(10, 2),
cost NUMERIC(10, 2),
quantity INTEGER DEFAULT 0,
weight_kg NUMERIC(8, 3),
is_active BOOLEAN DEFAULT true,
is_featured BOOLEAN DEFAULT false,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT products_price_positive CHECK (price >= 0),
CONSTRAINT products_quantity_positive CHECK (quantity >= 0)
);
CREATE INDEX idx_products_sku ON products(sku);
CREATE INDEX idx_products_slug ON products(slug);
CREATE INDEX idx_products_is_active ON products(is_active) WHERE is_active = true;
-- Product Categories (many-to-many)
CREATE TABLE product_categories (
product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
category_id BIGINT NOT NULL REFERENCES categories(id) ON DELETE CASCADE,
PRIMARY KEY (product_id, category_id)
);
-- Product Images
CREATE TABLE product_images (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
url TEXT NOT NULL,
alt_text VARCHAR(255),
sort_order INTEGER DEFAULT 0,
is_primary BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_product_images_product_id ON product_images(product_id);
-- Orders
CREATE TYPE order_status AS ENUM (
'pending', 'confirmed', 'processing',
'shipped', 'delivered', 'cancelled', 'refunded'
);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT REFERENCES customers(id) ON DELETE SET NULL,
order_number VARCHAR(20) NOT NULL UNIQUE,
status order_status DEFAULT 'pending',
subtotal NUMERIC(10, 2) NOT NULL,
tax NUMERIC(10, 2) DEFAULT 0,
shipping NUMERIC(10, 2) DEFAULT 0,
discount NUMERIC(10, 2) DEFAULT 0,
total NUMERIC(10, 2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
shipping_address JSONB,
billing_address JSONB,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_order_number ON orders(order_number);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created_at ON orders(created_at);
-- Order Items
CREATE TABLE order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id BIGINT REFERENCES products(id) ON DELETE SET NULL,
sku VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL,
quantity INTEGER NOT NULL,
unit_price NUMERIC(10, 2) NOT NULL,
total NUMERIC(10, 2) NOT NULL,
CONSTRAINT order_items_quantity_positive CHECK (quantity > 0)
);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
SaaS Multi-tenant Schema
-- Organizations (tenants)
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
plan VARCHAR(50) DEFAULT 'free',
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_organizations_slug ON organizations(slug);
-- Users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255),
avatar_url TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Organization memberships
CREATE TYPE member_role AS ENUM ('owner', 'admin', 'member', 'viewer');
CREATE TABLE organization_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role member_role DEFAULT 'member',
invited_by UUID REFERENCES users(id),
joined_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT org_members_unique UNIQUE (organization_id, user_id)
);
CREATE INDEX idx_org_members_org_id ON organization_members(organization_id);
CREATE INDEX idx_org_members_user_id ON organization_members(user_id);
-- Projects (scoped to organization)
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_archived BOOLEAN DEFAULT false,
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_projects_org_id ON projects(organization_id);
-- Audit log
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE SET NULL,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100) NOT NULL,
resource_id UUID,
old_values JSONB,
new_values JSONB,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_audit_logs_org_id ON audit_logs(organization_id);
CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
Common Patterns
Soft Deletes
-- Add to table
deleted_at TIMESTAMPTZ,
-- Query active records
WHERE deleted_at IS NULL
-- Index for soft deletes
CREATE INDEX idx_table_active ON table(id) WHERE deleted_at IS NULL;
Full-Text Search (PostgreSQL)
-- Add search vector column
ALTER TABLE products ADD COLUMN search_vector tsvector;
-- Create GIN index
CREATE INDEX idx_products_search ON products USING gin(search_vector);
-- Update trigger
CREATE FUNCTION products_search_trigger() RETURNS trigger AS $$
BEGIN
NEW.search_vector := to_tsvector('english',
coalesce(NEW.name, '') || ' ' ||
coalesce(NEW.description, '')
);
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER products_search_update
BEFORE INSERT OR UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION products_search_trigger();
Updated At Trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply to tables
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
Validation Checklist
Before outputting, verify:
- Tables have primary keys
- Foreign keys have appropriate ON DELETE actions
- Unique constraints where needed
- Check constraints for data validation
- Indexes on foreign keys and frequently queried columns
- Timestamps use TIMESTAMPTZ (timezone aware)
- Naming conventions are consistent
- No reserved word conflicts
Example Invocations
Prompt: "Create SQL schema for a blog with posts, comments, and tags"
Output: Complete schema.sql with normalized tables, relationships, indexes.
Prompt: "Generate PostgreSQL schema for a booking system" Output: Complete schema with users, resources, bookings, availability tables.
Prompt: "Database schema for inventory management" Output: Complete schema with products, warehouses, stock movements, transactions.