# Why AI-generated SQL is slow

> **Quick answer:** AI writes SQL that is locally correct against the code it can
> see, but blind to the database it cannot, the indexes, row counts, data
> distribution, and query plans.  So it ships N+1 loops, filters on unindexed
> columns, `SELECT *`, unbounded results, and functions that disable indexes.  The
> code reads clean; the slowness is a database fact the model never had access to.

## The one reason behind all the others

Performance lives in the database, not the source.  Your coding agent (Claude
Code, Cursor, Copilot) can read your schema and your query, but it does **not**
see:

- which columns are actually **indexed**,
- how many **rows** the table holds,
- the **distribution** of your data (is `status = 'pending'` rare or half the
  table?),
- or the **query plan** the planner will choose.

Every performance bug below is a special case of that blindness.  The AI writes
code that would be correct against a small, evenly distributed, fully indexed
table, and then it meets production.

## The five patterns

### 1.  N+1 query loops

The agent loads a list, then accesses a related object in a loop, and the ORM
fires one query per row.  It reads perfectly.`for post in posts: post.author`, and it is the most common ORM performance bug.  See
[how to find N+1 queries](https://www.dbgorilla.com/learn/postgres/how-to-find-n-plus-1-queries/).

### 2.  Filtering or joining on unindexed columns

`WHERE customer_id = $1` is instant with an index and a full table scan without
one.  The agent has no idea which of your columns are indexed, so it writes the
natural predicate and hopes.  On a big table that is a `Seq Scan`, see
[reading an EXPLAIN plan](https://www.dbgorilla.com/learn/postgres/how-to-read-a-postgres-explain-analyze-plan/).

### 3.  SELECT * instead of the columns you need

`SELECT *` pulls every column, moves more data than the app uses, and defeats
index-only scans.  The agent defaults to it because it does not know which columns
matter.

### 4.  No LIMIT

Fetching an unbounded result set is fine on 100 rows and a memory problem on 10
million.  "Get the users" becomes `SELECT * FROM users` with no ceiling.

### 5.  Functions that disable the index (non-sargable predicates)

This one is subtle.  A predicate like:

```sql
WHERE lower(email) = 'a@b.com'          -- index on email is NOT used
WHERE created_at::date = '2026-01-01'   -- index on created_at is NOT used
```

wraps an indexed column in a function, so the planner cannot use the plain index
and falls back to a sequential scan.  The fix is to index the expression or
rewrite the predicate:

Either index the expression the query actually uses:

```sql
CREATE INDEX ON users (lower(email));
```

…or rewrite the predicate so the raw column is compared against a range, leaving
it sargable:

```sql
SELECT * FROM orders
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-01-02';
```

Leading-wildcard `LIKE '%foo'` has the same problem, a plain B-tree index cannot
serve it.

## Why does this keep happening?

Because none of it is visible in the code.  Whether a column is indexed, how big
the table is, whether the planner picks the index, these are runtime facts about
your specific database.  The model reproduces patterns from its training data
(lots of `SELECT *`, lots of lazy-loading tutorials) and writes code that is
idiomatic and locally right.  The more SQL your team generates with AI, the more
of these ship, not because the AI is bad, but because it is optimizing code it
can see against a database it cannot.

## How do I catch it?

- Review new queries against the actual schema and indexes, not just for
  correctness.
- Rank real load with [`pg_stat_statements`](https://www.dbgorilla.com/learn/postgres/how-to-identify-slow-queries-in-postgresql/)
  and pull plans for the top offenders.
- Give whatever writes your SQL, human or agent, the database context it needs
  to reason about performance.

## How DBGorilla helps

DBGorilla closes exactly this gap: it connects to your database read-only and
gives your AI coding agent (Claude Code, Cursor) the real context, the indexes,
row counts, `pg_stat_statements` load, and query plans, so the agent can catch
an N+1, a missing index, or a non-sargable predicate the way a senior engineer
would in review.  It surfaces and explains through your agent; it does not touch
production or rewrite anything on its own. [Get started free →](https://app.dbgorilla.com/signup)

## FAQ

**Why is AI-generated SQL often slow?**
Performance is a property of the database, not the code.  The agent sees the query
but not the indexes, row counts, distribution, or plan, so its locally-correct
SQL only reveals its cost against real data.

**What are the most common performance bugs in AI-written SQL?**
N+1 loops, filtering/joining on unindexed columns, `SELECT *`, no `LIMIT`, and
wrapping an indexed column in a function that disables the index.

**Why does wrapping a column in a function make a query slow?**
`WHERE lower(email) = $1` is not sargable against a plain index on `email`, so the
planner does a sequential scan.  Index the expression or compare the column
directly.

**Does using an AI coding agent mean my SQL will be slow?**
No, not if the agent has database context.  Give it the indexes, row counts,
plans, and stats and it can catch the same issues a reviewer would.

## Related

- [How to find N+1 queries in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-find-n-plus-1-queries/)
- [How to optimize ORM-generated queries](https://www.dbgorilla.com/learn/postgres/how-to-optimize-orm-generated-queries/)
- [How to identify slow queries in PostgreSQL](https://www.dbgorilla.com/learn/postgres/how-to-identify-slow-queries-in-postgresql/)
- [How to read a PostgreSQL EXPLAIN ANALYZE plan](https://www.dbgorilla.com/learn/postgres/how-to-read-a-postgres-explain-analyze-plan/)
- Product: [DBGorilla docs](https://www.dbgorilla.com/docs/)