While approach used by @fuelen will work, it is far from optimal one. The best would be using NOT EXISTS, but Ecto do not support it directly, so we need to fallback to fragment:
query =
from u in User, where: fragment("NOT EXISTS (SELECT * FROM office_hours h WHERE h.user_id = ?)", u.id)
This would allow query planner to optimise this query as much as possible.
Thanks for the reply, @fuelen! Could you possibly link the relevant docs? I see exists/1 but nothing that corresponds to “NOT EXISTS” as far as I can tell.
If you try to do comparisons with nil in a query like “x.id == nil” it will tell you to use is_nil in the error so I assume thats the “correct” method.
You can also externalise things with dynamics like below. I needed to get tag IDs from a list, so “t.id in ^tags” but you can’t use is_nil or == nil for lists as far as I’m aware. So I used dynamic to externalize the condition where the list was empty.
def list_user_posts(current_user, user, params) do
tag_condition =
if tags !== nil do
dynamic([_p, _pt, t], t.id in ^tags)
else
dynamic([p, pt, t], (not is_nil(t.id) or t.id > 0))
end
Repo.all(
from p in Post,
left_join: pt in Post_Tag, on: p.id == pt.post_id,
left_join: t in Tag, on: t.id == pt.tag_id,
where: p.user_id == ^user.id
and p.inserted_at >= ^time_period
and fragment("lower(?) like lower(?)", p.title, ^"%#{query}%"),
where: ^tag_condition,
order_by: ^order_by,
distinct: true,
preload: [:user, :board, :comments, :tags]
)
end
@fuelen nevermind! I think I figured it out. I didn’t consider that you could just use not/1 with exists/1 in this way, but the docs seem to suggest that you can: Ecto.Query.API — Ecto v3.11.0
So I think I should be all good. Thanks again for the reply earlier!