Claude Skill

sql-ops

Quick reference for common SQL patterns, CTEs, window functions, and indexing strategies. Triggers on: sql patterns, cte example, window functions, sql join, index strategy, pagination sql.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download 0xdarkmatter-claude-mods-skills_sql-ops-3dfaf0b.zip · 5 KB
Part of 0xdarkmatter/claude-mods — 94 skills

Install

skills CLI npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/sql-ops
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git git clone https://github.com/0xDarkMatter/claude-mods.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SQL Patterns

Quick reference for common SQL patterns.

CTE (Common Table Expressions)

WITH active_users AS (
    SELECT id, name, email
    FROM users
    WHERE status = 'active'
)
SELECT * FROM active_users WHERE created_at > '2024-01-01';

Chained CTEs

WITH
    active_users AS (
        SELECT id, name FROM users WHERE status = 'active'
    ),
    user_orders AS (
        SELECT user_id, COUNT(*) as order_count
        FROM orders GROUP BY user_id
    )
SELECT u.name, COALESCE(o.order_count, 0) as orders
FROM active_users u
LEFT JOIN user_orders o ON u.id = o.user_id;

Window Functions (Quick Reference)

Function Use
ROW_NUMBER() Unique sequential numbering
RANK() Rank with gaps (1, 2, 2, 4)
DENSE_RANK() Rank without gaps (1, 2, 2, 3)
LAG(col, n) Previous row value
LEAD(col, n) Next row value
SUM() OVER Running total
AVG() OVER Moving average
SELECT
    date,
    revenue,
    LAG(revenue, 1) OVER (ORDER BY date) as prev_day,
    SUM(revenue) OVER (ORDER BY date) as running_total
FROM daily_sales;

JOIN Reference

Type Returns
INNER JOIN Only matching rows
LEFT JOIN All left + matching right
RIGHT JOIN All right + matching left
FULL JOIN All rows, NULL where no match

Pagination

-- OFFSET/LIMIT (simple, slow for large offsets)
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40;

-- Keyset (fast, scalable)
SELECT * FROM products WHERE id > 42 ORDER BY id LIMIT 20;

Transaction Isolation Levels

Level Dirty Read Non-Repeatable Read Phantom Read Use
READ UNCOMMITTED Possible Possible Possible Rarely (PostgreSQL treats as READ COMMITTED)
READ COMMITTED No Possible Possible Default in most databases
REPEATABLE READ No No Possible* Consistent multi-statement reads
SERIALIZABLE No No No Critical invariants (retry on serialization failure)

*PostgreSQL's REPEATABLE READ also prevents phantoms via snapshot isolation.

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- ... critical operations ...
COMMIT;  -- be prepared to retry on serialization failure

Keep the default READ COMMITTED globally; raise the level per-transaction only where the logic requires it.

Index Quick Reference

Index Type Best For
B-tree Range queries, ORDER BY
Hash Exact equality only
GIN Arrays, JSONB, full-text
Covering Avoid table lookup

Anti-Patterns

Mistake Fix
SELECT * List columns explicitly
WHERE YEAR(date) = 2024 WHERE date >= '2024-01-01'
NOT IN with NULLs Use NOT EXISTS
N+1 queries Use JOIN or batch

Additional Resources

For detailed patterns, load:

  • ./references/window-functions.md - Complete window function patterns
  • ./references/indexing-strategies.md - Index types, covering indexes, optimization
Files (claude-mods)
  • assets
    • .gitkeep 0 B · in bundle
  • references
    • indexing-strategies.md 3.9 KB
      # SQL Indexing Strategies
      
      Vendor-neutral indexing fundamentals. For PostgreSQL-specific index types (GIN, GiST, BRIN, Hash, partial, expression indexes), see `postgres-ops/references/indexing.md`.
      
      ## B-Tree (Default)
      
      The standard index type across all major databases. Best for: equality, range queries, ORDER BY, prefix LIKE.
      
      ```sql
      -- Standard index
      CREATE INDEX idx_users_email ON users(email);
      
      -- Unique index
      CREATE UNIQUE INDEX idx_users_email ON users(email);
      
      -- Works well for:
      WHERE email = 'x@y.com'           -- equality
      WHERE email LIKE 'john%'          -- prefix search
      WHERE created_at > '2024-01-01'   -- range
      ORDER BY created_at               -- sorting
      ```
      
      ## Composite Indexes
      
      ### Column Order Matters
      
      ```sql
      -- Leftmost prefix rule
      CREATE INDEX idx_orders ON orders(user_id, status, created_at);
      
      -- This index supports:
      WHERE user_id = 123                              -- yes
      WHERE user_id = 123 AND status = 'pending'       -- yes
      WHERE user_id = 123 AND status = 'pending'
        AND created_at > '2024-01-01'                  -- yes
      WHERE user_id = 123
        AND created_at > '2024-01-01'                  -- partial (user_id only)
      WHERE status = 'pending'                          -- no (user_id not present)
      ```
      
      ### Optimal Column Order
      
      ```sql
      -- Rule: equality columns first, then range columns
      -- Most selective equality column first when multiple equalities
      
      -- If filtering by status (equality) and date range:
      CREATE INDEX idx_orders_status_date ON orders(status, created_at);
      
      -- If user_id is more selective than status:
      CREATE INDEX idx_orders_user_status_date ON orders(user_id, status, created_at);
      ```
      
      ## Covering Indexes
      
      Include extra columns to avoid table lookup (index-only scan):
      
      ```sql
      -- Query needs name but filters by email
      SELECT name FROM users WHERE email = 'x@y.com';
      
      -- Covering index (PostgreSQL INCLUDE, SQL Server INCLUDE)
      CREATE INDEX idx_users_email_name ON users(email) INCLUDE (name);
      
      -- Now the query uses index-only scan (no table access needed)
      ```
      
      ### When to Use
      
      ```sql
      -- Frequently accessed columns in SELECT
      CREATE INDEX idx_orders_status ON orders(status)
      INCLUDE (total, created_at);
      
      -- Supports without table access:
      SELECT total, created_at FROM orders WHERE status = 'pending';
      ```
      
      ## Query Analysis with EXPLAIN
      
      ```sql
      -- Basic plan
      EXPLAIN SELECT * FROM users WHERE email = 'x@y.com';
      
      -- Key scan types to look for:
      -- Seq Scan       - Full table scan (bad for large tables)
      -- Index Scan     - Using index, then fetching rows
      -- Index Only Scan - Using covering index (best)
      -- Bitmap Scan    - Multiple index conditions combined
      ```
      
      ```sql
      -- With actual execution metrics
      EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending';
      
      -- Shows:
      -- Planning Time: 0.5 ms
      -- Execution Time: 12.3 ms
      -- actual rows vs estimated rows (mismatch = stale statistics)
      ```
      
      ## Anti-Patterns
      
      | Mistake | Why | Fix |
      |---------|-----|-----|
      | Function on indexed column | Prevents index use | Expression index or rewrite query |
      | `WHERE col LIKE '%text%'` | Leading wildcard, no B-tree match | Full-text search or trigram index |
      | `OR` across different columns | May skip index | Rewrite as `UNION ALL` |
      | Over-indexing | Slows writes, wastes space | Audit unused indexes regularly |
      | Missing index on FK column | Slow cascading deletes, slow joins | Add B-tree on FK columns |
      
      ## Quick Reference
      
      | Scenario | Index Strategy |
      |----------|---------------|
      | Equality lookup | B-tree on column |
      | Range queries | B-tree on column |
      | Multiple conditions | Composite (equality first, range last) |
      | Avoid table access | Covering index with INCLUDE |
      | Case-insensitive | Expression index on LOWER() |
      | Full-text search | Database-specific (GIN in PostgreSQL) |
      
      ## See Also
      
      - **PostgreSQL-specific**: `postgres-ops/references/indexing.md` - GIN, GiST, BRIN, Hash, partial, expression indexes
      - **SQLite-specific**: `sqlite-ops` - SQLite indexing considerations
      
    • window-functions.md 6.9 KB
      # SQL Window Functions
      
      Complete reference for window functions and analytical queries.
      
      ## Core Window Functions
      
      ### ROW_NUMBER
      
      Assigns unique sequential numbers within partition:
      
      ```sql
      -- Rank employees by salary within department
      SELECT
          name,
          department,
          salary,
          ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank
      FROM employees;
      
      -- Get top N per group
      WITH ranked AS (
          SELECT *,
              ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rn
          FROM employees
      )
      SELECT * FROM ranked WHERE rn <= 3;
      ```
      
      ### RANK and DENSE_RANK
      
      Handle ties differently:
      
      ```sql
      -- RANK: 1, 2, 2, 4 (skips after ties)
      -- DENSE_RANK: 1, 2, 2, 3 (no skip)
      SELECT
          name,
          score,
          RANK() OVER (ORDER BY score DESC) as rank,
          DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank
      FROM contestants;
      
      -- Result for scores [100, 95, 95, 90]:
      -- name    score   rank   dense_rank
      -- Alice   100     1      1
      -- Bob     95      2      2
      -- Carol   95      2      2
      -- Dave    90      4      3
      ```
      
      ### NTILE
      
      Divide into N equal groups:
      
      ```sql
      -- Divide into quartiles
      SELECT
          name,
          salary,
          NTILE(4) OVER (ORDER BY salary) as quartile
      FROM employees;
      
      -- Percentile buckets
      SELECT
          name,
          score,
          NTILE(100) OVER (ORDER BY score) as percentile
      FROM students;
      ```
      
      ## Navigation Functions
      
      ### LAG and LEAD
      
      Access previous/next rows:
      
      ```sql
      -- Previous and next day revenue
      SELECT
          date,
          revenue,
          LAG(revenue, 1) OVER (ORDER BY date) as prev_day,
          LEAD(revenue, 1) OVER (ORDER BY date) as next_day,
          revenue - LAG(revenue, 1) OVER (ORDER BY date) as day_change
      FROM daily_sales;
      
      -- With default value for first/last
      SELECT
          date,
          revenue,
          LAG(revenue, 1, 0) OVER (ORDER BY date) as prev_or_zero
      FROM daily_sales;
      
      -- Multiple periods back
      SELECT
          date,
          revenue,
          LAG(revenue, 7) OVER (ORDER BY date) as same_day_last_week
      FROM daily_sales;
      ```
      
      ### FIRST_VALUE and LAST_VALUE
      
      Get first/last value in window:
      
      ```sql
      -- Compare to first sale of month
      SELECT
          date,
          revenue,
          FIRST_VALUE(revenue) OVER (
              PARTITION BY DATE_TRUNC('month', date)
              ORDER BY date
          ) as first_day_revenue,
          revenue - FIRST_VALUE(revenue) OVER (
              PARTITION BY DATE_TRUNC('month', date)
              ORDER BY date
          ) as diff_from_first
      FROM daily_sales;
      
      -- Note: LAST_VALUE needs explicit frame
      SELECT
          date,
          revenue,
          LAST_VALUE(revenue) OVER (
              ORDER BY date
              ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
          ) as last_revenue
      FROM daily_sales;
      ```
      
      ### NTH_VALUE
      
      Get Nth value in window:
      
      ```sql
      -- Get 2nd highest salary per department
      SELECT
          department,
          name,
          salary,
          NTH_VALUE(salary, 2) OVER (
              PARTITION BY department
              ORDER BY salary DESC
              ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
          ) as second_highest
      FROM employees;
      ```
      
      ## Aggregate Window Functions
      
      ### Running Totals
      
      ```sql
      -- Running total
      SELECT
          date,
          amount,
          SUM(amount) OVER (ORDER BY date) as running_total
      FROM transactions;
      
      -- Running total by category
      SELECT
          date,
          category,
          amount,
          SUM(amount) OVER (
              PARTITION BY category
              ORDER BY date
          ) as category_running_total
      FROM transactions;
      ```
      
      ### Moving Averages
      
      ```sql
      -- 7-day moving average
      SELECT
          date,
          value,
          AVG(value) OVER (
              ORDER BY date
              ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
          ) as moving_avg_7day
      FROM metrics;
      
      -- Centered moving average
      SELECT
          date,
          value,
          AVG(value) OVER (
              ORDER BY date
              ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
          ) as centered_avg
      FROM metrics;
      ```
      
      ### Cumulative Statistics
      
      ```sql
      -- Running count, sum, avg, min, max
      SELECT
          date,
          revenue,
          COUNT(*) OVER (ORDER BY date) as cumulative_count,
          SUM(revenue) OVER (ORDER BY date) as cumulative_sum,
          AVG(revenue) OVER (ORDER BY date) as cumulative_avg,
          MIN(revenue) OVER (ORDER BY date) as cumulative_min,
          MAX(revenue) OVER (ORDER BY date) as cumulative_max
      FROM daily_sales;
      ```
      
      ## Window Frame Specification
      
      ### ROWS vs RANGE
      
      ```sql
      -- ROWS: Physical row count
      SELECT
          date,
          revenue,
          SUM(revenue) OVER (
              ORDER BY date
              ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
          ) as sum_3_rows
      FROM sales;
      
      -- RANGE: Logical value range (careful with duplicates)
      SELECT
          date,
          revenue,
          SUM(revenue) OVER (
              ORDER BY date
              RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
          ) as sum_7_days
      FROM sales;
      ```
      
      ### Frame Boundaries
      
      ```sql
      -- All frames available
      ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING  -- Entire partition
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW          -- From start to here
      ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING          -- From here to end
      ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING                  -- 7 rows centered
      ROWS BETWEEN 6 PRECEDING AND CURRENT ROW                  -- 7 rows trailing
      ```
      
      ## Practical Examples
      
      ### Year-over-Year Comparison
      
      ```sql
      SELECT
          date,
          revenue,
          LAG(revenue, 365) OVER (ORDER BY date) as revenue_year_ago,
          revenue - LAG(revenue, 365) OVER (ORDER BY date) as yoy_change,
          ROUND(100.0 * (revenue - LAG(revenue, 365) OVER (ORDER BY date))
              / NULLIF(LAG(revenue, 365) OVER (ORDER BY date), 0), 2) as yoy_pct
      FROM daily_sales;
      ```
      
      ### Running Percentage of Total
      
      ```sql
      SELECT
          category,
          sales,
          SUM(sales) OVER () as total,
          ROUND(100.0 * sales / SUM(sales) OVER (), 2) as pct_of_total,
          ROUND(100.0 * SUM(sales) OVER (ORDER BY sales DESC)
              / SUM(sales) OVER (), 2) as cumulative_pct
      FROM category_sales;
      ```
      
      ### Session/Gap Detection
      
      ```sql
      -- Find sessions (gaps > 30 minutes = new session)
      WITH events_with_gaps AS (
          SELECT
              *,
              EXTRACT(EPOCH FROM (timestamp - LAG(timestamp) OVER (
                  PARTITION BY user_id ORDER BY timestamp
              ))) / 60 as minutes_since_last
          FROM user_events
      )
      SELECT
          *,
          SUM(CASE WHEN minutes_since_last > 30 OR minutes_since_last IS NULL
              THEN 1 ELSE 0 END) OVER (
              PARTITION BY user_id ORDER BY timestamp
          ) as session_id
      FROM events_with_gaps;
      ```
      
      ### Deduplication with Row Number
      
      ```sql
      -- Keep only the latest record per user
      WITH ranked AS (
          SELECT *,
              ROW_NUMBER() OVER (
                  PARTITION BY user_id
                  ORDER BY updated_at DESC
              ) as rn
          FROM users
      )
      SELECT * FROM ranked WHERE rn = 1;
      ```
      
      ## Performance Tips
      
      1. **Index the ORDER BY column** - Window functions sort data
      2. **Limit partitions** - Large partitions = more memory
      3. **Named windows** - Reuse window definitions
      4. **Avoid nested windows** - Use CTEs instead
      
      ### Named Windows
      
      ```sql
      SELECT
          name,
          department,
          salary,
          ROW_NUMBER() OVER dept_salary as rank,
          AVG(salary) OVER dept_salary as dept_avg,
          salary - AVG(salary) OVER dept_salary as diff_from_avg
      FROM employees
      WINDOW dept_salary AS (PARTITION BY department ORDER BY salary DESC);
      ```
      
  • scripts
    • .gitkeep 0 B · in bundle
  • SKILL.md 3.4 KB
    ---
    name: sql-ops
    description: "Quick reference for common SQL patterns, CTEs, window functions, and indexing strategies. Triggers on: sql patterns, cte example, window functions, sql join, index strategy, pagination sql."
    license: MIT
    allowed-tools: "Read Write"
    metadata:
      author: claude-mods
      related-skills: postgres-ops, sqlite-ops
    ---
    
    # SQL Patterns
    
    Quick reference for common SQL patterns.
    
    ## CTE (Common Table Expressions)
    
    ```sql
    WITH active_users AS (
        SELECT id, name, email
        FROM users
        WHERE status = 'active'
    )
    SELECT * FROM active_users WHERE created_at > '2024-01-01';
    ```
    
    ### Chained CTEs
    
    ```sql
    WITH
        active_users AS (
            SELECT id, name FROM users WHERE status = 'active'
        ),
        user_orders AS (
            SELECT user_id, COUNT(*) as order_count
            FROM orders GROUP BY user_id
        )
    SELECT u.name, COALESCE(o.order_count, 0) as orders
    FROM active_users u
    LEFT JOIN user_orders o ON u.id = o.user_id;
    ```
    
    ## Window Functions (Quick Reference)
    
    | Function | Use |
    |----------|-----|
    | `ROW_NUMBER()` | Unique sequential numbering |
    | `RANK()` | Rank with gaps (1, 2, 2, 4) |
    | `DENSE_RANK()` | Rank without gaps (1, 2, 2, 3) |
    | `LAG(col, n)` | Previous row value |
    | `LEAD(col, n)` | Next row value |
    | `SUM() OVER` | Running total |
    | `AVG() OVER` | Moving average |
    
    ```sql
    SELECT
        date,
        revenue,
        LAG(revenue, 1) OVER (ORDER BY date) as prev_day,
        SUM(revenue) OVER (ORDER BY date) as running_total
    FROM daily_sales;
    ```
    
    ## JOIN Reference
    
    | Type | Returns |
    |------|---------|
    | `INNER JOIN` | Only matching rows |
    | `LEFT JOIN` | All left + matching right |
    | `RIGHT JOIN` | All right + matching left |
    | `FULL JOIN` | All rows, NULL where no match |
    
    ## Pagination
    
    ```sql
    -- OFFSET/LIMIT (simple, slow for large offsets)
    SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 40;
    
    -- Keyset (fast, scalable)
    SELECT * FROM products WHERE id > 42 ORDER BY id LIMIT 20;
    ```
    
    ## Transaction Isolation Levels
    
    | Level | Dirty Read | Non-Repeatable Read | Phantom Read | Use |
    |-------|-----------|--------------------|--------------|-----|
    | `READ UNCOMMITTED` | Possible | Possible | Possible | Rarely (PostgreSQL treats as READ COMMITTED) |
    | `READ COMMITTED` | No | Possible | Possible | Default in most databases |
    | `REPEATABLE READ` | No | No | Possible* | Consistent multi-statement reads |
    | `SERIALIZABLE` | No | No | No | Critical invariants (retry on serialization failure) |
    
    *PostgreSQL's REPEATABLE READ also prevents phantoms via snapshot isolation.
    
    ```sql
    BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    -- ... critical operations ...
    COMMIT;  -- be prepared to retry on serialization failure
    ```
    
    Keep the default `READ COMMITTED` globally; raise the level per-transaction only where the logic requires it.
    
    ## Index Quick Reference
    
    | Index Type | Best For |
    |------------|----------|
    | B-tree | Range queries, ORDER BY |
    | Hash | Exact equality only |
    | GIN | Arrays, JSONB, full-text |
    | Covering | Avoid table lookup |
    
    ## Anti-Patterns
    
    | Mistake | Fix |
    |---------|-----|
    | `SELECT *` | List columns explicitly |
    | `WHERE YEAR(date) = 2024` | `WHERE date >= '2024-01-01'` |
    | `NOT IN` with NULLs | Use `NOT EXISTS` |
    | N+1 queries | Use JOIN or batch |
    
    ## Additional Resources
    
    For detailed patterns, load:
    - `./references/window-functions.md` - Complete window function patterns
    - `./references/indexing-strategies.md` - Index types, covering indexes, optimization
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related