Database Query Optimizer
Analyzes SQL and NoSQL queries for performance bottlenecks, interprets query execution plans, and rewrites queries or proposes schema/index changes to reduce latency and resource usage.
When to Use
- User shares a slow query or reports high database latency
EXPLAIN / EXPLAIN ANALYZE output shows sequential scans, hash joins on large tables, or high cost estimates
- Application performance profiling identifies database calls as the bottleneck
- An ORM is generating N+1 queries
- User asks to add indexes to improve query performance
- A query times out in production under load
Process
Understand the query context:
- What database engine? (PostgreSQL, MySQL, SQLite, MongoDB, DynamoDB, etc.)
- What is the approximate table size and cardinality of key columns?
- Are there existing indexes? (request
\d tablename or SHOW INDEX FROM tablename if not provided)
- What is the acceptable latency target?
Parse and analyze the query:
- Identify which tables are scanned and which are indexed lookups
- Look for
SELECT * — replace with explicit column list
- Find unindexed filter columns in
WHERE, JOIN ON, and ORDER BY clauses
- Spot functions applied to indexed columns (
WHERE LOWER(email) = ...) that defeat indexes
- Identify correlated subqueries that execute once per row
- Check for
DISTINCT or GROUP BY on large result sets without filtering first
- Look for
OFFSET-based pagination on large tables (use keyset pagination instead)
Read and interpret the query plan (if provided):
Seq Scan on a large table → missing index
Hash Join with high rows → consider indexed nested loop join
- High
actual time vs estimated rows → stale statistics, run ANALYZE
Sort node with high cost → add index that provides sort order
Nested Loop with large outer table → may need to rewrite as CTE or temp table
Propose optimizations in priority order:
- Index additions: most impactful, lowest risk
- Query rewrite: equivalent logic, better plan
- Schema changes: denormalization, partitioning (higher effort, note trade-offs)
- Application-level: batching, caching, connection pooling
Write the optimized query and explain the improvement.
Suggest the index DDL with justification.
Estimate the improvement based on query plan changes (e.g., "reduces rows scanned from 500k to ~200").
Output Format
## Query Analysis
### Original Query
```sql
SELECT DISTINCT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE LOWER(u.email) LIKE '%@example.com'
GROUP BY u.name
ORDER BY order_count DESC;
Issues Found
- Function on indexed column (
LOWER(u.email)) — prevents index use on email.
- Leading wildcard in LIKE (
'%@example.com') — forces full table scan.
- SELECT DISTINCT + GROUP BY is redundant — DISTINCT can be removed.
- No index on
orders.user_id — the JOIN causes a sequential scan of orders.
Optimized Query
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.email ILIKE '%@example.com' -- or store domain separately
GROUP BY u.id, u.name
ORDER BY order_count DESC;
Recommended Indexes
-- Enables fast join on orders table
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);
-- For domain-based filtering, consider a generated column:
ALTER TABLE users ADD COLUMN email_domain TEXT GENERATED ALWAYS AS
(split_part(email, '@', 2)) STORED;
CREATE INDEX idx_users_email_domain ON users(email_domain);
Expected Improvement
idx_orders_user_id reduces JOIN cost from Seq Scan (O(n)) to Index Scan (O(log n))
- Estimated query time: 2400ms → ~80ms for typical dataset of 100k users / 2M orders
## Examples
### Example Input (N+1 Problem)
```python
# ORM generating N+1 queries
users = User.objects.all()
for user in users:
print(user.profile.bio) # triggers 1 query per user
Example Output
# Fix: use select_related to JOIN in a single query
users = User.objects.select_related('profile').all()
for user in users:
print(user.profile.bio) # no additional queries
# SQL generated (1 query instead of N+1):
# SELECT users.*, profiles.* FROM users
# INNER JOIN profiles ON profiles.user_id = users.id
Boundaries
- Do NOT suggest schema changes (adding columns, partitioning) without explicitly noting the migration effort and potential downtime.
- Do NOT recommend
CREATE INDEX without CONCURRENTLY on production PostgreSQL tables — blocking locks can cause outages.
- Do NOT assume cardinality or data distribution — ask if needed for accurate advice.
- Do NOT rewrite stored procedures or triggers unless explicitly asked.
- If the query plan is not provided, flag that recommendations are based on static analysis only and may not reflect actual execution behavior.
- Do NOT recommend disabling query planner features (e.g.,
enable_seqscan = off) as a production fix.
- NoSQL (MongoDB, DynamoDB) optimization follows different principles — confirm the query type before applying SQL-specific advice.
1---2name: database-query-optimizer3description: Analyzes SQL or NoSQL queries, explains query plans, and rewrites them for better performance. Invoke when asked to optimize a query, speed up a slow database call, analyze a query plan, add indexes, or fix N+1 query problems.4---56# Database Query Optimizer78Analyzes SQL and NoSQL queries for performance bottlenecks, interprets query execution plans, and rewrites queries or proposes schema/index changes to reduce latency and resource usage.910## When to Use1112- User shares a slow query or reports high database latency13- `EXPLAIN` / `EXPLAIN ANALYZE` output shows sequential scans, hash joins on large tables, or high cost estimates14- Application performance profiling identifies database calls as the bottleneck15- An ORM is generating N+1 queries16- User asks to add indexes to improve query performance17- A query times out in production under load1819## Process20211. **Understand the query context**:22 - What database engine? (PostgreSQL, MySQL, SQLite, MongoDB, DynamoDB, etc.)23 - What is the approximate table size and cardinality of key columns?24 - Are there existing indexes? (request `\d tablename` or `SHOW INDEX FROM tablename` if not provided)25 - What is the acceptable latency target?26272. **Parse and analyze the query**:28 - Identify which tables are scanned and which are indexed lookups29 - Look for `SELECT *` — replace with explicit column list30 - Find unindexed filter columns in `WHERE`, `JOIN ON`, and `ORDER BY` clauses31 - Spot functions applied to indexed columns (`WHERE LOWER(email) = ...`) that defeat indexes32 - Identify correlated subqueries that execute once per row33 - Check for `DISTINCT` or `GROUP BY` on large result sets without filtering first34 - Look for `OFFSET`-based pagination on large tables (use keyset pagination instead)35363. **Read and interpret the query plan** (if provided):37 - `Seq Scan` on a large table → missing index38 - `Hash Join` with high rows → consider indexed nested loop join39 - High `actual time` vs `estimated rows` → stale statistics, run `ANALYZE`40 - `Sort` node with high cost → add index that provides sort order41 - `Nested Loop` with large outer table → may need to rewrite as CTE or temp table42434. **Propose optimizations in priority order**:44 - **Index additions**: most impactful, lowest risk45 - **Query rewrite**: equivalent logic, better plan46 - **Schema changes**: denormalization, partitioning (higher effort, note trade-offs)47 - **Application-level**: batching, caching, connection pooling48495. **Write the optimized query** and explain the improvement.50516. **Suggest the index DDL** with justification.52537. **Estimate the improvement** based on query plan changes (e.g., "reduces rows scanned from 500k to ~200").5455## Output Format5657```58## Query Analysis5960### Original Query61```sql62SELECT DISTINCT u.name, COUNT(o.id) as order_count63FROM users u64LEFT JOIN orders o ON o.user_id = u.id65WHERE LOWER(u.email) LIKE '%@example.com'66GROUP BY u.name67ORDER BY order_count DESC;68```6970### Issues Found71721. **Function on indexed column** (`LOWER(u.email)`) — prevents index use on `email`.732. **Leading wildcard** in LIKE (`'%@example.com'`) — forces full table scan.743. **SELECT DISTINCT + GROUP BY** is redundant — DISTINCT can be removed.754. **No index** on `orders.user_id` — the JOIN causes a sequential scan of `orders`.7677### Optimized Query78```sql79SELECT u.name, COUNT(o.id) AS order_count80FROM users u81LEFT JOIN orders o ON o.user_id = u.id82WHERE u.email ILIKE '%@example.com' -- or store domain separately83GROUP BY u.id, u.name84ORDER BY order_count DESC;85```8687### Recommended Indexes88```sql89-- Enables fast join on orders table90CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);9192-- For domain-based filtering, consider a generated column:93ALTER TABLE users ADD COLUMN email_domain TEXT GENERATED ALWAYS AS94 (split_part(email, '@', 2)) STORED;95CREATE INDEX idx_users_email_domain ON users(email_domain);96```9798### Expected Improvement99- `idx_orders_user_id` reduces JOIN cost from Seq Scan (O(n)) to Index Scan (O(log n))100- Estimated query time: 2400ms → ~80ms for typical dataset of 100k users / 2M orders101```102103## Examples104105### Example Input (N+1 Problem)106```python107# ORM generating N+1 queries108users = User.objects.all()109for user in users:110 print(user.profile.bio) # triggers 1 query per user111```112113### Example Output114```python115# Fix: use select_related to JOIN in a single query116users = User.objects.select_related('profile').all()117for user in users:118 print(user.profile.bio) # no additional queries119120# SQL generated (1 query instead of N+1):121# SELECT users.*, profiles.* FROM users122# INNER JOIN profiles ON profiles.user_id = users.id123```124125## Boundaries126127- Do NOT suggest schema changes (adding columns, partitioning) without explicitly noting the migration effort and potential downtime.128- Do NOT recommend `CREATE INDEX` without `CONCURRENTLY` on production PostgreSQL tables — blocking locks can cause outages.129- Do NOT assume cardinality or data distribution — ask if needed for accurate advice.130- Do NOT rewrite stored procedures or triggers unless explicitly asked.131- If the query plan is not provided, flag that recommendations are based on static analysis only and may not reflect actual execution behavior.132- Do NOT recommend disabling query planner features (e.g., `enable_seqscan = off`) as a production fix.133- NoSQL (MongoDB, DynamoDB) optimization follows different principles — confirm the query type before applying SQL-specific advice.