from o in Offer,
left_join: c in Customer, on: o.customer_id == c.id, as: :customer,
left_join: co in Company, on: c.company_id == co.id, as: :company,
where: ^maybe_search(term)
This works fine. However, there’s one (IMO big) problem with it, which is that the original query always performs the two joins (I think?), even in situations where the search term will be empty, which is the vast majority of the time. In my particular case, this is very wasteful.
How can the above be rewritten to eliminate this inefficiency? Ideally using the keyword syntax (i.e. not the pipe operator) because that’s what I use everywhere else in this codebase.
First some general thoughts: The “problem” is not big, and maybe even not a problem at all. If you have indexes in the customer_id and company_id, then for the database the query is nothing. So if you are in general fine with the code, then think again if you want to change it.
Now, having said that, I would still optimize it, because not doing joins is still better than doing them. Queries are composable, so you can do things like
def search(term) do
query = from o in Offer
maybe_search(query, term)
end
defp maybe_search(query, term) when term in [nil, ""], do: query
defp maybe_search(query, term) do
safe_term = strip_unsafe_chars(term)
from o in query,
left_join: c in Customer, on: o.customer_id == c.id, as: :customer,
left_join: co in Company, on: c.company_id == co.id, as: :company,
where: dynamic([company: co], fragment("? ILIKE '%' || ? || '%'", co.name, ^safe_term))
end
I would do it with pipe-based syntax personally, but it doesn’t really matter.
Assuming you always want to join the offer’s associated customers, but only join the companies associated with the customers when there’s a valid search term, you could try something like this.
# Create a query
base_query =
from o in Offer,
left_join: c in Customer, on: o.customer_id == c.id, as: :customer
# Extend the query
maybe_add_company_search(base_query, term)
def maybe_add_company_search(query, ""), do: query
def maybe_add_company_search(query, nil), do: query
def maybe_add_company_search(query, term) do
safe_term = strip_unsafe_chars(term)
query_with_company_search =
from [o, c] in base_query,
left_join: co in Company, on: c.company_id == co.id, as: :company,
where: dynamic([company: co], fragment("? ILIKE '%' || ? || '%'", co.name, ^safe_term))
end
Basically, it’s very similar @egze’s solution which was posted just as I was wrapping up this response, but includes the customer join in the base query since that’s how I interpreted the initial question.