Elixir 按虚拟计算字段排序

Elixir 按虚拟计算字段排序,elixir,ecto,Elixir,Ecto,给定以下模式,我想计算:games\u won/:games\u played,将其填充到:percentage\u won并按:percentage\u won排序。如果我使用选择_merge并省去“AS?”,我设法计算了该值,但如何在order_by中引用该计算列 schema "players" do field :name, :string field :games_played, :integer field :games_won, :integer field :p

给定以下模式,我想计算:games\u won/:games\u played,将其填充到:percentage\u won并按:percentage\u won排序。如果我使用
选择_merge
并省去“AS?”,我设法计算了该值,但如何在
order_by
中引用该计算列

schema "players" do
  field :name, :string
  field :games_played, :integer
  field :games_won, :integer

  field :percentage_won, :float, virtual: true

  timestamps()
end
我尝试了以下查询:

def list_players(sort_by, sort_order) do
  query =
    from(p in Player,
      select_merge: %{percentage_won: fragment("(?::decimal / NULLIF(?,0)) AS ?", p.games_won, p.games_played, p.percentage_won)},
      order_by: [{^sort_order, field(p, ^String.to_atom(sort_by))}])
  Repo.all(query)
end
但是调用
list\u players(“percentage\u wind”,:asc)
会产生以下错误:

** (Ecto.QueryError) ...:28: field `percentage_won` in `select` is a virtual field in schema Player in query:

from p0 in Player,
  order_by: [asc: p0.name],
  select: merge(p0, %{percentage_won: fragment("(?::decimal / NULLIF(?,0)) AS ?", p0.games_won, p0.games_played, p0.percentage_won)})
TL;DR

您需要在应用程序中创建另一个Elixir函数来进行排序,使用查询将不起作用

详细解释

Query只创建基于Elixir代码的数据库查询字符串。此查询将在数据库中执行,以便数据库需要知道此查询中指定的所有列

由于虚拟字段只存在于应用程序中,而不存在于数据库中,因此您只能使用Elixir应用程序在此字段中对数据进行排序,如下所示:

def list_players(sort_by, sort_order) do
 # ...

  Repo.all(query)
  |> order_result_manually()
end

Thiago Henrique已经回答了为什么
virtual
在这里不起作用,但根据您的底层数据库,我想提出另一种解决方案:

生成的列从版本12开始在PostgreSQL中可用,并允许创建基于其他列值的列(非常适合您的用例!)。您可以获得数据库的所有优势,并且无需在应用程序层中创建
虚拟
字段

要将其放入数据库,可以编写原始SQL迁移,例如:

def up do
  execute """
    ALTER TABLE players ADD percentage_won numeric GENERATED ALWAYS AS (games_won::decimal / NULLIF(games_played,0)) STORED
  """
end
您的模式如下所示:

schema "players" do
  field :name, :string
  field :games_played, :integer
  field :games_won, :integer
  field :percentage_won, :float

  timestamps()
end
每次插入/更新其中一个玩家行时,都会计算新的
获胜百分比
值并插入/更新。现在,您还可以像普通列一样在EXTO查询中使用此值

SQL(和EXTO)也支持order_by子句中的表达式,您只需将表达式从
select_merge
复制到order_by:

from(p in Player,
  select_merge: %{
    percentage_won: fragment("(?::decimal / NULLIF(?,0))", p.games_won, p.games_played)
  },
  order_by: [
    {^sort_order, fragment("(?::decimal / NULLIF(?,0))", p.games_won, p.games_played)}
  ])

只有当结果集包含表中的所有行并且对查询没有限制时,此解决方案才可行。这是真的,但如何避免冗余片段?最后,我使用了来自的
from(p在播放器中,选择\u merge:%{percentage\u-won:fragment((?::decimal/NULLIF(?,0))作为percentage\u-won,p.games\u-won,p.games\u-played)},order\u-by:[{^sort\u-order,fragment(“percentage\u-won”)}])
您可以创建一个宏,返回这个片段:
defmacro-percentage\u-won(p)do-quote-do-do-do(?::decimal/NULLIF(?,0)),p.games_won,p.games_played)end
但是,AS的技巧看起来也很有用,我将来可能会使用它。这是一个很好的建议,对我的用例非常有效。我不愿意接受这个答案,因为它没有解决原始问题。