Reputation: 582
I have 3 schemas: Post
, Author
, Assistant
.
Post
belongs_to :author
Author
has_many :posts
and has_one :assistant
Assistant
belongs_to :author
I want to build a query that only gets Post
s where the Author
has an Assistant
.
So far, I've only been able to join the Author
to the Post
. I can't figure out how to query Post
s based on the presence of an Author
's Assistant
.
Post
|> join(:inner, [p], a in assoc(p, :author))
|> Repo.all()
Edit 1
I think I've made some progress and this query seems to be working with the couple of records I have in the db right now, but not sure if it's the conventional way.
Post
|> join(:inner, [p], a in assoc(p, :author), as: :author)
|> join(:left, [author: a], asst in assoc(a, :assistant), as: :assistant)
|> where([assistant: asst], not is_nil(asst.id)))
|> Repo.all()
Upvotes: 2
Views: 2792
Reputation: 2882
I don't think a left join is necessary for this scenario. You should be able to inner join on all of the tables because an inner join will indicate the presence of an assistant and rule out any possibility of getting an author without an assistant. You should be able to write something like this:
query = from(p in Post,
join: a in Author,
on: a.id == p.author_id,
join: ast in Assistant,
on: ast.author_id == a.id,
# See notes below about the distinct
distinct: p.id
)
Repo.all(query)
Because you inner join on all of the tables it indicates that a post must have an author and an author must have an assistant to be a resulting row in this query. If you wanted to do something like figure out all of the posts that have an author with no assistant you would want to left join.
Note about distinct
:
I put the distinct clause in case you have multiple assistants assigned to an author in your database (which would indicate bad data) and posts in the result set would be duplicated. This is because a has_one
relationship is just a constrained has_many
. If you have a unique index on the author_id
column in the assistants
table, you do not need the distinct clause. If you do not have a unique index you can write this in a migration:
create unique_index("assistants", :author_id)
You should probably do this for all of your has_one
relationships.
Cheers!
Upvotes: 3
Reputation: 121000
This might be an overkill in this particular case, but it’s always easier to decouple such queries into subqueries.
authors_with_assistants =
from a in Author, where: not is_nil(a.assistant_id)
or
authors_with_assistants =
from a in Author,
left_join: asst in assoc(a, :assistant)
and
posts =
from p in Post,
join: a in subquery(authors_with_assistants),
on: p.author_id == a.id
Repo.all(posts)
Also, chaining joins with a condition on the latter would also work (meaning your query in Edit1 section looks fine.)
Upvotes: 2