按虚拟计算字段排序

问题描述 投票:2回答:3

[给出以下模式,我想计算:games_won /:games_played,将其填充到:percentage_won并按:percentage_won进行排序。如果使用select_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_players("percentage_won", :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)})
elixir ecto
3个回答
4
投票

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

Generated Columns自PostgreSQL 12版起可用,并允许创建基于其他列值的列(这非常适合您的用例!)。您将获得数据库的所有优势,并且无需在应用程序层中创建virtual字段。

要使其进入数据库,您可以编写原始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

每次插入/更新播放器行之一时,都会计算并插入/更新新的percentage_won值。现在,您也可以像普通列一样在ecto查询中使用此值!


1
投票

TL; DR

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

详细说明

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

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

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

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

1
投票

SQL(和ecto)也支持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)}
  ])
© www.soinside.com 2019 - 2024. All rights reserved.