我想执行如下查询:
SELECT id, name
FROM mytable
ORDER BY FIELD(name, 'B', 'A', 'D', 'E', 'C')
FIELD
是MySQL特定的函数,'B', 'A', 'D', 'E', 'C'
是来自List的值。
我尝试使用fragment,但它似乎不允许仅在运行时中知道动态arity。
除了使用Ecto.Adapters.SQL.query
使用完全原始版本,有没有办法使用Ecto的查询DSL来处理它?</ p>
编辑:这是第一种天真的方法,当然不起作用:
ids = [2, 1, 3] # this list is of course created dynamically and does not always have three items
query = MyModel
|> where([a], a.id in ^ids)
|> order_by(fragment("FIELD(id, ?)", ^ids))
答案 0 :(得分:3)
ORM很棒,直到他们leak。一切都做到了。 Ecto很年轻(fe,它只能获得OR
一起使用30 days ago条款的能力,所以它还不够成熟,不足以开发出考虑高级SQL回转的API。
调查可能的选项,您并不是唯一的请求。 Ecto issue #1485 StackOverflow上Elixir Forum上提到了无法理解片段中的列表(无论是order_by
还是where
还是其他任何地方的一部分)这个blog post。后者特别有启发性。更多关于这一点。首先,让我们尝试一些实验。
实验#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,?,?,?)
给定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&#34;与谷物&#34;可以这么说,它要求你有一个有限的,可管理的可用字段数。这可能会也可能不会改变游戏规则。
我的建议:不要试图解决ORM的漏洞。你知道最好的查询。如果ORM不接受它,请直接用原始SQL编写它,并记录ORM不起作用的原因。将其屏蔽在功能或模块后面,以便您可以保留未来更改其实施的权利。有一天,当ORM赶上时,你可以很好地重写它,而不会影响系统的其他部分。
答案 1 :(得分:1)
创建一个包含2列的表:
B 1
A 2
D 3
E 4
C 5
然后JOIN
LEFT(name, 1)
并获得序数。然后按那个排序。
(抱歉,我无法帮助Elixir / Ecto / Arity。)
答案 2 :(得分:1)
我会尝试使用以下SQL SELECT语句解决此问题:
[注意:现在不能访问系统以检查语法的正确性,但我认为没关系]
SELECT A.MyID , A.MyName
FROM (
SELECT id AS MyID ,
name AS MyName ,
FIELD(name, 'B', 'A', 'D', 'E', 'C') AS Order_By_Field
FROM mytable
) A
ORDER BY A.Order_By_Field
;
请注意,列表&#39; B&#39; A&#39;,...可以作为数组或任何其他方法传递,并替换上面代码示例中的内容
答案 3 :(得分:0)
这实际上使我发疯,直到我发现(至少在MySQL中)有一个FIND_IN_SET
函数。语法有点怪异,但是它不包含可变参数,因此您应该能够做到这一点:
ids = [2, 1, 3] # this list is of course created dynamically and does not always have three items
ids_string = Enum.join(ids, ",")
query = MyModel
|> where([a], a.id in ^ids)
|> order_by(fragment("FIND_IN_SET(id, ?)", ^ids_string))