Ecto:动态 order_by 对虚拟字段进行计数查询

Ecto: Dynamic order_by with count query on virtual field

我想通过处理构建具有动态顺序的查询。 基本排序示例 id + name(见下文)工作正常。排序变量插入到查询中。

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)

此外,我想对计数字段进行排序 (favorites_count & downloads_count)。在查询中将此部分静态化即可。

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

我现在的问题是如何像第一个使用动态变量的例子那样处理这个问题。我没有找到处理这个问题的方法。

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

最后是 Unknown column 'w0.favorites_count' in 'order clause'

order_by: ^foo(sort),

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

最后是 undefined function coalesce/2 (fragment/3)

使用 dynamic/2 最终得到 expected a field as an atom, a list or keyword list in 'order_by', got: 'dynamic(...)'

我了解主要问题,但没有得到解决方案。任何想法或最好的解决方案?感谢您的帮助!

解决办法是调用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

我成功地做到了这一点,所以这是我的解决方案:

from(p in Post,
  order_by: [desc_nulls_last: fragment("array_length(?, 1)", p.votes)]
)
|> Repo.all()

desc_nulls_last你可以自己选择,但是片段效果很好。 array_length 是 PostgreSQL 特有的东西,所以要小心!