在 Ecto 查询中使用变量引用命名绑定
Use variable to reference named binding in an Ecto Query
我正在尝试构建一个函数来搜索查询中给定 table 的字段中的术语。
对于像
这样的查询
initial_query =
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
我希望能够在 :question
和 :survey
引用的 table 中进行搜索。
现在,此代码有效:
initial_query
|> or_where(
[question: t], #:question hard coded
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
但是,我想要一个将命名绑定作为参数的函数,但我找不到实现它的方法。
我的尝试:
defp search_field(initial_query, table, field, search_term) do
initial_query
|> or_where(
[{table, t}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
报错
** (Ecto.Query.CompileError) unbound variable t
in query. If you are attempting to interpolate a value, use ^var
expanding macro: Ecto.Query.or_where/3
当这样调用时:
search_field(initial_query, :question, :text, search_text)
和
defp search_field(initial_query, table, field, search_term) do
initial_query
|> or_where(
[{^table, t}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
给予
** (Ecto.Query.CompileError) binding list should contain only variables or {as, var}
tuples, got: {^table, t}
expanding macro: Ecto.Query.or_where/3
有没有办法在 Ecto 查询中使用变量来引用命名绑定?
诀窍是检索命名绑定在绑定中的位置。命名绑定存储在 %Ecto.Query{aliases: aliases}
字段中。
def named_binding_position(query, binding) do
Map.get(query.aliases, binding)
end
def search_field(query, table, field, search_term) do
position = named_binding_position(query, table)
query
|> or_where(
[{t, position}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
我们首先在query.aliases中查找命名绑定的位置。然后使用这个位置来构建查询。
现在,当我们调用
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
|> search_field(:question, :text, "bogus")
它应该产生类似
的东西
#Ecto.Query<from a in Answer,
left_join: q in assoc(a, :question), as: :question,
or_where: fragment("CAST(? AS varchar) ILIKE ?", q.text, ^"%bogus%")>
值得注意的是,%Query.aliases 中的 {t, position}
元组引用命名绑定的位置是一个内部实现,没有记录。因此,可能会发生变化。有关详细信息,请参阅 https://github.com/elixir-ecto/ecto/issues/2832
所以这个问题的答案似乎是 Ecto 不支持这样做的方法。 @maartenvanvliet 解决方案效果很好,缺点是依赖内部实现。
我对这个问题的解决方案是让函数 search_field
始终在最后加入的 table 中搜索,使用 here 描述的 ...
语法:
# Searches for the `search_term` in the `field` in the last joined table in `initial_query`.
defp search_field(initial_query, field, search_term) do
initial_query
|> or_where(
[..., t],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
所以这个函数可以这样使用:
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> search_field(:text, search_text)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
|> search_field(:title, search_text)
在我看来,它仍然读起来很好,缺点是要求我们能够更改 initial_query
。
我正在尝试构建一个函数来搜索查询中给定 table 的字段中的术语。
对于像
这样的查询initial_query =
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
我希望能够在 :question
和 :survey
引用的 table 中进行搜索。
现在,此代码有效:
initial_query
|> or_where(
[question: t], #:question hard coded
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
但是,我想要一个将命名绑定作为参数的函数,但我找不到实现它的方法。
我的尝试:
defp search_field(initial_query, table, field, search_term) do
initial_query
|> or_where(
[{table, t}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
报错
** (Ecto.Query.CompileError) unbound variable
t
in query. If you are attempting to interpolate a value, use ^var expanding macro: Ecto.Query.or_where/3
当这样调用时:
search_field(initial_query, :question, :text, search_text)
和
defp search_field(initial_query, table, field, search_term) do
initial_query
|> or_where(
[{^table, t}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
给予
** (Ecto.Query.CompileError) binding list should contain only variables or
{as, var}
tuples, got: {^table, t} expanding macro: Ecto.Query.or_where/3
有没有办法在 Ecto 查询中使用变量来引用命名绑定?
诀窍是检索命名绑定在绑定中的位置。命名绑定存储在 %Ecto.Query{aliases: aliases}
字段中。
def named_binding_position(query, binding) do
Map.get(query.aliases, binding)
end
def search_field(query, table, field, search_term) do
position = named_binding_position(query, table)
query
|> or_where(
[{t, position}],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
我们首先在query.aliases中查找命名绑定的位置。然后使用这个位置来构建查询。
现在,当我们调用
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
|> search_field(:question, :text, "bogus")
它应该产生类似
的东西#Ecto.Query<from a in Answer,
left_join: q in assoc(a, :question), as: :question,
or_where: fragment("CAST(? AS varchar) ILIKE ?", q.text, ^"%bogus%")>
值得注意的是,%Query.aliases 中的 {t, position}
元组引用命名绑定的位置是一个内部实现,没有记录。因此,可能会发生变化。有关详细信息,请参阅 https://github.com/elixir-ecto/ecto/issues/2832
所以这个问题的答案似乎是 Ecto 不支持这样做的方法。 @maartenvanvliet 解决方案效果很好,缺点是依赖内部实现。
我对这个问题的解决方案是让函数 search_field
始终在最后加入的 table 中搜索,使用 here 描述的 ...
语法:
# Searches for the `search_term` in the `field` in the last joined table in `initial_query`.
defp search_field(initial_query, field, search_term) do
initial_query
|> or_where(
[..., t],
fragment(
"CAST(? AS varchar) ILIKE ?",
field(t, ^field),
^"%#{search_term}%"
)
)
end
所以这个函数可以这样使用:
Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> search_field(:text, search_text)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
|> search_field(:title, search_text)
在我看来,它仍然读起来很好,缺点是要求我们能够更改 initial_query
。