Database Development Patterns
A reporting query that scanned a few thousand rows in staging now sequentially scans 40 million in production and times out the dashboard. A “quick” column rename ships a migration that takes an ACCESS EXCLUSIVE lock and freezes every write for ninety seconds. And an ORM relation you added last week quietly fires one query per row, so the orders page makes 500 round-trips to render. These are the database failures that don’t show up until real data and real concurrency arrive — and they’re where a coding agent is genuinely useful, because the fix is a recognizable pattern, not invention.
This recipe shows the prompts that get the AI to read an actual EXPLAIN ANALYZE plan, propose a safe expand/contract migration, and collapse an N+1 into one batched query. The anchor stack is PostgreSQL with Drizzle ORM, but the prompts work just as well with Prisma or raw SQL.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- A prompt that pastes a real
EXPLAIN (ANALYZE, BUFFERS)plan and gets back targeted index recommendations, not generic advice. - A zero-downtime column-rename prompt using the expand/contract pattern, so writes never block.
- A prompt that turns an N+1 ORM access pattern into a single batched query.
- The three-tool workflow for schema work (Cursor, Claude Code, Codex) and how the Postgres MCP server changes it.
- A “When This Breaks” checklist for the bad advice AI models give on database work.
Designing a schema across the three tools
Section titled “Designing a schema across the three tools”Schema generation is the one task where all three tools do roughly the same thing — read your existing tables, match the conventions, propose new ones. The difference is the surface you drive it from.
Open your existing schema file (src/db/schema.ts) so Agent mode has it in context, then Cmd/Ctrl + I:
Following the Drizzle conventions in
@src/db/schema.ts, addordersandorder_itemstables. Includecreated_at/updated_atwith defaults, adeleted_atfor soft delete, a foreign key fromorder_itemstoorderswithON DELETE CASCADE, and an index onorders(user_id, created_at DESC)for our most common lookup.
Review the diff before accepting, and use a Checkpoint so you can revert the whole schema change if the index choice is wrong.
Let Claude Code read the schema and the migration history itself:
claude "Read src/db/schema.ts and the latest files in drizzle/, then add orders and \order_items tables matching our conventions: timestamps, soft delete, FK with cascade, \and an index on orders(user_id, created_at DESC). Generate the Drizzle migration too."Add --permission-mode plan to have it propose the schema and migration before writing anything.
Run the change non-interactively so it lands as one reviewable commit:
codex exec --ask-for-approval on-request \"Add orders and order_items tables to src/db/schema.ts following our Drizzle conventions \(timestamps, soft delete, FK cascade, index on orders(user_id, created_at DESC)) and \generate the migration."Codex Cloud is the better fit when the migration is risky: it runs the task in an isolated worktree and opens a PR, so you can review the generated SQL before it touches a shared branch.
Optimizing a slow query from its real plan
Section titled “Optimizing a slow query from its real plan”The single biggest upgrade to AI query tuning is refusing to let it guess. A model handed only the SQL will pattern-match (“add an index on the WHERE columns”) and is often wrong. A model handed the actual execution plan can see the sequential scan, the row-count misestimate, and the spilled sort — and recommend the index that matches.
The discipline that makes this reliable: ask for CREATE INDEX CONCURRENTLY explicitly (a plain CREATE INDEX locks writes on a large table), and demand justification against the plan. If the AI recommends four indexes “to be safe,” push back — every index is a write-time tax, and the model has no idea about your write volume unless you tell it.
Zero-downtime migrations: the expand/contract pattern
Section titled “Zero-downtime migrations: the expand/contract pattern”Renaming a column with ALTER TABLE ... RENAME COLUMN looks harmless and is fine on small tables, but on a hot table the lock plus any dependent view rebuild can stall production. The safe pattern is expand/contract: add the new column, backfill, dual-write from the app, switch reads, then drop the old column in a later deploy. The AI knows this pattern — your job is to make it produce all the steps, not just the rename.
Watch for the model collapsing this into a single RENAME “because it’s simpler.” It is simpler, and it’s the thing that pages you at 2am. The batched backfill is the part most often missing — without it, one big UPDATE rewrites every row under a lock and you’ve reintroduced the downtime you were avoiding.
Killing an N+1 with a single batched query
Section titled “Killing an N+1 with a single batched query”ORMs make N+1 effortless to write: iterate a list, touch a relation, and you’ve fired one query per element. The fix is to load the children in one query keyed by parent ID. Give the AI the offending code and the model, and it will reshape the access pattern.
The verification step — “show the two SQL statements” — matters because some AI rewrites swap an N+1 for a single query with a Cartesian join, which loads the same row data N times over the wire. Two clean statements grouped in memory is the shape you want.
When This Breaks
Section titled “When This Breaks”What’s Next
Section titled “What’s Next”- API patterns — idempotent endpoints, cursor pagination, and OpenAPI generation on top of this schema.
- Serverless patterns for running these databases in cloud and edge environments.