ORM 很棒,直到它们泄漏。最终,一切都会。Ecto 很年轻(fe,它只是在30 天前OR
获得了将where 子句组合在一起的能力),因此它还不够成熟,无法开发一个考虑高级 SQL 回转的 API。
调查可能的选项,您并不孤单。Ecto 第 1485 期、StackOverflow、Elixir 论坛和这篇博文中提到了无法理解片段中的列表(无论是作为其一部分order_by
还是where
其他任何地方)。后者特别具有指导意义。稍后再谈。首先,让我们尝试一些实验。
实验#1:可能首先尝试使用Kernel.apply/3
将列表传递给fragment
,但这不起作用:
|> order_by(Kernel.apply(Ecto.Query.Builder, :fragment, ^ids))
实验#2:那么也许我们可以用字符串操作来构建它。如何fragment
在运行时提供一个带有足够占位符的字符串以使其从列表中拉出:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(Enum.map(ids, fn _ -> "?" end), ","), ")"], ""), ^ids))
哪个会产生FIELD(id,?,?,?)
given ids = [1, 2, 3]
。不,这也不起作用。
实验#3:创建从 id 构建的完整的最终 SQL,将原始 ID 值直接放在组合字符串中。除了可怕之外,它也不起作用:
|> order_by(fragment(Enum.join(["FIELD(id,", Enum.join(^ids, ","), ")"], "")))
实验#4:这让我想到了我提到的那篇博客文章。在其中,作者解决了缺乏or_where
使用一组基于条件数量的预定义宏来组合在一起的问题:
defp orderby_fragment(query, [v1]) do
from u in query, order_by: fragment("FIELD(id,?)", ^v1)
end
defp orderby_fragment(query, [v1,v2]) do
from u in query, order_by: fragment("FIELD(id,?,?)", ^v1, ^v2)
end
defp orderby_fragment(query, [v1,v2,v3]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3)
end
defp orderby_fragment(query, [v1,v2,v3,v4]) do
from u in query, order_by: fragment("FIELD(id,?,?,?)", ^v1, ^v2, ^v3, ^v4)
end
虽然这可以说是有效的并且使用了 ORM,但它要求您拥有有限的、可管理数量的可用字段。这可能会或可能不会改变游戏规则。
我的建议:不要试图绕过 ORM 的泄漏。你知道最好的查询。如果 ORM 不接受,直接用原始 SQL 编写,并记录 ORM 不起作用的原因。将其屏蔽在功能或模块后面,以便您可以保留将来更改其实现的权利。有一天,当 ORM 赶上来时,您可以很好地重写它,而不会影响系统的其余部分。