You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<pclass="text-smurf-blood">Posted on 2024-11-15</p>
26
+
<p>
27
+
I recently had <ahref="https://twitter.com/andyleclair/status/1857112936101134588">this exchange</a> on Twitter about using Ecto’s <codeclass="inline">Repo.preload</code> and I wanted to describe
28
+
the way that we handle this at Appcues. Obviously everyone has their opinions, but this has served us very well for years, and we’ve never posted anything about it!</p>
29
+
<p>
30
+
Hopefully this can help somebody out there.</p>
31
+
<h2>
32
+
The Problem</h2>
33
+
<p>
34
+
So, if you do something like <codeclass="inline">Repo.get(queryable) |> Repo.preload(:association)</code>, you’re going to get a query for the original record, and then a query for each of the associated records. This is the classic N+1 query problem, and it’s not good.</p>
I recently had [this exchange](https://twitter.com/andyleclair/status/1857112936101134588) on Twitter about using Ecto's `Repo.preload` and I wanted to describe
11
+
the way that we handle this at Appcues. Obviously everyone has their opinions, but this has served us very well for years, and we've never posted anything about it!
12
+
13
+
Hopefully this can help somebody out there.
14
+
15
+
## The Problem
16
+
17
+
So, if you do something like `Repo.get(queryable) |> Repo.preload(:association)`, you're going to get a query for the original record, and then a query for each of the associated records. This is the classic N+1 query problem, and it's not good.
18
+
19
+
How do you solve it? More functions!
20
+
21
+
22
+
## The Solution
23
+
24
+
```elixir
25
+
defget_thing(id, opts \\ []) do
26
+
from(t inThing, where: t.id ==^id)
27
+
|>preload(opts[:preload])
28
+
|>Repo.one(query)
29
+
end
30
+
31
+
defpreload(query), do:preload(query, true)
32
+
defpreload(query, nil), do: query
33
+
34
+
defpreload(query, true) do
35
+
from q in query, preload: [
36
+
:association,
37
+
other_assoc: [:sub_assoc]
38
+
]
39
+
end
40
+
41
+
defpreload(query, preloads) do
42
+
from q in query, preload:^preloads
43
+
end
44
+
```
45
+
46
+
If you need to get fancier with it, you can also use a `left_join` and get more specific with your preload conditions.
47
+
This will allow you to do things like adjust the query based on the associations, like if you'd need to sort based on
48
+
say, the index of the association.
49
+
50
+
```elixir
51
+
defpreload(query, true) do
52
+
from q in query,
53
+
left_join: t inassoc(q, :thing),
54
+
left_join: s inassoc(t, :sub_thing),
55
+
preload: [
56
+
thing: {t, [sub_thing: s]}
57
+
],
58
+
order_by: [asc: t.index]
59
+
]
60
+
end
61
+
```
62
+
That's the basic gist of it. I hope this helps someone out there!
0 commit comments