[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J5DPYNTZVQRTWS5CVG1APT":3,"skill-same:01M6J5DPYNTZVQRTWS5CVG1APT":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":13,"repositoryHtmlUrl":56,"repositoryHomepageUrl":6,"ownerHtmlUrl":57,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":58,"install":59,"resources":72},"01M6J5DPYNTZVQRTWS5CVG1APT","sql-optimization-patterns",null,"Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries. Use when debugging slow queries, designing database schemas, or optimizing application performance.","jest-won\u002Farchi-ro","jest-won","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F170307740?v=4","01M6J5DDZB6Q1Y5F3NWNQSAE03","01M6J5DE3QGT24C87G4JA4XTEH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fbackend\u002Fsql-optimization-patterns\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"sql","Optimization","sql optimization","patterns","sql optimization patterns","use","sql query optimization","indexing","strategies","query","queries","Designing Database Schemas","master","performance","explain","improve","analysis","database","debugging","database performance","2026-11-10 10:12:30",false,"---\nname: sql-optimization-patterns\ndescription: Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries. Use when debugging slow queries, designing database schemas, or optimizing application performance.\n---\n\n# SQL Optimization Patterns\n\nTransform slow database queries into lightning-fast operations through systematic optimization, proper indexing, and query plan analysis.\n\n## When to Use This Skill\n\n- Debugging slow-running queries\n- Designing performant database schemas\n- Optimizing application response times\n- Reducing database load and costs\n- Improving scalability for growing datasets\n- Analyzing EXPLAIN query plans\n- Implementing efficient indexes\n- Resolving N+1 query problems\n\n## Core Concepts\n\n### 1. Query Execution Plans (EXPLAIN)\n\nUnderstanding EXPLAIN output is fundamental to optimization.\n\n**PostgreSQL EXPLAIN:**\n\n```sql\n-- Basic explain\nEXPLAIN SELECT * FROM users WHERE email = 'user@example.com';\n\n-- With actual execution stats\nEXPLAIN ANALYZE\nSELECT * FROM users WHERE email = 'user@example.com';\n\n-- Verbose output with more details\nEXPLAIN (ANALYZE, BUFFERS, VERBOSE)\nSELECT u.*, o.order_total\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE u.created_at > NOW() - INTERVAL '30 days';\n```\n\n**Key Metrics to Watch:**\n\n- **Seq Scan**: Full table scan (usually slow for large tables)\n- **Index Scan**: Using index (good)\n- **Index Only Scan**: Using index without touching table (best)\n- **Nested Loop**: Join method (okay for small datasets)\n- **Hash Join**: Join method (good for larger datasets)\n- **Merge Join**: Join method (good for sorted data)\n- **Cost**: Estimated query cost (lower is better)\n- **Rows**: Estimated rows returned\n- **Actual Time**: Real execution time\n\n### 2. Index Strategies\n\nIndexes are the most powerful optimization tool.\n\n**Index Types:**\n\n- **B-Tree**: Default, good for equality and range queries\n- **Hash**: Only for equality (=) comparisons\n- **GIN**: Full-text search, array queries, JSONB\n- **GiST**: Geometric data, full-text search\n- **BRIN**: Block Range INdex for very large tables with correlation\n\n```sql\n-- Standard B-Tree index\nCREATE INDEX idx_users_email ON users(email);\n\n-- Composite index (order matters!)\nCREATE INDEX idx_orders_user_status ON orders(user_id, status);\n\n-- Partial index (index subset of rows)\nCREATE INDEX idx_active_users ON users(email)\nWHERE status = 'active';\n\n-- Expression index\nCREATE INDEX idx_users_lower_email ON users(LOWER(email));\n\n-- Covering index (include additional columns)\nCREATE INDEX idx_users_email_covering ON users(email)\nINCLUDE (name, created_at);\n\n-- Full-text search index\nCREATE INDEX idx_posts_search ON posts\nUSING GIN(to_tsvector('english', title || ' ' || body));\n\n-- JSONB index\nCREATE INDEX idx_metadata ON events USING GIN(metadata);\n```\n\n### 3. Query Optimization Patterns\n\n**Avoid SELECT \\*:**\n\n```sql\n-- Bad: Fetches unnecessary columns\nSELECT * FROM users WHERE id = 123;\n\n-- Good: Fetch only what you need\nSELECT id, email, name FROM users WHERE id = 123;\n```\n\n**Use WHERE Clause Efficiently:**\n\n```sql\n-- Bad: Function prevents index usage\nSELECT * FROM users WHERE LOWER(email) = 'user@example.com';\n\n-- Good: Create functional index or use exact match\nCREATE INDEX idx_users_email_lower ON users(LOWER(email));\n-- Then:\nSELECT * FROM users WHERE LOWER(email) = 'user@example.com';\n\n-- Or store normalized data\nSELECT * FROM users WHERE email = 'user@example.com';\n```\n\n**Optimize JOINs:**\n\n```sql\n-- Bad: Cartesian product then filter\nSELECT u.name, o.total\nFROM users u, orders o\nWHERE u.id = o.user_id AND u.created_at > '2024-01-01';\n\n-- Good: Filter before join\nSELECT u.name, o.total\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE u.created_at > '2024-01-01';\n\n-- Better: Filter both tables\nSELECT u.name, o.total\nFROM (SELECT * FROM users WHERE created_at > '2024-01-01') u\nJOIN orders o ON u.id = o.user_id;\n```\n\n## Optimization Patterns\n\n### Pattern 1: Eliminate N+1 Queries\n\n**Problem: N+1 Query Anti-Pattern**\n\n```python\n# Bad: Executes N+1 queries\nusers = db.query(\"SELECT * FROM users LIMIT 10\")\nfor user in users:\n    orders = db.query(\"SELECT * FROM orders WHERE user_id = ?\", user.id)\n    # Process orders\n```\n\n**Solution: Use JOINs or Batch Loading**\n\n```sql\n-- Solution 1: JOIN\nSELECT\n    u.id, u.name,\n    o.id as order_id, o.total\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id\nWHERE u.id IN (1, 2, 3, 4, 5);\n\n-- Solution 2: Batch query\nSELECT * FROM orders\nWHERE user_id IN (1, 2, 3, 4, 5);\n```\n\n```python\n# Good: Single query with JOIN or batch load\n# Using JOIN\nresults = db.query(\"\"\"\n    SELECT u.id, u.name, o.id as order_id, o.total\n    FROM users u\n    LEFT JOIN orders o ON u.id = o.user_id\n    WHERE u.id IN (1, 2, 3, 4, 5)\n\"\"\")\n\n# Or batch load\nusers = db.query(\"SELECT * FROM users LIMIT 10\")\nuser_ids = [u.id for u in users]\norders = db.query(\n    \"SELECT * FROM orders WHERE user_id IN (?)\",\n    user_ids\n)\n# Group orders by user_id\norders_by_user = {}\nfor order in orders:\n    orders_by_user.setdefault(order.user_id, []).append(order)\n```\n\n### Pattern 2: Optimize Pagination\n\n**Bad: OFFSET on Large Tables**\n\n```sql\n-- Slow for large offsets\nSELECT * FROM users\nORDER BY created_at DESC\nLIMIT 20 OFFSET 100000;  -- Very slow!\n```\n\n**Good: Cursor-Based Pagination**\n\n```sql\n-- Much faster: Use cursor (last seen ID)\nSELECT * FROM users\nWHERE created_at \u003C '2024-01-15 10:30:00'  -- Last cursor\nORDER BY created_at DESC\nLIMIT 20;\n\n-- With composite sorting\nSELECT * FROM users\nWHERE (created_at, id) \u003C ('2024-01-15 10:30:00', 12345)\nORDER BY created_at DESC, id DESC\nLIMIT 20;\n\n-- Requires index\nCREATE INDEX idx_users_cursor ON users(created_at DESC, id DESC);\n```\n\n### Pattern 3: Aggregate Efficiently\n\n**Optimize COUNT Queries:**\n\n```sql\n-- Bad: Counts all rows\nSELECT COUNT(*) FROM orders;  -- Slow on large tables\n\n-- Good: Use estimates for approximate counts\nSELECT reltuples::bigint AS estimate\nFROM pg_class\nWHERE relname = 'orders';\n\n-- Good: Filter before counting\nSELECT COUNT(*) FROM orders\nWHERE created_at > NOW() - INTERVAL '7 days';\n\n-- Better: Use index-only scan\nCREATE INDEX idx_orders_created ON orders(created_at);\nSELECT COUNT(*) FROM orders\nWHERE created_at > NOW() - INTERVAL '7 days';\n```\n\n**Optimize GROUP BY:**\n\n```sql\n-- Bad: Group by then filter\nSELECT user_id, COUNT(*) as order_count\nFROM orders\nGROUP BY user_id\nHAVING COUNT(*) > 10;\n\n-- Better: Filter first, then group (if possible)\nSELECT user_id, COUNT(*) as order_count\nFROM orders\nWHERE status = 'completed'\nGROUP BY user_id\nHAVING COUNT(*) > 10;\n\n-- Best: Use covering index\nCREATE INDEX idx_orders_user_status ON orders(user_id, status);\n```\n\n### Pattern 4: Subquery Optimization\n\n**Transform Correlated Subqueries:**\n\n```sql\n-- Bad: Correlated subquery (runs for each row)\nSELECT u.name, u.email,\n    (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count\nFROM users u;\n\n-- Good: JOIN with aggregation\nSELECT u.name, u.email, COUNT(o.id) as order_count\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.id, u.name, u.email;\n\n-- Better: Use window functions\nSELECT DISTINCT ON (u.id)\n    u.name, u.email,\n    COUNT(o.id) OVER (PARTITION BY u.id) as order_count\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id;\n```\n\n**Use CTEs for Clarity:**\n\n```sql\n-- Using Common Table Expressions\nWITH recent_users AS (\n    SELECT id, name, email\n    FROM users\n    WHERE created_at > NOW() - INTERVAL '30 days'\n),\nuser_order_counts AS (\n    SELECT user_id, COUNT(*) as order_count\n    FROM orders\n    WHERE created_at > NOW() - INTERVAL '30 days'\n    GROUP BY user_id\n)\nSELECT ru.name, ru.email, COALESCE(uoc.order_count, 0) as orders\nFROM recent_users ru\nLEFT JOIN user_order_counts uoc ON ru.id = uoc.user_id;\n```\n\n### Pattern 5: Batch Operations\n\n**Batch INSERT:**\n\n```sql\n-- Bad: Multiple individual inserts\nINSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');\nINSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');\nINSERT INTO users (name, email) VALUES ('Carol', 'carol@example.com');\n\n-- Good: Batch insert\nINSERT INTO users (name, email) VALUES\n    ('Alice', 'alice@example.com'),\n    ('Bob', 'bob@example.com'),\n    ('Carol', 'carol@example.com');\n\n-- Better: Use COPY for bulk inserts (PostgreSQL)\nCOPY users (name, email) FROM '\u002Ftmp\u002Fusers.csv' CSV HEADER;\n```\n\n**Batch UPDATE:**\n\n```sql\n-- Bad: Update in loop\nUPDATE users SET status = 'active' WHERE id = 1;\nUPDATE users SET status = 'active' WHERE id = 2;\n-- ... repeat for many IDs\n\n-- Good: Single UPDATE with IN clause\nUPDATE users\nSET status = 'active'\nWHERE id IN (1, 2, 3, 4, 5, ...);\n\n-- Better: Use temporary table for large batches\nCREATE TEMP TABLE temp_user_updates (id INT, new_status VARCHAR);\nINSERT INTO temp_user_updates VALUES (1, 'active'), (2, 'active'), ...;\n\nUPDATE users u\nSET status = t.new_status\nFROM temp_user_updates t\nWHERE u.id = t.id;\n```\n\n## Advanced Techniques\n\n### Materialized Views\n\nPre-compute expensive queries.\n\n```sql\n-- Create materialized view\nCREATE MATERIALIZED VIEW user_order_summary AS\nSELECT\n    u.id,\n    u.name,\n    COUNT(o.id) as total_orders,\n    SUM(o.total) as total_spent,\n    MAX(o.created_at) as last_order_date\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id\nGROUP BY u.id, u.name;\n\n-- Add index to materialized view\nCREATE INDEX idx_user_summary_spent ON user_order_summary(total_spent DESC);\n\n-- Refresh materialized view\nREFRESH MATERIALIZED VIEW user_order_summary;\n\n-- Concurrent refresh (PostgreSQL)\nREFRESH MATERIALIZED VIEW CONCURRENTLY user_order_summary;\n\n-- Query materialized view (very fast)\nSELECT * FROM user_order_summary\nWHERE total_spent > 1000\nORDER BY total_spent DESC;\n```\n\n### Partitioning\n\nSplit large tables for better performance.\n\n```sql\n-- Range partitioning by date (PostgreSQL)\nCREATE TABLE orders (\n    id SERIAL,\n    user_id INT,\n    total DECIMAL,\n    created_at TIMESTAMP\n) PARTITION BY RANGE (created_at);\n\n-- Create partitions\nCREATE TABLE orders_2024_q1 PARTITION OF orders\n    FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');\n\nCREATE TABLE orders_2024_q2 PARTITION OF orders\n    FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');\n\n-- Queries automatically use appropriate partition\nSELECT * FROM orders\nWHERE created_at BETWEEN '2024-02-01' AND '2024-02-28';\n-- Only scans orders_2024_q1 partition\n```\n\n### Query Hints and Optimization\n\n```sql\n-- Force index usage (MySQL)\nSELECT * FROM users\nUSE INDEX (idx_users_email)\nWHERE email = 'user@example.com';\n\n-- Parallel query (PostgreSQL)\nSET max_parallel_workers_per_gather = 4;\nSELECT * FROM large_table WHERE condition;\n\n-- Join hints (PostgreSQL)\nSET enable_nestloop = OFF;  -- Force hash or merge join\n```\n\n## Best Practices\n\n1. **Index Selectively**: Too many indexes slow down writes\n2. **Monitor Query Performance**: Use slow query logs\n3. **Keep Statistics Updated**: Run ANALYZE regularly\n4. **Use Appropriate Data Types**: Smaller types = better performance\n5. **Normalize Thoughtfully**: Balance normalization vs performance\n6. **Cache Frequently Accessed Data**: Use application-level caching\n7. **Connection Pooling**: Reuse database connections\n8. **Regular Maintenance**: VACUUM, ANALYZE, rebuild indexes\n\n```sql\n-- Update statistics\nANALYZE users;\nANALYZE VERBOSE orders;\n\n-- Vacuum (PostgreSQL)\nVACUUM ANALYZE users;\nVACUUM FULL users;  -- Reclaim space (locks table)\n\n-- Reindex\nREINDEX INDEX idx_users_email;\nREINDEX TABLE users;\n```\n\n## Common Pitfalls\n\n- **Over-Indexing**: Each index slows down INSERT\u002FUPDATE\u002FDELETE\n- **Unused Indexes**: Waste space and slow writes\n- **Missing Indexes**: Slow queries, full table scans\n- **Implicit Type Conversion**: Prevents index usage\n- **OR Conditions**: Can't use indexes efficiently\n- **LIKE with Leading Wildcard**: `LIKE '%abc'` can't use index\n- **Function in WHERE**: Prevents index usage unless functional index exists\n\n## Monitoring Queries\n\n```sql\n-- Find slow queries (PostgreSQL)\nSELECT query, calls, total_time, mean_time\nFROM pg_stat_statements\nORDER BY mean_time DESC\nLIMIT 10;\n\n-- Find missing indexes (PostgreSQL)\nSELECT\n    schemaname,\n    tablename,\n    seq_scan,\n    seq_tup_read,\n    idx_scan,\n    seq_tup_read \u002F seq_scan AS avg_seq_tup_read\nFROM pg_stat_user_tables\nWHERE seq_scan > 0\nORDER BY seq_tup_read DESC\nLIMIT 10;\n\n-- Find unused indexes (PostgreSQL)\nSELECT\n    schemaname,\n    tablename,\n    indexname,\n    idx_scan,\n    idx_tup_read,\n    idx_tup_fetch\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0\nORDER BY pg_relation_size(indexrelid) DESC;\n```","# SQL Optimization Patterns\n\nTransform slow database queries into lightning-fast operations through systematic optimization, proper indexing, and query plan analysis.\n\n## When to Use This Skill\n\n- Debugging slow-running queries\n- Designing performant database schemas\n- Optimizing application response times\n- Reducing database load and costs\n- Improving scalability for growing datasets\n- Analyzing EXPLAIN query plans\n- Implementing efficient indexes\n- Resolving N+1 query problems\n\n## Core Concepts\n\n### 1. Query Execution Plans (EXPLAIN)\n\nUnderstanding EXPLAIN output is fundamental to optimization.\n\n**PostgreSQL EXPLAIN:**\n\n```sql\n-- Basic explain\nEXPLAIN SELECT * FROM users WHERE email = 'user@example.com';\n\n-- With actual execution stats\nEXPLAIN ANALYZE\nSELECT * FROM users WHERE email = 'user@example.com';\n\n-- Verbose output with more details\nEXPLAIN (ANALYZE, BUFFERS, VERBOSE)\nSELECT u.*, o.order_total\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE u.created_at > NOW() - INTERVAL '30 days';\n```\n\n**Key Metrics to Watch:**\n\n- **Seq Scan**: Full table scan (usually slow for large tables)\n- **Index Scan**: Using index (good)\n- **Index Only Scan**: Using index without touching table (best)\n- **Nested Loop**: Join method (okay for small datasets)\n- **Hash Join**: Join method (good for larger datasets)\n- **Merge Join**: Join method (good for sorted data)\n- **Cost**: Estimated query cost (lower is better)\n- **Rows**: Estimated rows returned\n- **Actual Time**: Real execution time\n\n### 2. Index Strategies\n\nIndexes are the most powerful optimization tool.\n\n**Index Types:**\n\n- **B-Tree**: Default, good for equality and range queries\n- **Hash**: Only for equality (=) comparisons\n- **GIN**: Full-text search, array queries, JSONB\n- **GiST**: Geometric data, full-text search\n- **BRIN**: Block Range INdex for very large tables with correlation\n\n```sql\n-- Standard B-Tree index\nCREATE INDEX idx_users_email ON users(email);\n\n-- Composite index (order matters!)\nCREATE INDEX idx_orders_user_status ON orders(user_id, status);\n\n-- Partial index (index subset of rows)\nCREATE INDEX idx_active_users ON users(email)\nWHERE status = 'active';\n\n-- Expression index\nCREATE INDEX idx_users_lower_email ON users(LOWER(email));\n\n-- Covering index (include additional columns)\nCREATE INDEX idx_users_email_covering ON users(email)\nINCLUDE (name, created_at);\n\n-- Full-text search index\nCREATE INDEX idx_posts_search ON posts\nUSING GIN(to_tsvector('english', title || ' ' || body));\n\n-- JSONB index\nCREATE INDEX idx_metadata ON events USING GIN(metadata);\n```\n\n### 3. Query Optimization Patterns\n\n**Avoid SELECT \\*:**\n\n```sql\n-- Bad: Fetches unnecessary columns\nSELECT * FROM users WHERE id = 123;\n\n-- Good: Fetch only what you need\nSELECT id, email, name FROM users WHERE id = 123;\n```\n\n**Use WHERE Clause Efficiently:**\n\n```sql\n-- Bad: Function prevents index usage\nSELECT * FROM users WHERE LOWER(email) = 'user@example.com';\n\n-- Good: Create functional index or use exact match\nCREATE INDEX idx_users_email_lower ON users(LOWER(email));\n-- Then:\nSELECT * FROM users WHERE LOWER(email) = 'user@example.com';\n\n-- Or store normalized data\nSELECT * FROM users WHERE email = 'user@example.com';\n```\n\n**Optimize JOINs:**\n\n```sql\n-- Bad: Cartesian product then filter\nSELECT u.name, o.total\nFROM users u, orders o\nWHERE u.id = o.user_id AND u.created_at > '2024-01-01';\n\n-- Good: Filter before join\nSELECT u.name, o.total\nFROM users u\nJOIN orders o ON u.id = o.user_id\nWHERE u.created_at > '2024-01-01';\n\n-- Better: Filter both tables\nSELECT u.name, o.total\nFROM (SELECT * FROM users WHERE created_at > '2024-01-01') u\nJOIN orders o ON u.id = o.user_id;\n```\n\n## Optimization Patterns\n\n### Pattern 1: Eliminate N+1 Queries\n\n**Problem: N+1 Query Anti-Pattern**\n\n```python\n# Bad: Executes N+1 queries\nusers = db.query(\"SELECT * FROM users LIMIT 10\")\nfor user in users:\n    orders = db.query(\"SELECT * FROM orders WHERE user_id = ?\", user.id)\n    # Process orders\n```\n\n**Solution: Use JOINs or Batch Loading**\n\n```sql\n-- Solution 1: JOIN\nSELECT\n    u.id, u.name,\n    o.id as order_id, o.total\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id\nWHERE u.id IN (1, 2, 3, 4, 5);\n\n-- Solution 2: Batch query\nSELECT * FROM orders\nWHERE user_id IN (1, 2, 3, 4, 5);\n```\n\n```python\n# Good: Single query with JOIN or batch load\n# Using JOIN\nresults = db.query(\"\"\"\n    SELECT u.id, u.name, o.id as order_id, o.total\n    FROM users u\n    LEFT JOIN orders o ON u.id = o.user_id\n    WHERE u.id IN (1, 2, 3, 4, 5)\n\"\"\")\n\n# Or batch load\nusers = db.query(\"SELECT * FROM users LIMIT 10\")\nuser_ids = [u.id for u in users]\norders = db.query(\n    \"SELECT * FROM orders WHERE user_id IN (?)\",\n    user_ids\n)\n# Group orders by user_id\norders_by_user = {}\nfor order in orders:\n    orders_by_user.setdefault(order.user_id, []).append(order)\n```\n\n### Pattern 2: Optimize Pagination\n\n**Bad: OFFSET on Large Tables**\n\n```sql\n-- Slow for large offsets\nSELECT * FROM users\nORDER BY created_at DESC\nLIMIT 20 OFFSET 100000;  -- Very slow!\n```\n\n**Good: Cursor-Based Pagination**\n\n```sql\n-- Much faster: Use cursor (last seen ID)\nSELECT * FROM users\nWHERE created_at \u003C '2024-01-15 10:30:00'  -- Last cursor\nORDER BY created_at DESC\nLIMIT 20;\n\n-- With composite sorting\nSELECT * FROM users\nWHERE (created_at, id) \u003C ('2024-01-15 10:30:00', 12345)\nORDER BY created_at DESC, id DESC\nLIMIT 20;\n\n-- Requires index\nCREATE INDEX idx_users_cursor ON users(created_at DESC, id DESC);\n```\n\n### Pattern 3: Aggregate Efficiently\n\n**Optimize COUNT Queries:**\n\n```sql\n-- Bad: Counts all rows\nSELECT COUNT(*) FROM orders;  -- Slow on large tables\n\n-- Good: Use estimates for approximate counts\nSELECT reltuples::bigint AS estimate\nFROM pg_class\nWHERE relname = 'orders';\n\n-- Good: Filter before counting\nSELECT COUNT(*) FROM orders\nWHERE created_at > NOW() - INTERVAL '7 days';\n\n-- Better: Use index-only scan\nCREATE INDEX idx_orders_created ON orders(created_at);\nSELECT COUNT(*) FROM orders\nWHERE created_at > NOW() - INTERVAL '7 days';\n```\n\n**Optimize GROUP BY:**\n\n```sql\n-- Bad: Group by then filter\nSELECT user_id, COUNT(*) as order_count\nFROM orders\nGROUP BY user_id\nHAVING COUNT(*) > 10;\n\n-- Better: Filter first, then group (if possible)\nSELECT user_id, COUNT(*) as order_count\nFROM orders\nWHERE status = 'completed'\nGROUP BY user_id\nHAVING COUNT(*) > 10;\n\n-- Best: Use covering index\nCREATE INDEX idx_orders_user_status ON orders(user_id, status);\n```\n\n### Pattern 4: Subquery Optimization\n\n**Transform Correlated Subqueries:**\n\n```sql\n-- Bad: Correlated subquery (runs for each row)\nSELECT u.name, u.email,\n    (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count\nFROM users u;\n\n-- Good: JOIN with aggregation\nSELECT u.name, u.email, COUNT(o.id) as order_count\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id\nGROUP BY u.id, u.name, u.email;\n\n-- Better: Use window functions\nSELECT DISTINCT ON (u.id)\n    u.name, u.email,\n    COUNT(o.id) OVER (PARTITION BY u.id) as order_count\nFROM users u\nLEFT JOIN orders o ON o.user_id = u.id;\n```\n\n**Use CTEs for Clarity:**\n\n```sql\n-- Using Common Table Expressions\nWITH recent_users AS (\n    SELECT id, name, email\n    FROM users\n    WHERE created_at > NOW() - INTERVAL '30 days'\n),\nuser_order_counts AS (\n    SELECT user_id, COUNT(*) as order_count\n    FROM orders\n    WHERE created_at > NOW() - INTERVAL '30 days'\n    GROUP BY user_id\n)\nSELECT ru.name, ru.email, COALESCE(uoc.order_count, 0) as orders\nFROM recent_users ru\nLEFT JOIN user_order_counts uoc ON ru.id = uoc.user_id;\n```\n\n### Pattern 5: Batch Operations\n\n**Batch INSERT:**\n\n```sql\n-- Bad: Multiple individual inserts\nINSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');\nINSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');\nINSERT INTO users (name, email) VALUES ('Carol', 'carol@example.com');\n\n-- Good: Batch insert\nINSERT INTO users (name, email) VALUES\n    ('Alice', 'alice@example.com'),\n    ('Bob', 'bob@example.com'),\n    ('Carol', 'carol@example.com');\n\n-- Better: Use COPY for bulk inserts (PostgreSQL)\nCOPY users (name, email) FROM '\u002Ftmp\u002Fusers.csv' CSV HEADER;\n```\n\n**Batch UPDATE:**\n\n```sql\n-- Bad: Update in loop\nUPDATE users SET status = 'active' WHERE id = 1;\nUPDATE users SET status = 'active' WHERE id = 2;\n-- ... repeat for many IDs\n\n-- Good: Single UPDATE with IN clause\nUPDATE users\nSET status = 'active'\nWHERE id IN (1, 2, 3, 4, 5, ...);\n\n-- Better: Use temporary table for large batches\nCREATE TEMP TABLE temp_user_updates (id INT, new_status VARCHAR);\nINSERT INTO temp_user_updates VALUES (1, 'active'), (2, 'active'), ...;\n\nUPDATE users u\nSET status = t.new_status\nFROM temp_user_updates t\nWHERE u.id = t.id;\n```\n\n## Advanced Techniques\n\n### Materialized Views\n\nPre-compute expensive queries.\n\n```sql\n-- Create materialized view\nCREATE MATERIALIZED VIEW user_order_summary AS\nSELECT\n    u.id,\n    u.name,\n    COUNT(o.id) as total_orders,\n    SUM(o.total) as total_spent,\n    MAX(o.created_at) as last_order_date\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id\nGROUP BY u.id, u.name;\n\n-- Add index to materialized view\nCREATE INDEX idx_user_summary_spent ON user_order_summary(total_spent DESC);\n\n-- Refresh materialized view\nREFRESH MATERIALIZED VIEW user_order_summary;\n\n-- Concurrent refresh (PostgreSQL)\nREFRESH MATERIALIZED VIEW CONCURRENTLY user_order_summary;\n\n-- Query materialized view (very fast)\nSELECT * FROM user_order_summary\nWHERE total_spent > 1000\nORDER BY total_spent DESC;\n```\n\n### Partitioning\n\nSplit large tables for better performance.\n\n```sql\n-- Range partitioning by date (PostgreSQL)\nCREATE TABLE orders (\n    id SERIAL,\n    user_id INT,\n    total DECIMAL,\n    created_at TIMESTAMP\n) PARTITION BY RANGE (created_at);\n\n-- Create partitions\nCREATE TABLE orders_2024_q1 PARTITION OF orders\n    FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');\n\nCREATE TABLE orders_2024_q2 PARTITION OF orders\n    FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');\n\n-- Queries automatically use appropriate partition\nSELECT * FROM orders\nWHERE created_at BETWEEN '2024-02-01' AND '2024-02-28';\n-- Only scans orders_2024_q1 partition\n```\n\n### Query Hints and Optimization\n\n```sql\n-- Force index usage (MySQL)\nSELECT * FROM users\nUSE INDEX (idx_users_email)\nWHERE email = 'user@example.com';\n\n-- Parallel query (PostgreSQL)\nSET max_parallel_workers_per_gather = 4;\nSELECT * FROM large_table WHERE condition;\n\n-- Join hints (PostgreSQL)\nSET enable_nestloop = OFF;  -- Force hash or merge join\n```\n\n## Best Practices\n\n1. **Index Selectively**: Too many indexes slow down writes\n2. **Monitor Query Performance**: Use slow query logs\n3. **Keep Statistics Updated**: Run ANALYZE regularly\n4. **Use Appropriate Data Types**: Smaller types = better performance\n5. **Normalize Thoughtfully**: Balance normalization vs performance\n6. **Cache Frequently Accessed Data**: Use application-level caching\n7. **Connection Pooling**: Reuse database connections\n8. **Regular Maintenance**: VACUUM, ANALYZE, rebuild indexes\n\n```sql\n-- Update statistics\nANALYZE users;\nANALYZE VERBOSE orders;\n\n-- Vacuum (PostgreSQL)\nVACUUM ANALYZE users;\nVACUUM FULL users;  -- Reclaim space (locks table)\n\n-- Reindex\nREINDEX INDEX idx_users_email;\nREINDEX TABLE users;\n```\n\n## Common Pitfalls\n\n- **Over-Indexing**: Each index slows down INSERT\u002FUPDATE\u002FDELETE\n- **Unused Indexes**: Waste space and slow writes\n- **Missing Indexes**: Slow queries, full table scans\n- **Implicit Type Conversion**: Prevents index usage\n- **OR Conditions**: Can't use indexes efficiently\n- **LIKE with Leading Wildcard**: `LIKE '%abc'` can't use index\n- **Function in WHERE**: Prevents index usage unless functional index exists\n\n## Monitoring Queries\n\n```sql\n-- Find slow queries (PostgreSQL)\nSELECT query, calls, total_time, mean_time\nFROM pg_stat_statements\nORDER BY mean_time DESC\nLIMIT 10;\n\n-- Find missing indexes (PostgreSQL)\nSELECT\n    schemaname,\n    tablename,\n    seq_scan,\n    seq_tup_read,\n    idx_scan,\n    seq_tup_read \u002F seq_scan AS avg_seq_tup_read\nFROM pg_stat_user_tables\nWHERE seq_scan > 0\nORDER BY seq_tup_read DESC\nLIMIT 10;\n\n-- Find unused indexes (PostgreSQL)\nSELECT\n    schemaname,\n    tablename,\n    indexname,\n    idx_scan,\n    idx_tup_read,\n    idx_tup_fetch\nFROM pg_stat_user_indexes\nWHERE idx_scan = 0\nORDER BY pg_relation_size(indexrelid) DESC;\n```",{"name":5,"description":7},"main","c997e19776ffa338d4936e0fd166e9f91d2e3fbf","skills\u002Fbackend\u002Fsql-optimization-patterns","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro","https:\u002F\u002Fgithub.com\u002Fjest-won","2026-03-26 09:53:02",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add jest-won\u002Farchi-ro --skill sql-optimization-patterns",{"tool":65,"command":66},"bunx","bunx skills add jest-won\u002Farchi-ro --skill sql-optimization-patterns",{"tool":68,"command":69},"pnpm","pnpm dlx skills add jest-won\u002Farchi-ro --skill sql-optimization-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fbackend\u002Fsql-optimization-patterns\u002FSKILL.md",true,[],[74,99,128,147,155,170],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":83,"publishedAt":48},"01M6J5G7JAFYK6EF7KCYNTRSFC","playwright","Browser automation for E2E testing, web scraping, and UI testing. Use when writing E2E tests, automating browser interactions, or validating UI.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Ffrontend\u002Fplaywright\u002FSKILL.md",[80],{"categoryNo":81,"categoryName":82,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量",[76,84,85,86,87,33,88,89,90,91,92,93,94,95,96,97,98],"web","for","tests","ui testing","scraping","web scraping","e2e","ui","browser","e2e tests","e2e testing","automation","browser automation","writing","testing",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":108,"publishedAt":48},"01M6J5G3T5S6QM0S0XP74SWZPT","workflow-orchestrator","Use this agent when you need to design, implement, or optimize complex business process workflows with multiple states, error handling, and transaction management.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fcore\u002Fworkflow-orchestrator\u002FSKILL.md",[105],{"categoryNo":106,"categoryName":107,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[109,110,111,112,33,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127],"workflow orchestrator","workflow","orchestrator","read","process","business","edit","write","error","grep","error handling","workflows","implement","design","optimize","you","with","states","transaction",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":48},"01M6J5G0DBNSXSMFVTVFG3BVA2","ultrawork","Use for maximum-precision execution with strict explore-plan-execute-verify loops. Trigger when the user starts their message with 'ultrawork' or 'ult', or explicitly asks for autonomous execute-verify-fix behavior.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fcore\u002Fultrawork\u002FSKILL.md",[],[130,85,125,135,136,137,138,33,139,140,141,142,143,144,145,146],"fix","verify","trigger","message","execute","autonomous","user","behavior","plan","explore","loops","execution",{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":154,"publishedAt":48},"01M6J5FZ7W1B300ZQT6D0YW830","test","테스트 라고 하면 작동.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fcore\u002Ftest\u002FSKILL.md",[153],{"categoryNo":81,"categoryName":82,"taxonomyType":20,"taxonomyTypeDesc":21},[149],{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":161,"publishedAt":48},"01M6J5FTTBHVGPY85YMNNVN3MT","task-distributor","Use when distributing tasks across multiple agents or workers, managing queues, and balancing workloads to maximize throughput while respecting priorities and deadlines.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fcore\u002Ftask-distributor\u002FSKILL.md",[],[162,163,164,165,118,166,167,168,169,33,115,116,112],"task","task distributor","haiku","tasks","throughput","across","priorities","workers",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":48},"01M6J5FQ3FKJCXZJFK037BESWY","performance-monitor","Use when establishing observability infrastructure to track system metrics, detect performance anomalies, and optimize resource usage across multi-agent environments.","https:\u002F\u002Fgithub.com\u002Fjest-won\u002Farchi-ro\u002Fblob\u002Fmain\u002Fskills\u002Fcore\u002Fperformance-monitor\u002FSKILL.md",[],[177,41,178,115,179,180,181,182,167,123,33,116,112,118,164,183,184,185,186,187],"monitor","performance monitor","usage","environments","multi","detect","multi-agent","system","observability","metrics","infrastructure"]