Используя псевдоним таблицы / выражение запроса во фрагменте Ecto?
Начиная с этого запроса в качестве основы для версии Ecto:
select folder_id, json_agg(p.*)
from folder_memberships inner join profiles p
on p.id=folder_memberships.profile_id
where folder_id in (1234) group by folder_id;
У меня есть этот код:
# ids=[1234]
from(p in Profile,
join: link in FolderMembership, on: link.profile_id == p.id,
select: [link.folder_id, fragment("json_agg(?) as members", p)],
group_by: link.folder_id,
where: link.folder_id in ^ids
)
|> Repo.all
И это вызывает у меня следующую ошибку:
== Compilation error on file lib/profile.ex ==
** (Ecto.Query.CompileError) variable `p` is not a valid query expression.
Variables need to be explicitly interpolated in queries with ^
(ecto) expanding macro: Ecto.Query.select/3
Я уверен, что я скучаю по элементарному, но я не в себе, если знаю, что это такое. Я попробовал несколько вариантов, но все примеры, которые я смог увидеть, делают что-то вроде fragment("json_agg(?)", p.some_field)
не p
сам.
1 ответ
Решение не является идеальным, поскольку требует явного перечисления всех полей, а также не позволяет исключать поля из полученного JSON.
# ids=[1234]
from(p in Profile,
join: link in FolderMembership, on: link.profile_id == p.id,
select: [link.folder_id, fragment("json_agg((?, ?, ?)::profiles) as members", p.id, p.name, p.created_at)],
group_by: link.folder_id,
where: link.folder_id in ^ids
)
|> Repo.all
Количество вопросительных знаков в json_agg
должно быть точно таким же, как количество столбцов в таблице профилей, а также порядок столбцов в таблице должен соответствовать порядку fragment
аргументы. Я не знаю вашу схему, поэтому я "составил" 3 столбца - надеюсь, вы поняли идею.
Я попробовал этот подход самостоятельно на упрощенном примере (без объединения). Исходный код приложения, которое я использовал в качестве игровой площадки, находится там.
defmodule Magic do
import Ecto.Query
alias Badging.{Badge, Repo}
@fields Badge.__schema__(:fields)
@source Badge.__schema__(:source)
@questions Enum.map_join(@fields, ", ", fn _ -> "?" end)
@json_agg "json_agg((#{@questions})::#{@source})"
def run do
fields = Badge.__schema__(:fields)
source = Badge.__schema__(:source)
questions = Enum.map_join(fields, ", ", fn _ -> "?" end)
json_agg = "json_agg((#{questions})::#{source})"
from(
b in Badge,
select: [
b.id,
fragment(
"json_agg((?, ?, ?, ?, ?, ?, ?, ?, ?)::badges)",
b.id,
b.identifier,
b.subject,
b.status,
b.color,
b.svg,
b.svg_downloaded_at,
b.inserted_at,
b.updated_at
)
],
group_by: b.id
) |> Repo.all
end
end
Я также сделал попытку сделать его более гибким, используя Badge.__schema__(:fields)
а также Badge.__schema__(:source)
но наткнулся на неспособность fragment
принять переменное количество аргументов.
Это то, что я получил так далеко:
defmodule Magic do
import Ecto.Query
alias Badging.{Badge, Repo}
fields = Badge.__schema__(:fields)
source = Badge.__schema__(:source)
questions = Enum.map_join(fields, ", ", fn _ -> "?" end)
@json_agg "json_agg((#{questions})::#{@source})"
def run do
from(
b in Badge,
select: [
b.id,
fragment(
@json_agg,
field(b, :id), # or just b.id
b.identifier,
b.subject,
b.status,
b.color,
b.svg,
b.svg_downloaded_at,
b.inserted_at,
b.updated_at
)
],
group_by: b.id
) |> Repo.all
end
end
Я думаю, что технически можно положиться на __schema__(:fields)
вместо перечисления всех полей в явном виде. Список полей известен во время компиляции. Я просто не так хорош в макросах в Elixir/Ecto, чтобы сделать это (пока).