Design robust, scalable database schemas for SQL and NoSQL databases. Provides normalization guidelines, indexing strategies, migration patterns, constraint design, and performance optimization. Ensures data integrity, query performance, and maintainable data models. Use when this capability is needed.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Design robust, scalable database schemas for SQL and NoSQL databases. Provides normalization guidelines, indexing strategies, migration patterns, constraint design, and performance optimization. Ensures data integrity, query performance, and maintainable data models. Use when this capability is needed.
metadata
{"author":"gocanto"}
Database Schema Designer
Design production-ready database schemas with best practices built-in.
Quick Start
Just describe your data model:
design a schema for an e-commerce platform with users, products, orders
You'll get complete GORM models and PostgreSQL schema:
// User modeltype User struct {
ID uint`gorm:"primaryKey"`
Email string`gorm:"uniqueIndex;not null;size:255"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`// Relationships
Orders []Order `gorm:"foreignKey:UserID"`
}
// Order modeltype Order struct {
ID uint`gorm:"primaryKey"`
UserID uint`gorm:"index;not null"`
User User `gorm:"constraint:OnDelete:CASCADE"`
Total decimal.Decimal `gorm:"type:decimal(10,2);not null"`
CreatedAt time.Time
UpdatedAt time.Time
}
// Migration
db.AutoMigrate(&User{}, &Order{})
What to include in your request:
Entities (users, products, orders)
Key relationships (users have orders, orders have items)
Scale hints (high-traffic, millions of records)
GORM-specific features needed (soft deletes, hooks, custom types)
Triggers
Trigger
Example
design schema
"design a schema for user authentication"
database design
"database design for multi-tenant SaaS"
create tables
"create tables for a blog system"
schema for
"schema for inventory management"
model data
"model data for real-time analytics"
I need a database
"I need a database for tracking orders"
design NoSQL
"design NoSQL schema for product catalog"
Key Terms
Term
Definition
GORM
Go ORM library that simplifies database operations and migrations
Struct Tags
Go tags that define GORM behavior (gorm:"primaryKey", etc.)
AutoMigrate
GORM feature that automatically creates/updates tables from structs
Soft Delete
Marks records as deleted without removing (uses DeletedAt field)
Preloading
Loading related data with Preload() to avoid N+1 queries
Hooks
GORM callbacks (BeforeCreate, AfterUpdate, etc.) for custom logic
Foreign Key (FK)
Relationship constraint defined with gorm:"foreignKey:FieldName"
Index
Defined with tags like gorm:"index" or gorm:"uniqueIndex"
Access Pattern
How your app reads/writes data (queries, joins, filters)
Quick Reference
Task
GORM Approach
PostgreSQL Consideration
New model
Define struct with tags
Use gorm.Model for basics
Primary keys
gorm:"primaryKey"
Auto-incrementing BIGSERIAL
Foreign keys
gorm:"foreignKey"
Add constraint:OnDelete
Indexes
gorm:"index"
Composite: gorm:"index:idx_name"
Unique
gorm:"uniqueIndex"
Can be composite too
Migrations
db.AutoMigrate()
Use golang-migrate for production
Soft delete
Include gorm.DeletedAt
Automatic WHERE clauses
Process Overview
Your Data Requirements
|
v
+-----------------------------------------------------+
| Phase 1: ANALYSIS |
| * Identify entities and relationships |
| * Determine access patterns (read vs write heavy) |
| * Choose SQL or NoSQL based on requirements |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 2: DESIGN |
| * Normalize to 3NF (SQL) or embed/reference (NoSQL) |
| * Define primary keys and foreign keys |
| * Choose appropriate data types |
| * Add constraints (UNIQUE, CHECK, NOT NULL) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 3: OPTIMIZE |
| * Plan indexing strategy |
| * Consider denormalization for read-heavy queries |
| * Add timestamps (created_at, updated_at) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 4: MIGRATE |
| * Generate migration scripts (up + down) |
| * Ensure backward compatibility |
| * Plan zero-downtime deployment |
+-----------------------------------------------------+
|
v
Production-Ready Schema
Commands
Command
When to Use
Action
design schema for {domain}
Starting fresh
Full schema generation
normalize {table}
Fixing existing table
Apply normalization rules
add indexes for {table}
Performance issues
Generate index strategy
migration for {change}
Schema evolution
Create reversible migration
review schema
Code review
Audit existing schema
Workflow: Start with design schema → iterate with normalize → optimize with add indexes → evolve with migration
Core Principles
Principle
WHY
Implementation
Model the Domain
UI changes, domain doesn't
Entity names reflect business concepts
Data Integrity First
Corruption is costly to fix
Constraints at database level
Optimize for Access Pattern
Can't optimize for both
OLTP: normalized, OLAP: denormalized
Plan for Scale
Retrofitting is painful
Index strategy + partitioning plan
Anti-Patterns
Avoid
Why
GORM/PostgreSQL Solution
Using interface{} for types
Loses type safety
Define proper Go types
Missing gorm.Model
No timestamps/soft delete
Embed gorm.Model or add fields
float64 for money
Rounding errors
Use decimal.Decimal type
No struct tags
GORM guesses wrong
Explicit tags for all constraints
Missing FK indexes
Slow JOINs
Add gorm:"index" to FK fields
Raw SQL everywhere
Loses GORM benefits
Use GORM query builder
SELECT * with GORM
Fetches unnecessary data
Use Select() for specific columns
No connection pooling
Connection exhaustion
Configure SetMaxOpenConns()
Ignoring GORM errors
Silent failures
Always check if err != nil
Verification Checklist
After designing a schema:
Every table has a primary key
All relationships have foreign key constraints
ON DELETE strategy defined for each FK
Indexes exist on all foreign keys
Indexes exist on frequently queried columns
Appropriate data types (DECIMAL for money, etc.)
NOT NULL on required fields
UNIQUE constraints where needed
CHECK constraints for validation
created_at and updated_at timestamps
Migration scripts are reversible
Tested on staging with production data
Deep Dive: GORM Models & Normalization
Normal Forms in GORM
Form
Rule
GORM Implementation
1NF
Atomic values, no arrays in columns
Use separate models with relationships
2NF
No partial dependencies
Proper struct composition
3NF
No transitive dependencies
Reference tables via foreign keys
First Normal Form (1NF)
// BAD: Multiple values in single fieldtype Order struct {
ID uint
ProductIDs string// "101,102,103" - violates 1NF
}
// GOOD: Separate model for itemstype Order struct {
gorm.Model
CustomerID uint
Items []OrderItem `gorm:"foreignKey:OrderID"`
}
type OrderItem struct {
gorm.Model
OrderID uint
ProductID uint
Quantity int
Price decimal.Decimal `gorm:"type:decimal(10,2)"`
}
Relationships in GORM
// One-to-Manytype User struct {
gorm.Model
Email string`gorm:"uniqueIndex;not null"`
Orders []Order `gorm:"foreignKey:UserID"`
}
// Many-to-Manytype Product struct {
gorm.Model
Name string`gorm:"not null"`
Categories []Category `gorm:"many2many:product_categories;"`
}
// Self-referencing
Employee {
gorm.Model
Name
ManagerID *
Manager *Employee
Team []Employee
}
Deep Dive: PostgreSQL Data Types in GORM
String Types
type Product struct {
// PostgreSQL VARCHAR
Name string`gorm:"type:varchar(100);not null"`// PostgreSQL TEXT
Description string`gorm:"type:text"`// Fixed length CHAR
CountryCode string`gorm:"type:char(2)"`// With size constraint
Email string`gorm:"size:255;uniqueIndex"`
}
Numeric Types
import"github.com/shopspring/decimal"type FinancialRecord struct {
// Integer types
ID uint`gorm:"primaryKey"`// BIGSERIAL
Quantity int32`gorm:"type:integer"`// INTEGER
BigNumber int64`gorm:"type:bigint"`// BIGINT
SmallCount int16`gorm:"type:smallint"`// SMALLINT// Money - ALWAYS use decimal
Price decimal.Decimal `gorm:"type:decimal(10,2);not null"`// Float - only for scientific data
Temperature float64`gorm:"type:real"`// REAL
Precision float64`gorm:"type:double precision"`// DOUBLE PRECISION
}
type User struct {
gorm.Model
// Single column index
Email string`gorm:"index;not null"`// Unique index
Username string`gorm:"uniqueIndex"`// Composite index
FirstName string`gorm:"index:idx_name"`
LastName string`gorm:"index:idx_name"`// Index with options
Phone string`gorm:"index:,unique,sort:desc"`// Foreign key (auto-indexed by GORM)
TeamID uint`gorm:"index"`
}
// Custom index creation
db.Exec("CREATE INDEX CONCURRENTLY idx_users_email_lower ON users(LOWER(email))")
PostgreSQL Index Types
// B-Tree (default) - ranges, equalitytype Product struct {
Price decimal.Decimal `gorm:"index;type:decimal(10,2)"`
}
// GIN index for JSONBtype Document struct {
Data json.RawMessage `gorm:"type:jsonb;index:,type:gin"`
}
// GiST for full-text search
db.Exec("CREATE INDEX idx_search ON products USING GiST(to_tsvector('english', name || ' ' || description))")
// Partial index
db.Exec("CREATE INDEX idx_active_users ON users(email) WHERE deleted_at IS NULL")
Query Optimization with GORM
// Use index hintsvar users []User
// Efficient: uses index
db.Where(, ).Find(&users)
db.Where(, , ).Find(&users)
db.Where(, ).Find(&users)
db.Exec()
Deep Dive: Constraints in GORM
Primary Keys
// Auto-increment (default)type User struct {
ID uint`gorm:"primaryKey"`// BIGSERIAL in PostgreSQL
}
// UUID primary keyimport"github.com/google/uuid"type Document struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey"`
}
// Composite primary keytype Enrollment struct {
StudentID uint`gorm:"primaryKey"`
CourseID uint`gorm:"primaryKey"`
Grade string
}
Foreign Key Constraints
// Basic foreign keytype Order struct {
gorm.Model
UserID uint`gorm:"not null"`
User User `gorm:"foreignKey:UserID"`
}
// With ON DELETE behaviortype OrderItem struct {
gorm.Model
OrderID uint`gorm:"not null"`
Order Order `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"`
}
// Optional relationship (nullable FK)type Post struct {
gorm.Model
AuthorID *uint// Nullable
Author *User `gorm:"constraint:OnDelete:SET NULL"`
}
Unique Constraints
type Account struct {
gorm.Model
Email
Domain
Username
}
-- migrations/000001_create_users_table.up.sqlCREATE TABLE IF NOTEXISTS users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUENOT NULL,
created_at TIMESTAMPTZ NOT NULLDEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULLDEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
INDEX idx_users_email users(email);
INDEX idx_users_deleted_at users(deleted_at);
IF users;
Deep Dive: Performance Optimization with GORM & PostgreSQL
Query Analysis in PostgreSQL
// Enable query logging in GORM
db = db.Debug()
// Analyze query performancevar explain []map[string]interface{}
db.Raw("EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email = ?", email).Scan(&explain)
// Check slow queries
db.Raw(`
SELECT query, calls, mean_exec_time, max_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC
LIMIT 10
`).Scan(&slowQueries)
Solving N+1 Query Problem
// BAD: N+1 queriesvar users []User
db.Find(&users)
for _, user := range users {
var orders []Order
db.Where("user_id = ?", user.ID).Find(&orders)
}
// GOOD: Eager loading with Preloadvar users []User
db.Preload("Orders").Find(&users)
// BETTER: Selective preloading
db.Preload("Orders", "status = ?", "pending").
Preload("Orders.Items").
Find(&users)
// BEST: Joins for read-only datavar results []struct {
UserName string
OrderID uint
Total decimal.Decimal
}
db.Table("users").
Select("users.name as user_name, orders.id as order_id, orders.total").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Scan(&results)
Connection Pool Optimization
sqlDB, err := db.DB()
// SetMaxIdleConns sets the maximum number of connections in the idle pool
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns()
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime( * time.Minute)
Extension Points
PostgreSQL Advanced Features: JSONB operations, full-text search, PostGIS for geospatial
Advanced Patterns: Time-series with TimescaleDB, event sourcing, CQRS, multi-tenancy with RLS
// Denormalized for performancetype Order struct {
gorm.Model
UserID uint// Denormalized fields (cached)
TotalAmount decimal.Decimal `gorm:"type:decimal(10,2)"`
ItemCount int
UserEmail string`gorm:"type:varchar(255)"`// Cached from User
}
// Use hooks to maintain denormalized datafunc(o *Order) BeforeSave(tx *gorm.DB) error {
// Calculate total from itemsvar total decimal.Decimal
tx.Model(&OrderItem{}).Where("order_id = ?", o.ID).
Select("SUM(price * quantity)").Scan(&total)
o.TotalAmount = total
returnnil
}
import
"time"
"database/sql/driver"
type
struct
// Includes CreatedAt, UpdatedAt, DeletedAt
// PostgreSQL DATE
`gorm:"type:date"`
// PostgreSQL TIME
`gorm:"type:time"`
// PostgreSQL TIMESTAMP WITH TIME ZONE
`gorm:"type:timestamptz"`
// Custom nullable time
`gorm:"type:timestamptz"`
PostgreSQL-Specific Types
import (
"github.com/lib/pq""github.com/google/uuid"
)
type AdvancedModel struct {
// UUID
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey"`// Arrays
Tags pq.StringArray `gorm:"type:text[]"`
Numbers pq.Int64Array `gorm:"type:integer[]"`// JSON/JSONB
Metadata json.RawMessage `gorm:"type:jsonb"`// Boolean
IsActive bool`gorm:"type:boolean;default:true"`// ENUM (requires type creation)
Status string`gorm:"type:order_status;default:'pending'"`
}
// Manual transaction
tx := db.Begin()
deferfunc() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&payment).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
// Using Transaction function
err := db.Transaction(func(tx *gorm.DB)error {
if err := tx.Create(&order).Error; err != nil {
return err
}
if err := tx.Create(&payment).Error; err != nil {
return err
}
returnnil
})
Advanced Query Techniques
// Subqueriesvar users []User
db.Where("id IN (?)",
db.Table("orders").Select("user_id").Where("total > ?", 100),
).Find(&users)
// Raw SQL with scantype Result struct {
Date time.Time
Total float64
}
var results []Result
db.Raw(`
SELECT DATE(created_at) as date, SUM(total) as total
FROM orders
WHERE created_at > ?
GROUP BY DATE(created_at)
`, lastMonth).Scan(&results)
// Locking
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&user, 1)
CREATE
ON
CREATE
ON
-- migrations/000001_create_users_table.down.sql
DROP
TABLE
EXISTS
Zero-Downtime Migration Strategy
// Step 1: Add nullable columntype User struct {
gorm.Model
Email string
Phone *string`gorm:"type:varchar(20)"`// Nullable initially
}
// Step 2: Backfill datafuncBackfillPhones(db *gorm.DB)error {
return db.Model(&User{}).
Where("phone IS NULL").
Update("phone", "").Error
}
// Step 3: Make non-nullablefuncMakePhoneRequired(db *gorm.DB)error {
return db.Exec(`
ALTER TABLE users
ALTER COLUMN phone SET NOT NULL
`).Error
}
// Partial indexes for soft deletes
db.Exec(`
CREATE INDEX idx_active_users
ON users(email)
WHERE deleted_at IS NULL
`)
// Use BRIN indexes for time-series data
db.Exec(`
CREATE INDEX idx_orders_created_brin
ON orders USING BRIN(created_at)
`)
// Table partitioning for large tables
db.Exec(`
CREATE TABLE orders_2024 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')
`)
// Materialized views for complex aggregations
db.Exec(`
CREATE MATERIALIZED VIEW daily_sales AS
SELECT DATE(created_at) as date,
COUNT(*) as order_count,
SUM(total) as total_sales
FROM orders
GROUP BY DATE(created_at)
`)
Caching Strategy
import"github.com/go-redis/redis/v8"type CachedDB struct {
db *gorm.DB
cache *redis.Client
}
func(c *CachedDB) GetUser(id uint) (*User, error) {
key := fmt.Sprintf("user:%d", id)
// Check cache
val, err := c.cache.Get(ctx, key).Result()
if err == nil {
var user User
json.Unmarshal([]byte(val), &user)
return &user, nil
}
// Fetch from DBvar user User
if err := c.db.First(&user, id).Error; err != nil {
returnnil, err
}
// Cache for 1 hour
data, _ := json.Marshal(user)
c.cache.Set(ctx, key, data, time.Hour)
return &user, nil
}