SQL Optimizer
Systematic SQL performance analysis: parse query structure, interpret EXPLAIN plans,
detect anti-patterns (N+1, full scans, cartesian joins), recommend indexes, and rewrite
queries — with explanations of WHY each change improves performance, not just WHAT changed.
Reference Files
| File |
Contents |
Load When |
references/anti-patterns.md |
Common SQL anti-patterns with detection rules and fixes |
Always |
references/index-strategies.md |
Index type selection, composite index ordering, covering indexes |
Index recommendations needed |
references/explain-guide.md |
Reading EXPLAIN output for PostgreSQL, MySQL, SQLite |
EXPLAIN plan provided |
references/join-optimization.md |
Join type selection, join order optimization, subquery-to-join conversion |
Query contains joins or subqueries |
Prerequisites
- The SQL query to optimize
- Database engine (PostgreSQL, MySQL, SQLite) — optimization differs by engine
- Table schemas and approximate row counts (helpful but not required)
- EXPLAIN output (highly valuable when available)
Workflow
Phase 1: Query Analysis
Parse the SQL to understand its structure:
- Identify operations — SELECT columns, FROM tables, JOIN conditions, WHERE filters,
GROUP BY, ORDER BY, HAVING, subqueries.
- Map table relationships — Which tables are joined? On what keys? Are there
implicit cartesian products?
- Detect immediate red flags:
SELECT * — fetching unnecessary columns
- Functions on indexed columns in WHERE — prevents index use
OR in WHERE — often prevents index use
- Correlated subqueries — potential N+1
- Missing WHERE on DELETE/UPDATE — dangerous
Phase 2: EXPLAIN Interpretation
If an EXPLAIN plan is provided:
- Scan types — Sequential Scan (bad for large tables), Index Scan (good),
Index Only Scan (best), Bitmap Index Scan (acceptable).
- Join methods — Nested Loop (good for small tables), Hash Join (good for
equi-joins), Merge Join (good for sorted data).
- Row estimates — Compare estimated rows with actual rows. Large discrepancies
indicate stale statistics (
ANALYZE).
- Cost hotspots — Highest-cost node is the bottleneck. Optimize there first.
- Sort operations — External sorts (disk) are expensive. Consider indexes
that match ORDER BY.
Phase 3: Anti-Pattern Detection
Check for known performance anti-patterns (see references/anti-patterns.md):
| Pattern |
Detection |
Impact |
| SELECT * |
Star in select list |
Transfers unnecessary data |
| N+1 queries |
Loop with query inside |
N additional roundtrips |
| Function on indexed column |
WHERE UPPER(name) = 'X' |
Index bypass |
| Implicit type cast |
String compared to integer |
Index bypass |
| Missing join condition |
Cartesian product |
Exponential rows |
| LIKE '%prefix' |
Leading wildcard |
Full scan |
| OR with different columns |
WHERE a=1 OR b=2 |
Index bypass |
| SELECT DISTINCT as band-aid |
Hides duplicate-producing join |
Fix the join instead |
Phase 4: Optimization
- Index recommendations — Based on WHERE, JOIN, ORDER BY, GROUP BY columns.
Consider composite indexes for multi-column conditions.
- Query rewrite — Convert correlated subqueries to JOINs, replace
IN (SELECT...)
with EXISTS, use CTEs for readability without performance cost (PostgreSQL 12+
may inline CTEs).
- Schema suggestions — Denormalization, materialized views, partitioning
(mention only when query-level optimization is insufficient).
Phase 5: Output
Present the original query, detected issues, recommended indexes, rewritten query,
and explanation of each change.
Output Format
## SQL Optimization Analysis
### Original Query
```sql
{original SQL}
```
### Issues Detected
| # | Issue | Severity | Location | Impact |
| --- | ------- | ----------------- | ------------------- | ---------------- |
| 1 | {issue} | {High/Medium/Low} | {WHERE/JOIN/SELECT} | {what it causes} |
### EXPLAIN Interpretation
{If EXPLAIN provided}
- **Bottleneck:** {node type} on `{table}` (cost: {N})
- **Rows scanned:** {N} (estimated {M})
- **Index used:** {name or "None"}
- **Key insight:** {what this reveals}
### Recommended Indexes
```sql
-- {Reason for this index}
CREATE INDEX {name} ON {table}({columns});
```
### Optimized Query
```sql
{rewritten query}
```
### Change Explanation
1. **{Change}** — {Why this improves performance. Include estimated impact.}
### Expected Improvement
- Scan type: {before} → {after}
- Estimated rows scanned: {before} → {after}
- Index usage: {before} → {after}
Configuring Scope
| Mode |
Input |
Depth |
When to Use |
quick |
Single query |
Anti-pattern scan + index suggestion |
Fast feedback during development |
standard |
Query + schema |
Full analysis with rewrites |
Default for optimization requests |
deep |
Query + EXPLAIN + schema + row counts |
Full analysis with statistics validation |
Production performance investigation |
Calibration Rules
- Measure before optimizing. Request EXPLAIN output before recommending changes.
Intuition about query performance is unreliable — a "slow-looking" query may be
fast with proper indexes, and a "simple" query may scan millions of rows.
- Index discipline. Every index has write overhead. Do not recommend indexes
that won't be used by the actual query workload. Consider the read/write ratio.
- Explain WHY, not just WHAT. "Add an index on
users.email" is incomplete.
"Add an index on users.email because the WHERE clause filters by email, currently
causing a sequential scan of 1M rows" is actionable.
- Preserve correctness. Query rewrites must return identical results. If a
rewrite changes semantics (e.g., INNER JOIN vs LEFT JOIN), flag it explicitly.
- Database engine matters. PostgreSQL, MySQL, and SQLite have different optimizers,
index types, and capabilities. Always target the specific engine.
Error Handling
| Problem |
Resolution |
| No EXPLAIN output provided |
Analyze query structure and anti-patterns. Note that recommendations are best-effort without EXPLAIN. |
| Unknown database engine |
Ask which engine. Default anti-pattern analysis applies to all engines. |
| Query uses ORM-generated SQL |
Optimize the SQL, then suggest ORM-level changes (e.g., select_related in Django, eager loading). |
| Schema not provided |
Infer table structure from the query. Note assumptions. |
| Query is already optimal |
State that no significant improvements are possible. Suggest non-query optimizations (caching, denormalization). |
| Complex multi-CTE query |
Analyze each CTE independently, then analyze the composition. |
When NOT to Optimize
Push back if:
- The query runs infrequently and performance is acceptable (one-time admin query)
- The optimization requires schema changes that affect many consumers — suggest an ADR instead
- The real problem is application-level (N+1 from ORM loop) — fix the application code, not the SQL
- The query is auto-generated by a tool (ORM migration, BI tool) — optimize at the tool level
1---2name: sql-optimizer3description: Analyzes SQL queries for missing indexes, N+1 patterns, suboptimal joins, and full table scans. Interprets EXPLAIN, detects anti-patterns, rewrites queries. Triggers on: "optimize this query", "slow query", "add indexes", "explain plan", "N+1 query", "why is this query slow".4---56# SQL Optimizer78Systematic SQL performance analysis: parse query structure, interpret EXPLAIN plans,9detect anti-patterns (N+1, full scans, cartesian joins), recommend indexes, and rewrite10queries — with explanations of WHY each change improves performance, not just WHAT changed.1112## Reference Files1314| File | Contents | Load When |15| --------------------------------- | ------------------------------------------------------------------------- | ---------------------------------- |16| `references/anti-patterns.md` | Common SQL anti-patterns with detection rules and fixes | Always |17| `references/index-strategies.md` | Index type selection, composite index ordering, covering indexes | Index recommendations needed |18| `references/explain-guide.md` | Reading EXPLAIN output for PostgreSQL, MySQL, SQLite | EXPLAIN plan provided |19| `references/join-optimization.md` | Join type selection, join order optimization, subquery-to-join conversion | Query contains joins or subqueries |2021## Prerequisites2223- The SQL query to optimize24- Database engine (PostgreSQL, MySQL, SQLite) — optimization differs by engine25- Table schemas and approximate row counts (helpful but not required)26- EXPLAIN output (highly valuable when available)2728## Workflow2930### Phase 1: Query Analysis3132Parse the SQL to understand its structure:33341. **Identify operations** — SELECT columns, FROM tables, JOIN conditions, WHERE filters,35 GROUP BY, ORDER BY, HAVING, subqueries.362. **Map table relationships** — Which tables are joined? On what keys? Are there37 implicit cartesian products?383. **Detect immediate red flags**:39 - `SELECT *` — fetching unnecessary columns40 - Functions on indexed columns in WHERE — prevents index use41 - `OR` in WHERE — often prevents index use42 - Correlated subqueries — potential N+143 - Missing WHERE on DELETE/UPDATE — dangerous4445### Phase 2: EXPLAIN Interpretation4647If an EXPLAIN plan is provided:48491. **Scan types** — Sequential Scan (bad for large tables), Index Scan (good),50 Index Only Scan (best), Bitmap Index Scan (acceptable).512. **Join methods** — Nested Loop (good for small tables), Hash Join (good for52 equi-joins), Merge Join (good for sorted data).533. **Row estimates** — Compare estimated rows with actual rows. Large discrepancies54 indicate stale statistics (`ANALYZE`).554. **Cost hotspots** — Highest-cost node is the bottleneck. Optimize there first.565. **Sort operations** — External sorts (disk) are expensive. Consider indexes57 that match ORDER BY.5859### Phase 3: Anti-Pattern Detection6061Check for known performance anti-patterns (see `references/anti-patterns.md`):6263| Pattern | Detection | Impact |64| --------------------------- | ------------------------------ | -------------------------- |65| SELECT \* | Star in select list | Transfers unnecessary data |66| N+1 queries | Loop with query inside | N additional roundtrips |67| Function on indexed column | `WHERE UPPER(name) = 'X'` | Index bypass |68| Implicit type cast | String compared to integer | Index bypass |69| Missing join condition | Cartesian product | Exponential rows |70| LIKE '%prefix' | Leading wildcard | Full scan |71| OR with different columns | `WHERE a=1 OR b=2` | Index bypass |72| SELECT DISTINCT as band-aid | Hides duplicate-producing join | Fix the join instead |7374### Phase 4: Optimization75761. **Index recommendations** — Based on WHERE, JOIN, ORDER BY, GROUP BY columns.77 Consider composite indexes for multi-column conditions.782. **Query rewrite** — Convert correlated subqueries to JOINs, replace `IN (SELECT...)`79 with EXISTS, use CTEs for readability without performance cost (PostgreSQL 12+80 may inline CTEs).813. **Schema suggestions** — Denormalization, materialized views, partitioning82 (mention only when query-level optimization is insufficient).8384### Phase 5: Output8586Present the original query, detected issues, recommended indexes, rewritten query,87and explanation of each change.8889## Output Format9091````mardkown92## SQL Optimization Analysis9394### Original Query95```sql96{original SQL}97```9899### Issues Detected100101| # | Issue | Severity | Location | Impact |102| --- | ------- | ----------------- | ------------------- | ---------------- |103| 1 | {issue} | {High/Medium/Low} | {WHERE/JOIN/SELECT} | {what it causes} |104105### EXPLAIN Interpretation106107{If EXPLAIN provided}108109- **Bottleneck:** {node type} on `{table}` (cost: {N})110- **Rows scanned:** {N} (estimated {M})111- **Index used:** {name or "None"}112- **Key insight:** {what this reveals}113114### Recommended Indexes115116```sql117-- {Reason for this index}118CREATE INDEX {name} ON {table}({columns});119```120121### Optimized Query122123```sql124{rewritten query}125```126127### Change Explanation1281291. **{Change}** — {Why this improves performance. Include estimated impact.}130131### Expected Improvement132133- Scan type: {before} → {after}134- Estimated rows scanned: {before} → {after}135- Index usage: {before} → {after}136137````138139## Configuring Scope140141| Mode | Input | Depth | When to Use |142| ---------- | ------------------------------------- | ---------------------------------------- | ------------------------------------ |143| `quick` | Single query | Anti-pattern scan + index suggestion | Fast feedback during development |144| `standard` | Query + schema | Full analysis with rewrites | Default for optimization requests |145| `deep` | Query + EXPLAIN + schema + row counts | Full analysis with statistics validation | Production performance investigation |146147## Calibration Rules1481491. **Measure before optimizing.** Request EXPLAIN output before recommending changes.150 Intuition about query performance is unreliable — a "slow-looking" query may be151 fast with proper indexes, and a "simple" query may scan millions of rows.1522. **Index discipline.** Every index has write overhead. Do not recommend indexes153 that won't be used by the actual query workload. Consider the read/write ratio.1543. **Explain WHY, not just WHAT.** "Add an index on `users.email`" is incomplete.155 "Add an index on `users.email` because the WHERE clause filters by email, currently156 causing a sequential scan of 1M rows" is actionable.1574. **Preserve correctness.** Query rewrites must return identical results. If a158 rewrite changes semantics (e.g., INNER JOIN vs LEFT JOIN), flag it explicitly.1595. **Database engine matters.** PostgreSQL, MySQL, and SQLite have different optimizers,160 index types, and capabilities. Always target the specific engine.161162## Error Handling163164| Problem | Resolution |165| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |166| No EXPLAIN output provided | Analyze query structure and anti-patterns. Note that recommendations are best-effort without EXPLAIN. |167| Unknown database engine | Ask which engine. Default anti-pattern analysis applies to all engines. |168| Query uses ORM-generated SQL | Optimize the SQL, then suggest ORM-level changes (e.g., `select_related` in Django, eager loading). |169| Schema not provided | Infer table structure from the query. Note assumptions. |170| Query is already optimal | State that no significant improvements are possible. Suggest non-query optimizations (caching, denormalization). |171| Complex multi-CTE query | Analyze each CTE independently, then analyze the composition. |172173## When NOT to Optimize174175Push back if:176177- The query runs infrequently and performance is acceptable (one-time admin query)178- The optimization requires schema changes that affect many consumers — suggest an ADR instead179- The real problem is application-level (N+1 from ORM loop) — fix the application code, not the SQL180- The query is auto-generated by a tool (ORM migration, BI tool) — optimize at the tool level181182```183184```