Ecto.Query.join

You're seeing just the macro join, go back to Ecto.Query module for more information.
Link to this macro

join(query, qual, binding \\ [], expr, opts \\ [])

View Source (macro)

A join query expression.

Receives a source that is to be joined to the query and a condition for the join. The join condition can be any expression that evaluates to a boolean value. The qualifier must be one of :inner, :left, :right, :cross, :full, :inner_lateral or :left_lateral.

For a keyword query the :join keyword can be changed to :inner_join, :left_join, :right_join, :cross_join, :full_join, :inner_lateral_join or :left_lateral_join. :join is equivalent to :inner_join.

Currently it is possible to join on:

  • an Ecto.Schema, such as p in Post
  • an interpolated Ecto query with zero or more where clauses, such as c in ^(from "posts", where: [public: true])
  • an association, such as c in assoc(post, :comments)
  • a subquery, such as c in subquery(another_query)
  • a query fragment, such as c in fragment("SOME COMPLEX QUERY"), see "Joining with fragments" below.

Options

Each join accepts the following options:

  • :on - a query expression or keyword list to filter the join
  • :as - a named binding for the join
  • :prefix - the prefix to be used for the join when issuing a database query
  • :hints - a string or a list of strings to be used as database hints

In the keyword query syntax, those options must be given immediately after the join. In the expression syntax, the options are given as the fifth argument.

Keywords examples

from c in Comment,
  join: p in Post,
  on: p.id == c.post_id,
  select: {p.title, c.text}

from p in Post,
  left_join: c in assoc(p, :comments),
  select: {p, c}

Keywords can also be given or interpolated as part of on:

from c in Comment,
  join: p in Post,
  on: [id: c.post_id],
  select: {p.title, c.text}

Any key in on will apply to the currently joined expression.

It is also possible to interpolate an Ecto query on the right-hand side of in. For example, the query above can also be written as:

posts = Post
from c in Comment,
  join: p in ^posts,
  on: [id: c.post_id],
  select: {p.title, c.text}

The above is specially useful to dynamically join on existing queries, for example, to dynamically choose a source, or by choosing between public posts or posts that have been recently published:

posts =
  if params["drafts"] do
    from p in Post, where: [drafts: true]
  else
    from p in Post, where: [public: true]
  end

from c in Comment,
  join: p in ^posts, on: [id: c.post_id],
  select: {p.title, c.text}

Only simple queries with where expressions can be interpolated in a join.

Expressions examples

Comment
|> join(:inner, [c], p in Post, on: c.post_id == p.id)
|> select([c, p], {p.title, c.text})

Post
|> join(:left, [p], c in assoc(p, :comments))
|> select([p, c], {p, c})

Post
|> join(:left, [p], c in Comment, on: c.post_id == p.id and c.is_visible == true)
|> select([p, c], {p, c})

Joining with fragments

When you need to join on a complex query, Ecto supports fragments in joins:

Comment
|> join(:inner, [c], p in fragment("SOME COMPLEX QUERY", c.id, ^some_param))

Although using fragments in joins is discouraged in favor of Ecto Query syntax, they are necessary when writing lateral joins as lateral joins require a subquery that refer to previous bindings:

Game
|> join(:inner_lateral, [g], gs in fragment("SELECT * FROM games_sold AS gs WHERE gs.game_id = ? ORDER BY gs.sold_on LIMIT 2", g.id))
|> select([g, gs], {g.name, gs.sold_on})

Note that the join does not automatically wrap the fragment in parentheses, since some expressions require parens and others require no parens. Therefore, in cases such as common table expressions, you will have to explicitly wrap the fragment content in parens.

Hints

from and join also support index hints, as found in databases such as MySQL and MSSQL. Clickhouse.

For example, a developer using MySQL may write:

from p in Post,
  join: c in Comment,
  hints: ["USE INDEX FOO", "USE INDEX BAR"],
  where: p.id == c.post_id,
  select: c

Keep in mind you want to use hints rarely, so don't forget to read the database disclaimers about such functionality.

Hints must be static compile-time strings when they are specified as (list of) strings. Certain Ecto adapters may also accept dynamic hints using the tuple form:

from e in Event,
  hints: [sample: sample_threshold()],
  select: e