Claude-skill-registry database-workflows
Database workflows - schema design, migrations, query optimization. Use when designing schemas, reviewing migrations, optimizing queries, preventing N+1 problems, or working with ORMs like Prisma, Drizzle, and TypeORM.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/database-workflows" ~/.claude/skills/majiayu000-claude-skill-registry-database-workflows && rm -rf "$T"
manifest:
skills/data/database-workflows/SKILL.mdsource content
Database Workflows
Quick reference for database work with Claude Code - schema design, migrations, query optimization, and ORM patterns.
Quick Reference
| Task | Key Action |
|---|---|
| Schema design | Normalize to 3NF, add indexes for queries |
| Migration review | Check reversibility, data preservation |
| Query optimization | Explain analyze, check indexes |
| N+1 prevention | Eager load relations, use joins |
| Index selection | Composite for multi-column WHERE |
When to Use This Skill
- Designing new database schemas
- Reviewing migration files before running
- Optimizing slow queries
- Debugging N+1 query problems
- Adding or reviewing indexes
- Working with Prisma, Drizzle, or TypeORM
Schema Design Checklist
Before creating or modifying schemas:
- Tables have singular names (
notuser
)users - Primary keys are
(auto-increment or UUID)id - Foreign keys follow
pattern{table}_id - Timestamps include
,created_atupdated_at - Nullable columns are intentional
- Indexes cover common query patterns
- No redundant data (normalized to 3NF minimum)
See SCHEMA-DESIGN.md for detailed patterns.
Migration Workflow
Before Creating Migrations
# Prisma bunx prisma migrate dev --create-only --name descriptive_name # Drizzle bunx drizzle-kit generate:pg --name descriptive_name # TypeORM bunx typeorm migration:generate -n DescriptiveName
Migration Review Checklist
- Migration is reversible (has down/rollback)
- No data loss on rollback
- Large tables use batched operations
- Indexes created CONCURRENTLY (if supported)
- Foreign key constraints don't lock tables
- Default values for new NOT NULL columns
See MIGRATIONS.md for strategies.
Query Optimization Quick Guide
Identify Slow Queries
-- PostgreSQL: Find slow queries SELECT query, calls, mean_time, total_time FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10; -- MySQL: Enable slow query log SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;
Analyze Queries
-- PostgreSQL EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT ...; -- MySQL EXPLAIN ANALYZE SELECT ...;
Common Optimizations
| Problem | Solution |
|---|---|
| Full table scan | Add index on WHERE columns |
| Filesort | Add index matching ORDER BY |
| Using temporary | Optimize GROUP BY, add composite index |
| Seq Scan on large table | Add covering index |
See QUERIES.md for detailed optimization.
N+1 Query Prevention
Problem Pattern
// BAD: N+1 queries const users = await db.user.findMany(); for (const user of users) { const posts = await db.post.findMany({ where: { userId: user.id } }); }
Solution Pattern
// GOOD: Single query with relation const users = await db.user.findMany({ include: { posts: true } });
Detection
// Prisma: Enable query logging const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'], }); // Drizzle: Use query builder with joins const result = await db .select() .from(users) .leftJoin(posts, eq(users.id, posts.userId));
Index Quick Reference
When to Add Indexes
| Query Pattern | Index Type |
|---|---|
| B-tree on col |
| Composite (col1, col2) |
| B-tree on col |
| GIN full-text |
| B-tree on col |
| B-tree on col |
When NOT to Add Indexes
- Small tables (< 1000 rows)
- Columns with low cardinality
- Write-heavy tables with rare reads
- Columns rarely used in WHERE/ORDER BY
Index Commands
-- PostgreSQL: Create without locking CREATE INDEX CONCURRENTLY idx_name ON table(column); -- Check index usage SELECT schemaname, tablename, indexname, idx_scan FROM pg_stat_user_indexes ORDER BY idx_scan ASC; -- Find missing indexes SELECT relname, seq_scan, idx_scan, seq_scan - idx_scan AS difference FROM pg_stat_user_tables WHERE seq_scan > idx_scan ORDER BY difference DESC;
ORM Patterns
Prisma
// Schema definition model User { id Int @id @default(autoincrement()) email String @unique posts Post[] createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("users") @@index([email]) } // Efficient query with select const users = await prisma.user.findMany({ select: { id: true, email: true }, where: { email: { contains: '@company.com' } }, take: 10, });
Drizzle
// Schema definition export const users = pgTable('users', { id: serial('id').primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow(), }, (table) => ({ emailIdx: index('email_idx').on(table.email), })); // Efficient query with joins const result = await db .select({ id: users.id, email: users.email }) .from(users) .where(like(users.email, '%@company.com')) .limit(10);
TypeORM
// Entity definition @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column({ unique: true }) @Index() email: string; @CreateDateColumn({ name: 'created_at' }) createdAt: Date; @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date; @OneToMany(() => Post, post => post.user) posts: Post[]; } // Efficient query with QueryBuilder const users = await userRepository .createQueryBuilder('user') .select(['user.id', 'user.email']) .where('user.email LIKE :email', { email: '%@company.com' }) .take(10) .getMany();
Database-Specific Patterns
PostgreSQL
-- UPSERT INSERT INTO users (email, name) VALUES ('test@example.com', 'Test') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, updated_at = NOW(); -- Array columns ALTER TABLE users ADD COLUMN tags TEXT[]; CREATE INDEX idx_users_tags ON users USING GIN(tags); SELECT * FROM users WHERE 'admin' = ANY(tags); -- JSON columns ALTER TABLE users ADD COLUMN metadata JSONB DEFAULT '{}'; CREATE INDEX idx_users_metadata ON users USING GIN(metadata); SELECT * FROM users WHERE metadata->>'role' = 'admin';
MySQL
-- UPSERT INSERT INTO users (email, name) VALUES ('test@example.com', 'Test') ON DUPLICATE KEY UPDATE name = VALUES(name), updated_at = NOW(); -- Full-text search ALTER TABLE posts ADD FULLTEXT INDEX ft_content (title, content); SELECT * FROM posts WHERE MATCH(title, content) AGAINST('search term');
SQLite
-- UPSERT INSERT INTO users (email, name) VALUES ('test@example.com', 'Test') ON CONFLICT(email) DO UPDATE SET name = excluded.name, updated_at = datetime('now'); -- Enable foreign keys (per connection) PRAGMA foreign_keys = ON; -- WAL mode for better concurrency PRAGMA journal_mode = WAL;
Workflow: Schema Review
Prerequisites
- Schema file or migration to review
- Understanding of query patterns
Steps
-
Check Normalization
- No repeated groups
- All columns depend on primary key
- No transitive dependencies
-
Validate Relationships
- Foreign keys defined correctly
- Cascade rules appropriate
- Junction tables for many-to-many
-
Review Indexes
- Indexes on foreign keys
- Indexes on commonly queried columns
- Composite indexes in correct order
-
Check Constraints
- NOT NULL where required
- UNIQUE where appropriate
- CHECK constraints for valid ranges
Validation
- No N+1 patterns in expected queries
- Indexes support all common queries
- Schema can evolve without data loss
Workflow: Query Optimization
Prerequisites
- Slow query identified
- Access to EXPLAIN ANALYZE
Steps
-
Analyze Query Plan
- Run EXPLAIN ANALYZE
- Identify sequential scans
- Check join strategies
-
Identify Issues
- Missing indexes
- Incorrect join order
- Unnecessary columns in SELECT
-
Apply Fixes
- Add appropriate indexes
- Rewrite query if needed
- Use query hints if necessary
-
Verify Improvement
- Re-run EXPLAIN ANALYZE
- Compare execution times
- Test under load
Validation
- Query uses indexes effectively
- Execution time acceptable
- No regression in related queries
Common Mistakes
| Mistake | Fix |
|---|---|
| No index on foreign key | Add index on FK columns |
| SELECT * in production | Select only needed columns |
| N+1 in loops | Use eager loading or joins |
| Missing timestamps | Add created_at, updated_at |
| Nullable by default | Explicitly define NOT NULL |
| No migration rollback | Always write down migration |
Reference Files
| File | Contents |
|---|---|
| SCHEMA-DESIGN.md | Schema patterns, normalization, relationships |
| MIGRATIONS.md | Migration strategies, rollback, versioning |
| QUERIES.md | Query optimization, N+1 prevention, performance |