代码之家  ›  专栏  ›  技术社区  ›  tetranz

具有动态构建条件的exto连接

  •  5
  • tetranz  · 技术社区  · 8 年前

    我正在尝试构建一个带有左连接的extra条件的extra查询。我将尝试用典型的帖子和评论示例来描述它。

    帖子有很多评论 评论属于帖子。

    假设comment有两个布尔字段,approved和featured。

    我想得到所有的帖子,无论他们是否有评论,因此左连接。 我希望预加载注释,但最好是一个sql查询。 我想有选择地过滤评论批准和特色。

    我试图编写一个类似这样的函数,如果approved或featured不是nil,它们将包含在join中,如果它们是nil,它们将被忽略。我没有找到比这更好的方法:

    def posts_with_comments(approved, featured, some_var) do
      query = Post
      |> where([p], p.some_field == ^some_var
    
      cond do
        !is_nil(approved) and !is_nil(featured)
          -> join(query, :left, [p], c in Comment, [post_id: p.id, approved: ^approved, featured: ^featured])
    
        !is_nil(approved)
          -> join(query, :left, [p], c in Comment, [post_id: p.id, approved: ^approved])
    
        !is_nil(featured)
          -> join(query, :left, [p], c in Comment, [post_id: p.id, featured: ^featured])
    
        true -> join(query, :left, [p], c in Comment, [post_id: p.id])
      end
    
      |> preload([p, c], [comments: c])
      |> select([p], p)
      |> Repo.all
    
    end
    

    那是可行的,但一定有更好的办法。如果我有第三个参数,那就太疯狂了。我正在寻找一种方法来动态地为 on 参数 join() . 我的尝试失败了,因为我需要密码。

    我不能把这些条件放在 where 因为如果我做 where t.approved == true 我只得到帖子认可的评论。

    2 回复  |  直到 8 年前
        1
  •  1
  •   tetranz    8 年前

    我想答案是使用 dynamic 功能。

    这很管用。(排除了我之前的一些情况)。

    def posts_with_comments(approved, featured) do
      query = Post
      join(query, :left, [p], c in Comment, ^do_join(approved, featured))
      |> preload([p, c], [comments: c])
      |> Repo.all
    end
    
    defp do_join(approved, featured) do
      dynamic = dynamic([p, c], c.post_id == p.id)
    
      dynamic =
      case approved do
        nil -> dynamic
        _ -> dynamic([p, c], ^dynamic and c.approved == ^approved)
      end
    
      case featured do
        nil -> dynamic
        _ -> dynamic([p, c], ^dynamic and c.featured == ^featured)
      end
    end
    

    这比我的第一次尝试要好得多,因为这是一个简单的连接,只会随着条件的增加而变长,而不是条件的爆炸。

    作为一个练习,我无法通过给它提供一个字段列表并使用reduce之类的东西来使它更通用。我遇到的问题是让字段名(例如,c.approved)从变量开始工作。

    join 似乎支持两种类型 on 参数。关键字列表(我认为这意味着==)和更具表现力的格式。 dynamic 似乎不适用于关键字列表。它试图将p.id扩展到p.id()。

    我找不到@mudasobwa基于宏的解决方案。我还不完全是一个宏专家,但我不知道nil匹配在运行时是如何工作的。

    关于宏观解决方案还有一件事。出于某种原因,它也不适用于关键字列表。我希望这样一个简单的宏可以工作:

    defmacrop do_join do
      quote do
        [post_id: p.id]
      end
    end
    

    但它没有。它试图将p.id扩展到p.id()

        2
  •  0
  •   dina    7 年前

    我将在其中声明一个helper和模式匹配参数:

    def posts_with_comments(approved, featured, some_var) do
      query = Post
              |> where([p], p.some_field == ^some_var)
              |> join(:left, [p], c in Comment, do_join(approved, featured))
              |> preload([p, c], [comments: c])
              |> select([p], p)
              |> Repo.all
    end
    
    defmacrop do_join(nil, nil) do
      quote do: [post_id: p.id]
    end
    defmacrop do_join(approved, nil) do
      quote bind_quoted: [approved: approved] do
        [post_id: p.id, approved: ^approved]
      end
    end
    defmacrop do_join(nil, featured) do
      quote bind_quoted: [featured: featured] do
        [post_id: p.id, featured: ^featured]
      end
    end
    defmacrop do_join(approved, featured) do
      quote bind_quoted: [approved: approved, featured: featured] do
        [post_id: p.id, approved: ^approved, featured: ^featured]
      end
    end
    

    defmacro 必须允许pin运算符脱离上下文。

    或者,或者, Enum.reduce/3 它:

    # kw is approved: approved, featured: featured
    defmacrop do_join(kw) do
      initial = [{:post_id, {{:., [], [{:p, [], Elixir}, :id]}, [], []}}]
      Enum.reduce(kw, initial, fn
        {_, nil}, acc -> acc
        {k, _}, acc ->
          quoted = {k, {:^, [], [{k, [], Elixir}]}}
          [quoted | acc]
      end)
    end