Skip to content

Using named bindings and has_named_binding?

Joel Meador edited this page Jun 8, 2026 · 1 revision

How can I make it easier to keep track of my bindings when doing multiple joins in queries? I lose track of which bindings refer to which tables and having to bring the positional bindings along in every future join or where clause causes even more confusion.

I'd also like a way to check if a query has already included a join or if I should be doing the join myself in my function.

Named bindings in Ecto.Query are a great way to make managing your bindings more explicit and readable.

https://hexdocs.pm/ecto/Ecto.Query.html#join/5

Using the as: keyword option in a join you are able to set a name for the table in a join to something that you can later reference in other types of clauses.

Here is an example using your standard positional bindings

query =
  User
  |> join(:inner, [u], c in assoc(u, cats))
  |> join(:inner, [u, c], b in assoc(c, beds))
  |> where([u, c, b], b.texture == "Fleece")

Using positional binding as above you can sometimes run into an error if you forget a binding and Elixir gets confused as to which you're referencing. If you attempted to do a where clause leaving out one of the positional bindings it may complain that it doesn't know of an attribute you tried to use because you're referencing the wrong table

|> where([u, b], b.texture == "Sand")

May complain that column texture doesn't exist because it thinks you are talking about cats and not beds since you left out a binding for cats (bad example since cats do have texture in reality but let's say we aren't tracking that here).

Here is the example again using named bindings

query =
  User
  |> join(:inner, [u], c in assoc(u, cats), as: :cats)
  |> join(:inner, [u, c], b in assoc(c, beds), as: :beds)
  |> where([u, beds: b], b.texture == "Fleece")

See in this example we are setting the names :cats and :beds for the bindings using as:. We can then reference the :beds binding directly using beds: b in the where clause afterward.

Named bindings also give us the capability to check if a query already contains a join through the use of has_named_binding? before potentially trying to do that join again and causing problems or excessive queries.

The syntax for has_named_binding?(queryable, key) is pretty simple. You pass in the query as well as the key of the named binding you want to check and it will return true or false

https://hexdocs.pm/ecto/Ecto.Query.html#has_named_binding?/2

For our above query has_named_binding?(query, :cats) would return true while has_named_binding?(query, :dogs) would return false.

One application of this would be writing a function that you can pipe a query to that would conditionally attempt a join if it didn't already exist

def maybe_join_dogs(query) do
  case Ecto.Query.has_named_binding?(query, :dogs) do
    true -> query
    false -> join(query, :inner, [u], d in assoc(u, :dogs), as :dogs)
  end
end

This will join dogs to the user query we passed in if a named binding of :dogs hasn't already been attached and will just pass the query through untouched otherwise.

This does assume you are consistent in how you name your bindings throughout the application so try to have some kind of standard for that. Naming them the same thing as the table you're joining is probably a good place to start.

Home

TODO: can we recreate the tag system here roughly with sections about certain subjects?

Clone this wiki locally