Ecto.Query.join
join
, go back to Ecto.Query module for more information.
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 asp in Post
- an interpolated Ecto query with zero or more
where
clauses, such asc 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