Fixing the Data Model an AI Gave You

Written By
SprintX Team
AI & Product Engineering
August 10, 2026
7 min read

The six schema mistakes AI builders make over and over — and a safe, ordered plan for repairing your data model while the app stays live.
Everything else in a codebase is replaceable. You can rewrite a component, swap a framework, throw away an entire API layer over a weekend. The schema is different: it holds the data your customers put there, and every fix has to happen while that data stays intact and the app stays up.
Which is unfortunate, because the schema is what AI builders get wrong most consistently. A model designing tables optimizes for the screen you just described, not the eleven screens you will describe over the next year. The result is perfect at demo scale and starts leaking around the time you get real users. Here is what generated schemas get wrong, how to tell which problems you have, and the order to fix them without losing a row.
The six flaws that show up again and again
Everything is nullable and everything is text. The model does not know which fields are required, so it makes them all optional. It does not know the shape of a status field, so it stores 'active' as free text. Six months later you have 'active', 'Active', 'ACTIVE', and one row where a browser sent an empty string.
No foreign keys. Columns named user_id and organization_id, with nothing in the database enforcing that they point anywhere real. Delete a user and their rows become orphans your queries silently include. Foreign keys are what make "this row belongs to that row" a fact rather than a hope.
Tenant scoping bolted on later. The generated app assumed one team. Then you added teams, organization_id got appended to some tables but not all, and now three queries on the reporting page return other customers' data. The most dangerous flaw on this list, and the reason multi-tenant architecture is a decision to make up front.
JSON blobs where columns belong. A settings object as JSON is fine. Order line items as JSON, because the model did not want to create a second table, is not — you cannot index it usefully, cannot constrain it, and every report becomes an application-level loop.
No unique constraints. Nothing stops two rows with the same email, two memberships for the same user in one org, two payments for one invoice. Application-level checks feel like protection until two requests land in the same millisecond — which is how you get duplicate records with no validation.
Missing indexes on the columns you filter by. Generated schemas index primary keys and nothing else. Free at 200 rows, ruinous at 200,000 — the mechanism behind most cases of an app that gets slow once real data arrives.
Working out which ones you have
Twenty minutes with a database client tells you more than a day of reading application code.
| Question to ask the database | What a bad answer looks like |
|---|---|
| Which columns are nullable? | Almost all of them, including ones the app treats as required |
| Which foreign keys exist? | None, or only on one or two tables |
| Which unique constraints exist? | Only primary keys |
| Which indexes exist? | Only primary keys, while your list pages filter on four other columns |
| Which tables carry a tenant column? | Some, not all — the inconsistency is the finding |
| Which columns store JSON? | Anything you filter, sort, or report on |
| Which tables lack created_at? | Any table where you would ever ask "when did this happen?" |
Write the answers down as specific defects, each tied to a table and a column. A vague sense that "the schema is bad" produces a rewrite; a list of eleven named defects produces a fix plan.
Fix in this order
Sequence matters more here than anywhere else in a rescue, because each step makes the next one safe.
1. Get a restore you have actually tested. Not "backups are enabled." A restore you performed, to a scratch database, that you looked at. Everything below is reversible only if this step is real.
2. Add a real migration tool. If the schema has been changed by clicking around a dashboard, you have no history and no way to apply the same change to two environments. Prisma Migrate, Drizzle Kit, Atlas, plain SQL files with a runner — the tool matters less than every future change being a file in the repo. Teams also moving off a local database should read Prisma from SQLite to Postgres.
3. Clean the data before you constrain it. You cannot add NOT NULL to a column that has nulls, or a unique index to a column with duplicates. The real work is small data-repair scripts: pick a default, decide which duplicate wins, delete the orphans. In a transaction, on a copy first, counting rows before and after.
4. Add constraints from the outside in. Foreign keys, then unique constraints, then NOT NULL, then checks and enums. Each one is a class of bug that can no longer occur — a permanent reduction in what your application code has to defend against.
5. Fix tenant scoping, and enforce it in the database. Every tenant-owned table gets the tenant column, non-null, with a foreign key — then push enforcement below the application. On Postgres and Supabase that means row-level security on every table: see row-level security and roles and the RLS mistakes AI apps make.
6. Index what you actually query. Turn on slow query logging for a week, take the top ten, index for those. Composite indexes in the order your queries filter. Resist indexing speculatively — every index costs write throughput.
7. Only now, restructure. Pull JSON blobs into tables, split the table that grew three purposes, rename the column that lies about what it holds. Restructuring is safest last, on a schema that already has constraints protecting it.
Go one migration per defect, deployed and verified before the next. The temptation is one giant migration that fixes everything on a Saturday — which is also the one that runs for forty minutes against production, locks a table, and teaches you why people write posts about a migration that broke their data.
When the schema is beyond repair
Occasionally the model is wrong at the concept level — the app stores what it displays rather than what is true, and no sequence of constraints fixes that. The tell: you cannot answer a basic business question ("what did this customer pay us last quarter?") without application code to reconstruct it.
That is a rebuild of the data layer, not a repair. Design the correct schema alongside the old one, write a one-time transform, run both in parallel behind a flag, then cut over. Expensive, but bounded — and cheaper than piling constraints onto a model that describes the wrong thing.
Frequently asked questions
Can I fix a bad schema without downtime? Usually, if you go incrementally. Add columns as nullable, backfill in batches, then tighten the constraint in a separate migration. Add foreign keys and indexes on large Postgres tables in the non-blocking form so they do not lock writes. Changes that genuinely need a maintenance window are rare and predictable once a migration tool tells you what each one does.
Should I let the AI redesign the schema for me? It is a decent first-draft generator and a poor decision-maker. Models produce reasonable table structures when you describe the domain precisely, but they will not know that invoices must be immutable after issue, or that your tenant boundary is the organization and not the user. Give it those constraints explicitly, then review the output against the six flaws above.
How do I know if the schema is why my app is slow? Check whether slowness scales with data volume or with traffic. A page that was instant at 500 rows and takes four seconds at 50,000 means missing indexes or a query fetching everything and filtering in the application. If slowness tracks concurrent users instead, look at connection pooling and blocking work on the request path.
If your app is running on a schema that was generated rather than designed, you are one growth spurt away from finding out where it leaks. SprintX audits and repairs data models on live products — constraints, tenant isolation, indexes, and migrations that run safely against real data. Show us your schema and we will tell you what breaks first.


