我已经从客户端传入的参数列表中创建了一个查询列表:
[
#Ecto.Query<from v in Video, where: v.brand == ^"Cocacola">,
#Ecto.Query<from v in Video, where: v.type == ^"can">
]
但是,我需要遍历此列表并组成一个查询,这些查询都是所有查询的累加。 (下一个查询的输入是当前查询,等等。)
有人可以为我指出正确的方法。不胜感激!
我知道我可以一个一个地组成查询。但是我从客户那里得到了一些参数,并且字段列表(品牌,类型,...等)很长,并且不想为每个字段单独查询。
答案 0 :(得分:2)
除非您打开单个查询结构并经历其底层实现,否则既不可能也不建议像这样在Ecto中加入查询。相反,您应该尝试将它们分解并使其可组合。
Ecto使您轻松组合查询:
defmodule VideoQueries do
import Ecto.Query
def with_brand(query, brand) do
where(query, [v], v.brand == ^brand)
end
def with_type(query, type) do
where(query, [v], v.type == ^type)
end
def latest_first(query) do
order_by(query, desc: :inserted_at)
end
end
您可以像这样一起调用它们:
Video
|> VideoQueries.with_brand("Cocacola")
|> VideoQueries.with_type("can")
|> VideoQueries.latest_first
现在,假设您获得了Map
或Keyword List
个查询参数,并且想要应用它们,仍然可以通过遍历键/值来一起调用它们在运行时。您可以构建一个为您完成此操作的过滤器方法:
# Assuming args are a Keyword List or a Map with Atom keys
def filter(query, args) do
Enum.reduce(args, query, fn {k, v}, query ->
case k do
:brand -> with_brand(query, v)
:type -> with_type(query, v)
_ -> query
end
end)
end
并且可以动态地组成这样的查询:
user_input = %{brand: "Cocacola", type: "can"}
Video
|> VideoQueries.filter(user_input)
|> Repo.all
更多读物:
答案 1 :(得分:1)
尽管我同意@sheharyar的观点,可组合查询是最好的方法,但有时我们需要超越最佳实践的解决方案。因此,如上所述,我将为您的问题提供答案。
不要让我的示例架构分散您的注意力。这只是我加载的用于测试解决方案的项目...
要检查查询结构,您可以尝试以下操作:
iex(128)> Map.from_struct(from(q in OneIosThemeGen.Themes.Entry, where: q.base_hex == ^base_hex))
%{
assocs: [],
distinct: nil,
from: {"entries", OneIosThemeGen.Themes.Entry},
group_bys: [],
havings: [],
joins: [],
limit: nil,
lock: nil,
offset: nil,
order_bys: [],
prefix: nil,
preloads: [],
select: nil,
sources: nil,
updates: [],
wheres: [
%Ecto.Query.BooleanExpr{
expr: {:==, [],
[{{:., [], [{:&, [], [0]}, :base_hex]}, [], []}, {:^, [], [0]}]},
file: "iex",
line: 128,
op: :and,
params: [{"#E8EBED", {0, :base_hex}}]
}
]
}
如您所见,where子句位于wheres
字段中。它包含一个列表。
因此,我们可以从每个查询中提取wheres
字段并连接列表。这就是我在下面演示的内容。
这里是组成多个查询的where子句的示例。它只能通过“和”在一起来处理where子句。
base_hex = "#E8EBED"
name = "bodyText"
queries = [
from(q in OneIosThemeGen.Themes.Entry, where: q.base_hex == ^base_hex),
from(q in OneIosThemeGen.Themes.Entry, where: q.name == ^name)
]
build = fn queries ->
wheres = Enum.reduce(queries, [], fn %{wheres: wheres}, acc -> wheres ++ acc end)
from(q in OneIosThemeGen.Themes.Entry)
|> Map.put(:wheres, wheres)
end
query = build.(queries)
rows = Repo.all(query)
# sort function for result equality assertion
sort = fn list -> Enum.sort(list, & &1.id <= &2.id) end
# Single query for results equality test
combined_query = from(q in OneIosThemeGen.Themes.Entry, where: q.base_hex == ^base_hex and q.name == ^name)
rows_combined = Repo.all(combined_query)
# Test that the results are the same
sort.(rows) == sort.(rows_combined)
# true
# Now test that running the queries individually does not return the same results
rows3 = Enum.map(queries, &Repo.all/1) |> List.flatten()
sort.(rows3) != sort.(rows)
# true
IO.puts("length {rows, rows3}: " <> inspect({length(rows), length(rows3)}))
# length {rows, rows3}: {2, 5}
请注意,此解决方案依赖于Ecto查询的内部结构,这通常是一种不好的做法。它可能会在将来的Ecto更新中中断。但是,这是针对所提出的特定问题的一种潜在解决方案。