9

我想执行如下查询:

SELECT id, name
FROM mytable
ORDER BY FIELD(name, 'B', 'A', 'D', 'E', 'C')

FIELD是 MySQL 特定的函数,并且'B', 'A', 'D', 'E', 'C'是来自 List 的值。

我尝试使用fragment,但它似乎不允许仅在运行时知道动态参数。

除了完全原始 using 之外Ecto.Adapters.SQL.query,有没有办法使用 Ecto 的查询 DSL 来处理这个问题?

编辑:这是第一种天真的方法,当然不起作用:

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))
4

4 回答 4

3

ORM 很棒,直到它们泄漏。最终,一切都会。Ecto 很年轻(fe,它只是在30 天前OR获得了将where 子句组合在一起的能力),因此它还不够成熟,无法开发一个考虑高级 SQL 回转的 API。

调查可能的选项,您并不孤单。Ecto 第 1485 期StackOverflowElixir 论坛和这篇文中提到了无法理解片段中的列表(无论是作为其一部分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 赶上来时,您可以很好地重写它,而不会影响系统的其余部分。

于 2017-01-02T16:09:36.617 回答
1

创建一个包含 2 列的表:

B 1
A 2
D 3
E 4
C 5

然后JOIN LEFT(name, 1)到它并得到序数。然后按那个排序。

(抱歉,我对 Elixir/Ecto/Arity 无能为力。)

于 2016-12-30T16:27:41.573 回答
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 
;

请注意,列表 'B','A',... 可以作为数组或任何其他方法传递,并替换上面代码示例中编写的内容。

于 2017-01-02T13:34:21.067 回答
1

这实际上让我发疯,直到我发现(至少在 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))
于 2020-06-04T11:01:15.453 回答