简体   繁体   中英

Ecto Elixir: Sum values in preloaded association

I have a database which has different songs that can have multiple requests and each request can in part have multiple votes.

I'm trying to build a query that gets all of the info for a request (song information and votes made by users) but I just want to select the important information I need.

I currently have it set up like this

def get_requests_in_zone(zone_id, max_played) do
query = from request in Request,
  left_join: song in assoc(request, :song),
  where: request.zone_id == ^zone_id and request.times_played <= ^max_played,
  select: request,
  preload: [song: song],
  order_by:  [asc: request.times_played, desc: request.inserted_at]
Repo.all(query)
 end

def  calculate_rating(request_id)do
query = from votes in Vote,
  where: votes.request_id == ^request_id,
  select: sum(votes.rating)

Repo.one(query)
end

def get_requests_in_zone_with_votes(zone_id,max_played) do
Enum.map(get_requests_in_zone(zone_id,max_played),
  fn(x) ->
    %{title: x.song.title, url: x.song.url, thumbnail: x.song.thumbnail, rating: calculate_rating(x.id)} end)
end

But that makes multiple queries to calculate the rating for each request. I tried building a single query, it was something akin to:

def get_requests_in_zone(zone_id, max_played) do
query = from song in Song,
  left_join: request in assoc(song, :requests),
  join: votes in assoc(request, :votes), 
where: request.zone_id == ^zone_id and request.times_played <= ^max_played,
  select: %{title: song.title, url: song.url, thumbnail: song.thumbnail, rating: sum(votes.rating)},
  preload: [requests: {request, votes: votes}],
  order_by:  [asc: request.times_played, desc: request.inserted_at]
Repo.all(query)
end

But it didn't work... Giving out this error message

** (Ecto.QueryError) field .Song.request in preload is not an association in query:

So I decided to add the complete song in the select by changing the select line to

  select: %{song: song, rating: sum(votes.rating)},

And it just said ERROR 42803 (grouping_error): column "s0.id" must appear in the GROUP BY clause or be used in an aggregate function

How could I build a query that retrieves the requests and returns a struct similar to the one used in get_requests_in_zone_with_votes?

Thanks for you time.

Calculate the sum in a subquery, and join to it from the main query:

q1 = from votes in Vote,
     group_by: votes.request_id,
     select: %{request_id: votes.request_id, total_rating: sum(votes.rating)}

q2 = from request in Request,
     left_join: song in assoc(request, :song),
     left_join: rating in subquery(q2), on: request.id == rating.request_id,
     where: request.zone_id == ^zone_id and request.times_played <= ^max_played,
     select: %{title: song.title, url: song.url, thumbnail: song.thumbnail, rating: rating.total_rating},
     order_by:  [asc: request.times_played, desc: request.inserted_at]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM