Ecto 加入动态构建的条件
Ecto join with dynamically built conditions
我正在尝试使用左连接构建一个 Ecto 查询,并在连接上添加可选的额外条件。我将尝试用典型的帖子和评论示例来描述它。
Post has_many 评论
评论 belongs_to Post.
假设 Comment 有两个布尔值字段,approved 和 featured。
我想获取所有 Posts 而不管他们是否有评论,因此左连接。
我想要预加载评论,但最好是一个 SQL 查询。
我想有选择地过滤有关已批准和精选的评论。
我正在尝试编写一个类似这样的函数,如果 approved 或 featured 不是 nil,它们将被包含在连接中,如果它们是 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
这行得通,但必须有更好的方法。如果我有第三个参数,它会变得疯狂。我正在寻找一种方法来为 join()
的 on
参数动态构建该列表。由于需要固定,我尝试这样做失败了。
我不能将这些条件放在 where
中,因为如果我做类似 where t.approved == true
的事情,我只会得到帖子批准的评论。
我会在其中声明一个帮助程序和模式匹配参数:
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
我认为答案是使用 dynamic 函数。
这行得通。 (省略了我之前的 some_var 条件)。
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()
我正在尝试使用左连接构建一个 Ecto 查询,并在连接上添加可选的额外条件。我将尝试用典型的帖子和评论示例来描述它。
Post has_many 评论 评论 belongs_to Post.
假设 Comment 有两个布尔值字段,approved 和 featured。
我想获取所有 Posts 而不管他们是否有评论,因此左连接。 我想要预加载评论,但最好是一个 SQL 查询。 我想有选择地过滤有关已批准和精选的评论。
我正在尝试编写一个类似这样的函数,如果 approved 或 featured 不是 nil,它们将被包含在连接中,如果它们是 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
这行得通,但必须有更好的方法。如果我有第三个参数,它会变得疯狂。我正在寻找一种方法来为 join()
的 on
参数动态构建该列表。由于需要固定,我尝试这样做失败了。
我不能将这些条件放在 where
中,因为如果我做类似 where t.approved == true
的事情,我只会得到帖子批准的评论。
我会在其中声明一个帮助程序和模式匹配参数:
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
我认为答案是使用 dynamic 函数。
这行得通。 (省略了我之前的 some_var 条件)。
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()