agentsclimarketplace

Database design

Skill fabioc-aloha/Alex_Skill_Mall/plugins/data-analytics/database-design

284 curated plugins for AI assistants across 16 categories: security, Azure, documentation, code quality, cloud infrastructure, and more. Works with GitHub Copilot. Drop into .github/skills/local/ and go.

Install
npx -y skills add fabioc-aloha/Alex_Skill_Mall --skill database-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Schema design, normalization, query optimization, and data modeling patterns

SKILL.md

10.4 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

Database Design Skill

Model data to match access patterns. Normalize for integrity, denormalize for performance.


Choosing a Database

Decision Matrix

FactorSQL (Relational)NoSQL (Document)Key-ValueGraph
SchemaFixed, strictFlexibleNoneNodes/edges
TransactionsACID built-inEventually consistent (usually)LimitedVaries
JoinsNative supportExpensive/manualN/ANative (relationships)
ScaleVertical → HorizontalHorizontal nativeHorizontal nativeSpecialized
Best forStructured data, complex queriesRapid iteration, varied shapeCache, sessionsRelationships

Common Choices (2026)

DatabaseTypeBest For
PostgreSQLRelationalGeneral purpose, full-featured
SQLiteRelationalEmbedded, serverless
MongoDBDocumentRapid prototyping, flexible schema
RedisKey-ValueCaching, sessions, pub/sub
Cosmos DBMulti-modelAzure-native, global distribution
DynamoDBKey-Value/DocumentAWS-native, massive scale
Neo4jGraphSocial networks, recommendations

Schema Design Principles

Normalization Levels

FormRuleTrade-off
1NFNo repeating groupsBasic structure
2NFNo partial dependenciesRemove redundancy
3NFNo transitive dependenciesData integrity
BCNFEvery determinant is a keyStrict integrity

When to Denormalize

  • Read-heavy workloads
  • Complex queries hitting many tables
  • When consistency can be eventual
  • Reporting/analytics tables

Example: E-Commerce Schema

-- Normalized (3NF)
CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id),
    created_at TIMESTAMP DEFAULT NOW(),
    status VARCHAR(50) NOT NULL
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL
);

-- Denormalized for reporting
CREATE TABLE order_summary (
    order_id INTEGER PRIMARY KEY,
    customer_email VARCHAR(255),
    customer_name VARCHAR(255),
    total_amount DECIMAL(10,2),
    item_count INTEGER,
    created_at TIMESTAMP
);

Modern ORM Patterns

Prisma (TypeScript)

// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  profile   Profile?
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}
// Usage - type-safe queries
const user = await prisma.user.findUnique({
  where: { email: '[email protected]' },
  include: { posts: true }
});

Drizzle (TypeScript)

// schema.ts
import { pgTable, serial, varchar, integer, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).unique().notNull(),
  name: varchar('name', { length: 255 }),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  authorId: integer('author_id').references(() => users.id),
});

// Usage
const result = await db.select().from(users).where(eq(users.email, '[email protected]'));

Entity Framework (.NET)

public class User
{
    public int Id { get; set; }
    public string Email { get; set; } = null!;
    public string? Name { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

// Usage
var user = await context.Users
    .Include(u => u.Posts)
    .FirstOrDefaultAsync(u => u.Email == "[email protected]");

Query Optimization

The EXPLAIN Plan

-- PostgreSQL
EXPLAIN ANALYZE
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.author_id
GROUP BY u.id
ORDER BY post_count DESC
LIMIT 10;

Reading Execution Plans

OperationMeaningConcern
Seq ScanFull table scanConsider index
Index ScanUsing indexGood
Nested LoopFor each row...OK for small sets
Hash JoinBuild hash tableGood for large sets
SortIn-memory sortCheck work_mem

Index Strategy

-- Single column (most common)
CREATE INDEX idx_users_email ON users(email);

-- Composite (column order matters!)
CREATE INDEX idx_posts_author_date ON posts(author_id, created_at DESC);

-- Partial (when you query a subset)
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';

-- Covering (includes all needed columns)
CREATE INDEX idx_posts_covering ON posts(author_id) INCLUDE (title, created_at);

Query Anti-Patterns

Anti-PatternProblemFix
SELECT *Fetches unnecessary dataList specific columns
N+1 queriesLoop makes N queriesUse JOIN or eager loading
Missing index on WHEREFull table scanAdd appropriate index
OR in WHERECan't use index efficientlyUNION ALL or restructure
Functions on columnsWHERE YEAR(date) = 2026WHERE date >= '2026-01-01'

Data Modeling Patterns

Soft Deletes

-- Instead of DELETE, update a flag
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP;

-- Query active users
SELECT * FROM users WHERE deleted_at IS NULL;

-- Soft delete
UPDATE users SET deleted_at = NOW() WHERE id = 123;

Audit Trails

CREATE TABLE audit_log (
    id SERIAL PRIMARY KEY,
    table_name VARCHAR(100) NOT NULL,
    record_id INTEGER NOT NULL,
    action VARCHAR(20) NOT NULL,  -- INSERT, UPDATE, DELETE
    old_values JSONB,
    new_values JSONB,
    changed_by INTEGER REFERENCES users(id),
    changed_at TIMESTAMP DEFAULT NOW()
);

-- Trigger for automatic auditing (PostgreSQL)
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log (table_name, record_id, action, old_values, new_values)
    VALUES (TG_TABLE_NAME, COALESCE(NEW.id, OLD.id), TG_OP, 
            to_jsonb(OLD), to_jsonb(NEW));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Temporal Tables

-- PostgreSQL example
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    price DECIMAL(10,2),
    valid_from TIMESTAMP DEFAULT NOW(),
    valid_to TIMESTAMP DEFAULT 'infinity'
);

-- Query current data
SELECT * FROM products WHERE valid_to = 'infinity';

-- Query historical data (what was the price on Jan 1?)
SELECT * FROM products 
WHERE valid_from <= '2026-01-01' AND valid_to > '2026-01-01';

NoSQL Patterns

Document Modeling (MongoDB)

// ❌ Relational thinking (too normalized)
{ _id: 1, userId: 'u1', productId: 'p1' }  // separate collection

// ✅ Document thinking (embed when 1:few or frequent access together)
{
  _id: 'order123',
  customer: {
    id: 'c1',
    name: 'the AI assistant',
    email: '[email protected]'
  },
  items: [
    { productId: 'p1', name: 'Widget', price: 9.99, quantity: 2 },
    { productId: 'p2', name: 'Gadget', price: 19.99, quantity: 1 }
  ],
  total: 39.97,
  createdAt: ISODate('2026-02-11')
}

Embedding vs Referencing

Embed WhenReference When
1:1 or 1:few relationship1:many or many:many
Data accessed togetherData accessed independently
Data doesn't change oftenData changes frequently
Size is boundedUnbounded growth

Migration Strategies

Schema Migration Best Practices

  1. Forward-only migrations — Don't modify existing migrations
  2. Small, incremental changes — One concern per migration
  3. Backward compatible — Add before remove
  4. Test migrations — Run against production copy

Zero-Downtime Schema Changes

-- Phase 1: Add new column (nullable)
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);

-- Phase 2: Backfill data (in batches)
UPDATE users SET new_email = email WHERE new_email IS NULL LIMIT 1000;

-- Phase 3: Update application to write to both columns

-- Phase 4: Make new column required
ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;

-- Phase 5: Update application to read from new column

-- Phase 6: Drop old column
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN new_email TO email;

Connection Management

Connection Pooling

// Prisma with connection pool
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  // Pool settings in URL: ?connection_limit=10&pool_timeout=30
}

// Node postgres pool
const pool = new Pool({
  max: 20,              // Max connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

Connection Pool Sizing

Rule of thumb for PostgreSQL:

connections = (core_count * 2) + spindle_count

For cloud: Start with 10-20, monitor and adjust.


Implementation Checklist

New Database

  • Choose appropriate database type
  • Design initial schema (3NF or justified denormalization)
  • Define primary keys and foreign keys
  • Plan indexes for known query patterns
  • Set up connection pooling
  • Configure backups and point-in-time recovery
  • Set up monitoring (query performance, connections)

Production Readiness

  • Indexes for all WHERE/JOIN columns
  • No N+1 query patterns
  • Connection pool sized appropriately
  • Query timeouts configured
  • Slow query logging enabled
  • Backup restoration tested

Related Skills

  • performance-profiling — Query-level performance analysis
  • api-design — Data shapes for API responses
  • infrastructure-as-code — Database provisioning
  • security-review — Access control, encryption

Design for today's queries, but plan for tomorrow's scale.

Gives 1 of the 12 instructions most databases sql skills give in ~2.5k tokens

Counted across 589 of the 662 authors here whose files we hold, read 2026-08-07

  • Use parameterized queriesin 37 of 589, across 34 files
  • Use timestamptz for timestampsin 30 of 589, across 14 files
  • Index foreign keysin 29 of 589, across 18 files
  • Create indexes concurrentlyin 29 of 589, across 24 files
  • Use numeric type for moneyin 25 of 589, across 8 files
  • Use cursor pagination instead of offsetin 24 of 589, across 17 files
  • Select only required columnsin 24 of 589, across 20 files
  • Add indexes manually on foreign key columnsin 22 of 589, across 12 files
  • Normalize to third normal formin 19 of 589, across 10 files
  • Configure connection poolinghere, and in 19 of 589, across 17 files
  • Put equality columns before range columns in indexesin 18 of 589, across 10 files
  • Read individual rule files for detailed explanationsin 18 of 589, across 4 files

Said here and by no other author read

  • normalize data for integrity
  • set query timeouts

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.