【问题标题】:Compose Ecto Queries in Elixir在 Elixir 中编写 Ecto 查询
【发布时间】:2018-10-16 19:14:10
【问题描述】:

我已经根据从客户端传入的参数列表创建了一个查询列表:

[
  #Ecto.Query<from v in Video, where: v.brand == ^"Cocacola">,
  #Ecto.Query<from v in Video, where: v.type == ^"can">
]

但是,我需要遍历此列表并组成一个查询,该查询是所有查询的累积。 (下一个查询的输入是当前查询等)

有人能指出我如何做到这一点的正确方向吗?将不胜感激!

我知道我可以一一编写查询。但是我从客户那里得到了参数,并且有一长串字段(品牌、类型等),并且不想为每个字段单独查询。

【问题讨论】:

  • 所以你想要一些动态解决方案?
  • 您可以使用https://hexdocs.pm/ecto/Ecto.Query.API.html#field/2。我将使用任何字段广告值动态构建查询。您只需使用 params 中的键值对对其进行迭代。
  • @script 谢谢.. 是的,我实际上采用了 Sheharyar 的解决方案,并使用 field/2 动态获取它,而不是对每个字段进行多个查询

标签: elixir phoenix-framework ecto


【解决方案1】:

除非您打开单个查询结构并完成其底层实现,否则既不可能也不建议像这样在 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



现在假设您获得了 MapKeyword 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



进一步阅读:

【讨论】:

  • 你能提供最后一部分的例子吗? (从列表中组合它们)
【解决方案2】:

虽然我同意@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 查询的结构,也不想为每个查询进行单独的查询。不过感谢您提供这个聪明的解决方案!
  • @phil 可能有。您需要将实际查询存储在列表中,还是仅将键/值存储在列表中。像 [brand: "Cocacola, type: "can"]?。如果是这样,那么你可以这样做:Enum.reduce(list, from(v in Video), fn {k, y}, acc -> where( acc, [q], 字段(q, ^k) == ^y) end)
  • 是的,我似乎把查询存储在一个列表中是错误的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多