Use Cases (अपडेट: 2/6/2026)

Claude Code से Database Design Review: Practical Guide

Claude Code से database design review करें: tables, constraints, indexes, migrations, rollback और PostgreSQL examples.

Claude Code से Database Design Review: Practical Guide

Database design सिर्फ table names चुनना नहीं है। आपको तय करना होता है कि कौन सा data किस table में रहेगा, कौन सी column खाली नहीं होनी चाहिए, कौन से values duplicate नहीं हो सकते, tables के बीच relation कैसे safe रहेगा, और product बड़ा होने पर list, search, billing और reporting fast रहेंगे या नहीं। कमजोर schema शुरू में ठीक लगता है, लेकिन बाद में duplicate account, wrong subscription state, slow dashboard और rollback न हो पाने वाली migration बनाता है।

Claude Code इस काम में मदद कर सकता है, लेकिन इसे final truth की तरह use करना risky है। बेहतर तरीका है कि Claude Code को design assistant और reviewer बनाया जाए। वह draft schema बना सकता है, missing constraints बता सकता है, test SQL लिख सकता है, और risky migration पर सवाल उठा सकता है। Final decision human owner को official docs, product rules, real queries और operational risk देखकर लेना चाहिए।

Related workflow के लिए database migration automation और Prisma ORM with Claude Code पढ़ें। Constraints और indexes verify करने के लिए PostgreSQL constraints docs और PostgreSQL indexes docs देखें। Audit log या reporting में sensitive data हो सकता है, तो OWASP Top 10 भी review में शामिल करें।

Basic concepts

Table एक ही type के records रखती है, जैसे users, teams, projects। Column record का एक field है, जैसे email, status, created_at। Primary key एक row की identity है। Foreign key यह guarantee करती है कि row किसी existing parent row को point कर रही है। Unique constraint duplicate business value रोकता है, जैसे email, team slug या payment customer ID। Index common reads को fast करता है। Migration schema change की repeatable script है। Rollback गलत release के बाद वापस जाने की planning है।

Beginners अक्सर validation सिर्फ application में रखते हैं। Form validation UX के लिए जरूरी है, लेकिन concurrent requests और batch jobs के सामने कमजोर पड़ सकती है। Database को last safety layer बनना चाहिए। Claude Code से पूछें कि कौन सी safety layer schema में missing है।

claude -p "Read docs/database/schema.sql and migrations/*.sql.
Act as a database design reviewer, not as the owner of truth.

Check for:
- Missing primary keys, foreign keys, unique constraints, and NOT NULL rules
- status or role columns that are too loose
- Indexes needed for WHERE, JOIN, and ORDER BY patterns
- Destructive migrations and rollback gaps
- PII leaking into audit logs or search/reporting tables
- API shapes that may trigger N+1 queries

Return findings first. Include why it matters, how to verify it, and only then suggest a minimal patch."

चार concrete use cases

पहला use case SaaS user, team और project schema है। एक user कई teams में हो सकता है, और हर team के कई projects हो सकते हैं। Project slug पूरे product में unique होना जरूरी नहीं; अक्सर team के अंदर unique होना enough है। इसलिए UNIQUE (team_id, slug) business rule को बेहतर express करता है।

दूसरा use case audit log है। हमें जानना है कि किस user ने कब कौन सा object बदला। लेकिन audit table को JSON dumping place नहीं बनाना चाहिए। Email, phone, address या token को metadata में डालना आसान है, पर retention, access control और deletion request मुश्किल हो जाते हैं।

तीसरा use case billing और subscription है। Provider customer ID duplicate नहीं होना चाहिए। एक team के पास दो active subscriptions भी नहीं होनी चाहिए। फिर भी canceled history रखना useful है, इसलिए partial unique index काम आता है।

चौथा use case search या reporting table है। हर search में कई operational tables JOIN करना slow हो सकता है। Read-optimized table अच्छा option है, लेकिन refresh timing, indexes और verification साफ design करने होंगे।

Runnable PostgreSQL DDL

यह SQL local PostgreSQL में paste किया जा सकता है। यह सिर्फ demo schema recreate करता है।

DROP SCHEMA IF EXISTS claude_db_design_demo CASCADE;
CREATE SCHEMA claude_db_design_demo;
SET search_path = claude_db_design_demo;

CREATE TABLE users (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email TEXT NOT NULL,
  display_name TEXT NOT NULL,
  password_hash TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT users_email_unique UNIQUE (email),
  CONSTRAINT users_email_has_at CHECK (position('@' in email) > 1)
);

CREATE TABLE teams (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name TEXT NOT NULL,
  slug TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT teams_slug_unique UNIQUE (slug)
);

CREATE TABLE team_members (
  team_id BIGINT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
  joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (team_id, user_id)
);

CREATE TABLE projects (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  team_id BIGINT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  slug TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived')),
  created_by BIGINT NOT NULL REFERENCES users(id),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT projects_team_slug_unique UNIQUE (team_id, slug)
);

CREATE TABLE audit_logs (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  team_id BIGINT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  actor_user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
  action TEXT NOT NULL,
  entity_type TEXT NOT NULL,
  entity_id TEXT NOT NULL,
  metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT audit_action_not_blank CHECK (length(action) > 0),
  CONSTRAINT audit_metadata_no_email CHECK (
    metadata::text !~* '[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}'
  )
);

CREATE TABLE subscriptions (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  team_id BIGINT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  provider TEXT NOT NULL CHECK (provider IN ('stripe', 'manual')),
  provider_customer_id TEXT NOT NULL,
  plan_code TEXT NOT NULL CHECK (plan_code IN ('free', 'pro', 'business')),
  status TEXT NOT NULL CHECK (status IN ('trialing', 'active', 'past_due', 'canceled')),
  current_period_end DATE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT subscriptions_provider_customer_unique UNIQUE (provider, provider_customer_id)
);

CREATE TABLE project_search_documents (
  project_id BIGINT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
  team_id BIGINT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  search_text TEXT NOT NULL,
  refreshed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX subscriptions_one_open_per_team
  ON subscriptions(team_id)
  WHERE status IN ('trialing', 'active', 'past_due');

CREATE INDEX idx_team_members_user_id ON team_members(user_id);
CREATE INDEX idx_projects_team_status_created ON projects(team_id, status, created_at DESC);
CREATE INDEX idx_audit_logs_team_created ON audit_logs(team_id, created_at DESC);
CREATE INDEX idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
CREATE INDEX idx_project_search_documents_search
  ON project_search_documents USING GIN (to_tsvector('simple', search_text));

Seed data और constraint tests

Good design को successful data के साथ-साथ bad data reject करके भी verify करना चाहिए। नीचे expected failures को DO blocks में catch किया गया है।

SET search_path = claude_db_design_demo;

INSERT INTO users (email, display_name, password_hash) VALUES
  ('masa@example.com', 'Masa', '$2b$demo-hash-1'),
  ('aiko@example.com', 'Aiko', '$2b$demo-hash-2'),
  ('ren@example.com', 'Ren', '$2b$demo-hash-3');

INSERT INTO teams (name, slug) VALUES
  ('ClaudeCodeLab', 'clab'),
  ('Northstar Apps', 'northstar');

INSERT INTO team_members (team_id, user_id, role) VALUES
  (1, 1, 'owner'),
  (1, 2, 'admin'),
  (2, 3, 'owner');

INSERT INTO projects (team_id, name, slug, status, created_by) VALUES
  (1, 'Schema Review', 'schema-review', 'active', 1),
  (1, 'Billing Console', 'billing-console', 'active', 2),
  (2, 'Customer Portal', 'customer-portal', 'active', 3);

INSERT INTO audit_logs (team_id, actor_user_id, action, entity_type, entity_id, metadata) VALUES
  (1, 1, 'project.created', 'project', '1', '{"project_slug":"schema-review"}'::jsonb),
  (1, 2, 'billing.plan_changed', 'subscription', '1', '{"from":"free","to":"pro"}'::jsonb);

INSERT INTO subscriptions (team_id, provider, provider_customer_id, plan_code, status, current_period_end) VALUES
  (1, 'stripe', 'cus_demo_1', 'pro', 'active', DATE '2026-07-31'),
  (2, 'manual', 'manual_northstar_1', 'free', 'canceled', NULL);

INSERT INTO project_search_documents (project_id, team_id, title, search_text)
SELECT
  id,
  team_id,
  name,
  name || ' project notes for database design, billing, audit, and reporting.'
FROM projects;
SET search_path = claude_db_design_demo;

DO $$
BEGIN
  INSERT INTO users (email, display_name, password_hash)
  VALUES ('masa@example.com', 'Duplicate Masa', '$2b$demo-hash-x');
EXCEPTION WHEN unique_violation THEN
  RAISE NOTICE 'OK duplicate email blocked: %', SQLERRM;
END $$;

DO $$
BEGIN
  INSERT INTO projects (team_id, name, slug, created_by)
  VALUES (1, NULL, 'bad-null', 1);
EXCEPTION WHEN not_null_violation THEN
  RAISE NOTICE 'OK NULL project name blocked: %', SQLERRM;
END $$;

DO $$
BEGIN
  INSERT INTO team_members (team_id, user_id, role)
  VALUES (999, 1, 'member');
EXCEPTION WHEN foreign_key_violation THEN
  RAISE NOTICE 'OK missing team blocked: %', SQLERRM;
END $$;

DO $$
BEGIN
  INSERT INTO projects (team_id, name, slug, created_by)
  VALUES (1, 'Duplicate Slug', 'schema-review', 1);
EXCEPTION WHEN unique_violation THEN
  RAISE NOTICE 'OK duplicate team slug blocked: %', SQLERRM;
END $$;

DO $$
BEGIN
  INSERT INTO audit_logs (team_id, actor_user_id, action, entity_type, entity_id, metadata)
  VALUES (1, 1, 'project.invited', 'project', '1', '{"email":"customer@example.com"}'::jsonb);
EXCEPTION WHEN check_violation THEN
  RAISE NOTICE 'OK email in audit metadata blocked: %', SQLERRM;
END $$;

DO $$
BEGIN
  INSERT INTO subscriptions (team_id, provider, provider_customer_id, plan_code, status, current_period_end)
  VALUES (1, 'stripe', 'cus_demo_2', 'business', 'active', DATE '2026-08-31');
EXCEPTION WHEN unique_violation THEN
  RAISE NOTICE 'OK second open subscription blocked: %', SQLERRM;
END $$;

SELECT p.id, p.name, count(a.id) AS audit_events
FROM projects p
LEFT JOIN audit_logs a
  ON a.entity_type = 'project'
 AND a.entity_id = p.id::text
WHERE p.team_id = 1
GROUP BY p.id, p.name
ORDER BY audit_events DESC, p.id;

SELECT project_id, title
FROM project_search_documents
WHERE to_tsvector('simple', search_text) @@ plainto_tsquery('simple', 'billing');

EXPLAIN (COSTS OFF)
SELECT *
FROM audit_logs
WHERE team_id = 1
ORDER BY created_at DESC
LIMIT 20;

Pitfalls जिन्हें जरूर review करें

सबसे common pitfall missing unique constraints है। Email, OAuth account, payment customer ID और team slug database level पर protected होने चाहिए। दूसरा pitfall गलत nullable fields है। अगर projects.name, team_members.role या subscriptions.status खाली हो सकते हैं, तो UI, permission और billing logic fragile हो जाते हैं।

तीसरा pitfall destructive migration है। DROP COLUMN, type change, existing data पर NOT NULL, और large table पर default value production lock या data loss दे सकते हैं। Claude Code से expand-and-contract plan मांगें और human review करें। चौथा pitfall N+1 query है: 100 projects की list के लिए हर project पर audit count अलग query करने से 101 queries चलती हैं। पांचवा pitfall logs में PII है। Audit, search और reporting tables में email, phone, address या token casually न रखें।

Migration और rollback checklist

# Migration checklist: add projects.archived_at

## Design
- [ ] Why this column is needed is written in the issue or PR.
- [ ] Existing rows and backfill behavior are defined.
- [ ] NOT NULL, default value, and CHECK constraint choices are explained.
- [ ] Index impact was checked against real WHERE / ORDER BY queries.

## Safety
- [ ] This migration does not drop data in the first deploy.
- [ ] Large-table locks were considered.
- [ ] The application can run during the expand-and-contract period.
- [ ] Backfill can be retried safely.

## Up SQL
    ALTER TABLE projects ADD COLUMN archived_at TIMESTAMPTZ;
    UPDATE projects
    SET archived_at = now()
    WHERE status = 'archived' AND archived_at IS NULL;
    ALTER TABLE projects
      ADD CONSTRAINT projects_archived_at_matches_status
      CHECK (
        (status = 'archived' AND archived_at IS NOT NULL)
        OR (status = 'active' AND archived_at IS NULL)
      ) NOT VALID;
    ALTER TABLE projects VALIDATE CONSTRAINT projects_archived_at_matches_status;

## Down SQL
    ALTER TABLE projects DROP CONSTRAINT IF EXISTS projects_archived_at_matches_status;
    ALTER TABLE projects DROP COLUMN IF EXISTS archived_at;

## Verification
- [ ] Constraint test SQL was run locally.
- [ ] Read queries still use expected indexes.
- [ ] Rollback was reviewed, including data-loss notes.
- [ ] Claude Code findings were reviewed by a human owner.

Rollback को बहुत ध्यान से पढ़ें। Generated down migration table shape वापस ला सकती है, लेकिन deleted data वापस नहीं ला सकती। अगर rollback partial है, तो PR में साफ लिखें और backup, staged release या recovery process अलग रखें।

ER diagram से share करें

erDiagram
    users ||--o{ team_members : joins
    teams ||--o{ team_members : has
    teams ||--o{ projects : owns
    users ||--o{ projects : creates
    teams ||--o{ audit_logs : records
    users ||--o{ audit_logs : acts
    teams ||--o{ subscriptions : pays
    projects ||--|| project_search_documents : indexes

Diagram discussion को fast करता है, लेकिन DDL, migration और test SQL की जगह नहीं लेता। सुंदर ER diagram भी broken data रोक नहीं सकता अगर constraints missing हैं।

CTA और Masa का result

Team में Claude Code use करना है तो rules CLAUDE.md में लिखें: production DB direct access नहीं, destructive migration staged हो, constraint tests mandatory हों, और logs में PII बिना approval न जाए। ClaudeCodeLab teams को Claude Code adoption, migration review templates, PR checklists और practical training में help करता है। Existing product पर लागू करना हो तो Claude Code training and consultation देखें। Individual learning के लिए free cheatsheet और product templates भी useful हैं।

Masa ने जब यह workflow real schema review में try किया, तो सबसे useful चीज first ER diagram नहीं थी। Useful था वह SQL जो fail होना चाहिए था: duplicate email, null project name, missing team, duplicate slug, audit metadata में email, और second active subscription। Claude Code ने checklist जल्दी बनाई, लेकिन confidence PostgreSQL के bad data reject करने से आया।

#claude-code #database #prisma #sql
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.