Skip to content
Back to skills

Ecto Query Analysis

ASecurity

Analyzes Ecto queries for N+1 problems, missing preloads, and performance issues.

  • 666 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 8, 2026
databasesgobashsqldatabaseperformance

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill ecto-query-analysis --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ecto Query Analysis?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ecto Query Analysis
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-ecto-query-analysis/badge)](https://www.skillsdirectory.com/skills/majiayu000-ecto-query-analysis)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ecto-query-analysis
description: Analyzes Ecto queries for N+1 problems, missing preloads, and performance issues.
---

# Ecto Query Analysis Skill

Use this skill to analyze Ecto queries for performance issues and optimization opportunities.

## When to Use

- Reviewing Ecto query code
- Investigating slow database queries
- Optimizing database access patterns
- Designing database schemas for performance

## Common Issues

### N+1 Query Problem

**Symptom**: Multiple database queries executed in a loop to fetch associated data.

**Example**:
```elixir
# ❌ Bad - N+1 query problem
def get_users_with_posts do
  users = Repo.all(User)
  Enum.map(users, fn user ->
    posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
    %{user: user, posts: posts}
  end)
end

# ✅ Good - Preload associations
def get_users_with_posts do
  User
  |> preload([:posts])
  |> Repo.all()
end
```

### Missing Indexes

**Symptom**: Frequent queries on non-indexed columns are slow.

**Example**:
```elixir
# ❌ Bad - No index on frequently queried column
# Query: WHERE email = '...' on large table
# Result: Slow sequential scan

# ✅ Good - Add index
# CREATE INDEX users_email_idx ON users(email)
# Query becomes fast index scan
```

### Large Result Sets

**Symptom**: Loading all records into memory unnecessarily.

**Example**:
```elixir
# ❌ Bad - Loading all records
def list_users, do: Repo.all(User)

# ✅ Good - Pagination
def list_users(page, per_page \\ 20) do
  User
  |> limit(^per_page)
  |> offset((page - 1) * ^per_page)
  |> Repo.all()
end
```

## Optimization Strategies

### Preloading

**Associations**: Always preload associations to prevent N+1 queries.

```elixir
# Single association
User |> preload([:posts]) |> Repo.one()

# Multiple associations
User |> preload([:posts, :profile, :settings]) |> Repo.one()

# Nested associations
User |> preload([profile: [:avatar, [:background]]) |> Repo.one()
```

### Selective Preloading

**Only Load Needed Fields**:
```elixir
# Instead of preload(:posts) which loads all fields
User
|> Ash.Query.for_read()
|> Ash.Query.load([:posts, published_posts: [:author]])
|> Ash.Query.filter(posts[:published] == true)
|> Ash.read!()
```

### Query Optimization

**Use Ash Aggregates**:
```elixir
# Instead of loading all posts then counting
def count_published_posts(user_id) do
  Post
  |> Ash.Query.aggregate([:count], :first)
  |> Ash.Query.filter(author_id == ^user_id)
  |> Ash.Query.filter(status == :published)
  |> Ash.read_one!()
end
```

**Use Window Functions**:
```elixir
# Calculate stats efficiently
def get_user_stats(user_id) do
  stats = User
  |> Ash.Query.aggregate([:count, :max_age], :first)
  |> Ash.Query.filter(id == ^user_id)
  |> Ash.read_one!()
  stats
end
```

### Indexing Strategy

**Composite Index**: Index multiple columns often queried together.

```elixir
# For queries filtering by user_id and status
CREATE INDEX posts_user_id_status_idx ON posts(user_id, status)
```

**Partial Index**: Index on prefix for range queries.

```elixir
# For queries filtering by email LIKE 'user%'
CREATE INDEX users_email_prefix_idx ON users(email text_pattern_ops)
```

## Commands to Run

```bash
# Enable query logging
# In config/dev.exs:
config :my_app, MyApp.Repo,
  loggers: [{Ecto.LogEntry, :log, :info}],
  log_sql_queries: true

# Analyze query plans
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

# Check for missing indexes
# In IEx:
Ecto.Adapters.SQL.explain(MyApp.Repo, "EXPLAIN SELECT * FROM users")
```

## Best Practices

### Do

- Always preload associations
- Use selective preloading
- Use aggregates for efficient calculations
- Add indexes on frequently queried columns
- Use pagination for large result sets
- Filter at database level, not in Elixir

### Don't

- Enumerate over associations (N+1 problem)
- Load entire result sets into memory
- Use SELECT * when you only need specific columns
- Ignore query performance warnings
- Skip adding indexes on slow queries

---

## Tools

- **mgrep**: Search for N+1 patterns: `mgrep "N+1 query problems in codebase"`
- **Serena**: Analyze codebase for query optimization opportunities
- **Credo**: Check for code smells in database access code

---

**Use this skill to identify and fix Ecto performance issues.**

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.