home / skills / aj-geddes / useful-ai-prompts / sql-query-optimization

sql-query-optimization skill

/skills/sql-query-optimization

This skill analyzes and refactors SQL queries to boost performance in PostgreSQL and MySQL, guiding indexing, rewriting, and plan analysis for faster results.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill sql-query-optimization

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.9 KB
---
name: sql-query-optimization
description: Analyze and optimize SQL queries for performance. Use when improving slow queries, reducing execution time, or analyzing query performance in PostgreSQL and MySQL.
---

# SQL Query Optimization

## Overview

Analyze SQL queries to identify performance bottlenecks and implement optimization techniques. Includes query analysis, indexing strategies, and rewriting patterns for improved performance.

## When to Use

- Slow query analysis and tuning
- Query rewriting and refactoring
- Index utilization verification
- Join optimization
- Subquery optimization
- Query plan analysis (EXPLAIN)
- Performance baseline establishment

## Query Analysis Framework

### 1. Analyze Current Performance

**PostgreSQL:**

```sql
-- Analyze query plan with execution time
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT u.id, u.email, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > NOW() - INTERVAL '1 year'
GROUP BY u.id, u.email;

-- Check table statistics
SELECT * FROM pg_stats
WHERE tablename = 'users' AND attname = 'created_at';
```

**MySQL:**

```sql
-- Analyze query plan
EXPLAIN FORMAT=JSON
SELECT u.id, u.email, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > DATE_SUB(NOW(), INTERVAL 1 YEAR)
GROUP BY u.id, u.email;

-- Check table size
SELECT table_name, ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size_MB'
FROM information_schema.tables WHERE table_schema = 'database_name';
```

### 2. Common Optimization Patterns

**PostgreSQL - Index Optimization:**

```sql
-- Create indexes for frequently filtered columns
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC)
WHERE status != 'cancelled';

-- Partial indexes for filtered queries
CREATE INDEX idx_active_products
ON products(category_id)
WHERE active = true;

-- Multi-column covering indexes
CREATE INDEX idx_users_email_verified_covering
ON users(email, verified)
INCLUDE (id, name, created_at);
```

**MySQL - Index Optimization:**

```sql
-- Create composite index for multi-column filtering
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);

-- Use FULLTEXT index for text search
CREATE FULLTEXT INDEX idx_products_search
ON products(name, description);

-- Prefix indexes for large VARCHAR
CREATE INDEX idx_large_text
ON large_table(text_column(100));
```

### 3. Query Rewriting Techniques

**PostgreSQL - Window Functions:**

```sql
-- Inefficient: multiple passes
SELECT p.id, p.name,
  (SELECT COUNT(*) FROM orders o WHERE o.product_id = p.id) as order_count,
  (SELECT SUM(quantity) FROM order_items oi WHERE oi.product_id = p.id) as total_sold
FROM products p;

-- Optimized: single pass with window functions
SELECT DISTINCT p.id, p.name,
  COUNT(*) OVER (PARTITION BY p.id) as order_count,
  SUM(oi.quantity) OVER (PARTITION BY p.id) as total_sold
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id;
```

**MySQL - JOIN Optimization:**

```sql
-- Inefficient: JOIN after aggregation
SELECT user_id, name, total_orders
FROM (
  SELECT u.id as user_id, u.name, COUNT(o.id) as total_orders
  FROM users u
  LEFT JOIN orders o ON u.id = o.user_id
  GROUP BY u.id, u.name
) subquery
WHERE total_orders > 5;

-- Optimized: aggregate with HAVING clause
SELECT u.id, u.name, COUNT(o.id) as total_orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5;
```

### 4. Batch Operations

**PostgreSQL - Bulk Insert:**

```sql
-- Inefficient: multiple round trips
INSERT INTO users (email, name) VALUES ('[email protected]', 'User One');
INSERT INTO users (email, name) VALUES ('[email protected]', 'User Two');

-- Optimized: single batch
INSERT INTO users (email, name) VALUES
  ('[email protected]', 'User One'),
  ('[email protected]', 'User Two'),
  ('[email protected]', 'User Three')
ON CONFLICT (email) DO UPDATE SET updated_at = NOW();
```

**MySQL - Bulk Update:**

```sql
-- Optimized: bulk update with VALUES clause
UPDATE products p
JOIN (
  SELECT id, price FROM product_updates
) AS updates ON p.id = updates.id
SET p.price = updates.price;
```

## Performance Monitoring

**PostgreSQL - Long Running Queries:**

```sql
-- Find slow queries
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 1000
ORDER BY mean_exec_time DESC
LIMIT 10;

-- Reset statistics
SELECT pg_stat_statements_reset();
```

**MySQL - Slow Query Log:**

```sql
-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;

-- View slow queries
SELECT * FROM mysql.slow_log
ORDER BY start_time DESC LIMIT 10;
```

## Key Optimization Checklist

- Use EXPLAIN/EXPLAIN ANALYZE before and after optimization
- Add indexes to columns in WHERE, JOIN, and ORDER BY clauses
- Use LIMIT when exploring large result sets
- Avoid SELECT * when only specific columns needed
- Use database functions instead of application-level processing
- Batch operations to reduce network round trips
- Partition large tables for improved query performance
- Update statistics regularly with ANALYZE

## Common Pitfalls

❌ Don't create indexes without testing impact
❌ Don't use LIKE with leading wildcard without full-text search
❌ Don't JOIN unnecessary tables
❌ Don't ignore ORDER BY performance impact
❌ Don't skip EXPLAIN analysis

✅ DO test query changes in development first
✅ DO monitor query performance after deployment
✅ DO update table statistics regularly
✅ DO use appropriate data types for columns
✅ DO consider materialized views for complex aggregations

## Resources

- [PostgreSQL EXPLAIN Documentation](https://www.postgresql.org/docs/current/sql-explain.html)
- [MySQL EXPLAIN Documentation](https://dev.mysql.com/doc/refman/8.0/en/explain.html)
- [pgBadger - PostgreSQL log analyzer](https://pgbadger.darold.net/)
- [MySQL Workbench Query Analyzer](https://www.mysql.com/products/workbench/)

Overview

This skill analyzes and optimizes SQL queries to reduce execution time and resource usage for PostgreSQL and MySQL. It identifies bottlenecks, recommends indexing and rewriting strategies, and verifies improvements with query plans and metrics. Use it to move slow queries toward predictable, production-ready performance.

How this skill works

The skill inspects query plans (EXPLAIN / EXPLAIN ANALYZE) and runtime metrics, checks table statistics and index usage, and suggests concrete changes: indexes, partial/covering indexes, query rewrites, and batching. It validates recommendations by comparing before/after plans and measuring execution time, buffer usage, and slow-query logs.

When to use it

  • Investigating slow or unpredictable query performance in PostgreSQL or MySQL
  • Before and after deploying schema or query changes to validate impact
  • When optimizing joins, aggregations, or subqueries that scan many rows
  • To design indexes for common WHERE, JOIN, and ORDER BY patterns
  • When preparing bulk operations or reducing application-level processing

Best practices

  • Always run EXPLAIN/EXPLAIN ANALYZE and capture JSON plans for comparison
  • Add indexes targeted to WHERE, JOIN, and ORDER BY columns, test impact
  • Prefer partial, covering, or composite indexes to reduce I/O
  • Use window functions or HAVING to eliminate redundant passes and subqueries
  • Batch inserts/updates to minimize round trips and use ON CONFLICT/joins for updates
  • Monitor slow-query logs and stats (pg_stat_statements / MySQL slow log) after changes

Example use cases

  • Rewrite nested subqueries into window functions to reduce multiple passes over data
  • Create a partial index on active products to speed filtered SELECTs without inflating index size
  • Convert multiple single-row inserts into a single bulk INSERT with ON CONFLICT handling
  • Diagnose a JOIN that causes sequential scans and add a composite index on join keys
  • Analyze long-running queries via pg_stat_statements or MySQL slow log and prioritize fixes

FAQ

How do I verify an optimization actually helps?

Capture EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) or EXPLAIN FORMAT=JSON before and after, then compare execution time, buffer hits, and row estimates.

When should I use a partial index?

Use partial indexes for queries that filter predictable subsets (e.g., active=true) to reduce index size and improve selectivity.