在 Elixir 中编写 Ecto 查询
Compose Ecto Queries in Elixir
我根据从客户端传入的参数列表创建了一个查询列表:
[
#Ecto.Query<from v in Video, where: v.brand == ^"Cocacola">,
#Ecto.Query<from v in Video, where: v.type == ^"can">
]
但是,我需要遍历这个列表并组成一个单一的查询,这是所有这些的累积..
(下一个查询的输入是当前查询等。)
有人能为我指明正确的方向吗?将不胜感激!
我知道我可以一个一个地编写查询。但是我从客户端获取参数并且有一长串字段(品牌、类型等)并且不想对每个字段进行单独的查询。
除非你打开单独的查询结构并通过它们的底层实现,否则不可能也不建议像这样在 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
的查询参数 并且您想要应用它们,您仍然可以通过迭代将它们一起调用在运行时超过 keys/values。您可以构建一个过滤方法来为您完成此操作:
# 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
进一步阅读:
尽管我同意@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 更新中中断。但是,这是针对所提出的特定问题的一种潜在解决方案。
我根据从客户端传入的参数列表创建了一个查询列表:
[
#Ecto.Query<from v in Video, where: v.brand == ^"Cocacola">,
#Ecto.Query<from v in Video, where: v.type == ^"can">
]
但是,我需要遍历这个列表并组成一个单一的查询,这是所有这些的累积.. (下一个查询的输入是当前查询等。)
有人能为我指明正确的方向吗?将不胜感激!
我知道我可以一个一个地编写查询。但是我从客户端获取参数并且有一长串字段(品牌、类型等)并且不想对每个字段进行单独的查询。
除非你打开单独的查询结构并通过它们的底层实现,否则不可能也不建议像这样在 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
的查询参数 并且您想要应用它们,您仍然可以通过迭代将它们一起调用在运行时超过 keys/values。您可以构建一个过滤方法来为您完成此操作:
# 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
进一步阅读:
尽管我同意@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 更新中中断。但是,这是针对所提出的特定问题的一种潜在解决方案。