Упорядочить по виртуальному вычисляемому полю - PullRequest
2 голосов
/ 27 октября 2019

Учитывая следующую схему, я хочу вычислить: games_won /: games_played, заполнить ее в: процент_вон и отсортировать по: процент_вон. Мне удалось вычислить значение, если я использую 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)})

Ответы [ 3 ]

4 голосов
/ 27 октября 2019

Тьяго Энрике уже ответил, почему virtual здесь не работает, но в зависимости от вашей базовой базы данных я хотел бы представить другое решение: Генерируемые столбцы

Созданные столбцы доступны в 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 будет вычисляться, а также вставляться / обновляться. ,Теперь вы также можете использовать это значение в экто-запросах, как обычный столбец!

1 голос
/ 29 октября 2019

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)}
  ])
1 голос
/ 27 октября 2019

TL; DR

Вам необходимо создать еще одну функцию Elixir для сортировки в вашем приложении, использование запроса не будет работать.

Длинное объяснение

Ecto.Query создает только строки запросов к базе данных на основе кода Elixir. Эти запросы будут выполняться в вашей базе данных, поэтому вашей базе данных необходимо знать все столбцы, указанные в этом запросе.

Поскольку виртуальные поля существуют только для вашего приложения, но не для вашей базы данных, вы можете только сортировать данныеосновываясь на этой области, используя ваше приложение Elixir, как показано ниже:

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

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