【问题标题】:How to do dynamic table joins using ecto?如何使用ecto进行动态表连接?
【发布时间】:2021-07-21 14:33:38
【问题描述】:

我正在尝试编写一个涉及动态表连接(照片到相册)的动态查询。我的第一次尝试仅适用于一对多(要放置的照片):

defmodule Test1 do
  def filter_by_place_id(dynamic, id) do
    dynamic([p], ^dynamic and p.place_id == ^id)
  end
end
dynamic =
  true
  |> Test1.filter_by_place_id(248)

但这不适用于多对多领域。我认为这需要一个表连接。所以我的下一个尝试:

defmodule Test2 do
  def filter_by_place_id({query, dynamic}, id) do
    dynamic = dynamic([p], ^dynamic and p.place_id == ^id)
    {query, dynamic}
  end
  def filter_by_album_id({query, dynamic}, id) do
    query = join(query, :inner, [p], album in assoc(p, :albums), as: :x)
    dynamic = dynamic([{:x, x}], ^dynamic and x.id == ^id)
    {query, dynamic}
  end
end
query = from(p in Photo)
{query, dynamic} =
  {query, true}
  |> Test2.filter_by_place_id(248)
  |> Test2.filter_by_album_id(10)
  |> Test2.filter_by_album_id(11)

但这失败了,因为绑定:x 是硬编码的,显然我不能重用它。但我需要一个绑定来确保 where 子句引用正确的连接。

但如果我尝试使用as: ^binding 而不是as :x,我会收到错误消息:

** (Ecto.Query.CompileError) `as` must be a compile time atom, got: `^binding`
    (ecto 3.6.2) expanding macro: Ecto.Query.join/5
    meow.exs:30: Test2.filter_by_album_id/2

所以我不确定从这里去哪里。是否可以为连接动态分配绑定?

【问题讨论】:

  • dynamic 表达式允许用于join:on 选项——这可能值得尝试,因为正如您所注意到的,:as 需要一个硬编码的命名绑定。

标签: elixir ecto


【解决方案1】:

绑定可以写成:first, ..., last

https://hexdocs.pm/ecto/Ecto.Query.html:

同样,如果您只对查询中的最后一个绑定(或最后一个绑定)感兴趣,您可以使用 ... 指定“之前的所有绑定”并匹配最后一个。

从posts_with_cmets中的[p, ..., c]中选择:{p.title, c.body}

所以尝试重写filter_by_album_id

def filter_by_album_id({query, dynamic}, id) do
    query = join(query, :inner, [p], album in assoc(p, :albums))
    dynamic = dynamic([p, ..., x], ^dynamic and x.id == ^id)
    {query, dynamic}
  end

【讨论】:

  • 不幸的是,这不起作用,因为我们总是在 select 语句处使用最后一个绑定。如果多次调用此函数,哪个不是正确的。
  • 你试过了吗?应用命令时的最后一个绑定是last。因此,如果您在加入后立即使用最后一个绑定,就像我在示例中所做的那样,它将起作用
  • 已确认,无效。问题是在from 语句之前没有任何东西将dynamic 链接到query。所以从函数中调用dynamic,它不知道绑定是什么。当它调用 from 语句时,会使用最后一个绑定。
【解决方案2】:

另一个答案给了我一个想法,放弃动态,只使用多个 where 子句。我不确定这是否会得到支持。看起来像是多个 where 子句被加在一起了。

def filter_by_value({query, dynamic}, field, id) do                            
  query = join(query, :inner, [p], album in assoc(p, ^field.id))               
  |> where([p, ..., x], x.id == ^id)                                           
  {query, dynamic}                                                             
end

不幸的是,这消除了您使用动态获得的灵活性,我真的很想要,但目前这是一个足够的解决方案。

【讨论】:

    【解决方案3】:

    实际上,您可以通过一些技巧来动态绑定别名。 Ecto query 是一个结构体,它有一个属性aliases 来存储名称绑定映射。

    例如你有一个查询:

    from(p in Post, join: c in assoc(p, :comments), as: :comments)
    

    那么别名将如下所示:

    %Ecto.Query{
       aliases: %{
         comments: 1
       }
    }
    

    comments 是别名,1 是位置。所以你可以手动更新别名映射。

    query = from(p in Post, join: c in assoc(p, :comments))
    column = :comments
    
    aliases = query.aliases
    
    if Map.has_key?(aliases, column) do
      raise ArgumentError, "Do not allow 2 ref binding with the same name"
    end
    
    aliases =
      case Enum.map(aliases, &elem(&1, 1)) do
        [] ->
          %{column => 1}
    
        positions ->
          max = Enum.max(positions)
          Map.put(aliases, column, max + 1)
      end
    
    # Update the alias map
    query = struct(query, aliases: aliases)
    

    但这只是一个 HACK,请注意这一点。

    我在构建动态连接查询时发现了这一点,您可以在此处找到源代码 https://github.com/bluzky/filtery/blob/6338f53c81608fd44f1eeac0a2ceb108043e56c9/lib/base.ex#L285

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 1970-01-01
      • 2017-02-13
      • 1970-01-01
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多