Search code examples
elixirecto

Ecto: Dynamic order_by with count query on virtual field


I want to build a query with dynamic order by handling. The basic sorting example id + name (see below) works fine. The sort variable is inserted in query.

favorites_count_query = from(f in Favorite,
  select: %{wallpaper_id: f.wallpaper_id, count: count(f.user_id)},
  group_by: f.wallpaper_id)

...

sort = [asc: :id, asc: :name] # comes from request

from(w in Wallpaper,
  left_join: f in subquery(favorites_count_query), on: f.wallpaper_id == w.id,
  left_join: d in subquery(downloads_count_query), on: d.wallpaper_id == w.id,
  select: w,
  select_merge: %{favorites_count: coalesce(f.count, 0)},
  select_merge: %{downloads_count: coalesce(d.count, 0)},
  order_by: ^sort,
  group_by: w.id)

In addition I want to sort on count fields (favorites_count & downloads_count). Put this part static in query will work.

...
order_by: coalesce(d.count, 0),

My question now is how to handle this like the first example with dynamic variable. I didn't find a way to handle this.

sort = [asc: :favorites_count, asc: :name] # comes from request
...
order_by: ^sort,

Ends up with Unknown column 'w0.favorites_count' in 'order clause'

order_by: ^foo(sort),

def foo(sort), do: coalesce(:count, 0)

Ends up with undefined function coalesce/2 (fragment/3)

Using dynamic/2 ends up with expected a field as an atom, a list or keyword list in 'order_by', got: 'dynamic(...)'

I understand the main problem, but didn't get a solution. Any ideas or in best case solutions? Thanks for help!


Solution

  • The solution is call order_by/3 as function multiple times instead of generate list and put them directly in from/2.

    sort = [asc: :favorites_count, asc: :name] # comes from request
    
    from(w in Wallpaper, ...)
    |> order_wallpapers(sort)
    
    ...
    
    defp order_wallpapers(query, []), do: query
    
    defp order_wallpapers(query, [{order, :favorites_count} = sort|others]) do
      query
      |> order_by([w, f, d], [{^order, f.count}])
      |> order_wallpapers(others)
    end
    
    defp order_wallpapers(query, [sort|others]) do
      query
      |> order_by([w, f, d], ^[sort])
      |> order_wallpapers(others)
    end