【问题标题】:Using a table alias/query expression in an Ecto fragment?在 Ecto 片段中使用表别名/查询表达式?
【发布时间】:2017-06-28 01:55:23
【问题描述】:

从这个查询开始,作为 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 本身。

【问题讨论】:

  • 我认为fragment("json_agg(?)", p.*) 会起作用,但它会转义* 并最终将名为“*”的列的值发送到json_agg。我查看了 Ecto 中包含fragment 的所有测试,但没有发现任何通过表名或table.*...我认为现在碎片不可能实现。
  • 是的,尝试了同样的结果。好的,很高兴知道它不只是我。 :) 如果您有任何想法,还将感谢您对实现相同结果的替代方法的回答!

标签: elixir ecto postgrex


【解决方案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 中的问号数应与profiles 表中的列数完全相同,并且表中列的顺序应与fragment 参数的顺序相对应。我不知道你的架构,所以我“编造”了 3 列 - 希望你明白。

我自己在一个简化的示例中尝试了这种方法(没有连接)。我用作游乐场的应用程序的源代码是there

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) 使其更灵活,但偶然发现了the inability of fragment to accept variable number of arguments

这是我目前得到的:

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 中的宏来做到这一点(还)。

【讨论】:

  • 感谢您的答案,即使它不是我正在寻找的答案 - 来自 ecto 邮件列表的消息是应该支持它,尽管它目前不起作用。实际上我昨天花了大约 5 个小时试图提出一个 PR 来支持它,但是我在 elixir 中的元编程技能还不能完全胜任这项任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多