【问题标题】:(Ecto.Query.CompileError) Tuples can only be used in comparisons with literal tuples of the same size. - Elixir(Ecto.Query.CompileError) 元组只能用于与相同大小的文本元组进行比较。 - 灵药
【发布时间】:2019-12-15 12:26:07
【问题描述】:

我在哪里

对于这个例子,考虑Friends.repo

Person 具有字段:id:name:age

Ecto 查询示例:

iex> from(x in Friends.Person, where: {x.id, x.age} in [{1,10}, {2, 20}, {1, 30}], select: [:name])

当我运行它时,我会得到相关的结果。比如:

[
  %{name: "abc"},
  %{name: "xyz"}
]

但是当我尝试插入查询时,它会抛出错误

iex> list = [{1,10}, {2, 20}, {1, 30}]
iex> from(x in Friends.Person, where: {x.id, x.age} in ^list, select: [:name])
** (Ecto.Query.CompileError) Tuples can only be used in comparisons with literal tuples of the same size

我假设我需要对 list 变量进行某种类型转换。文档here 中提到了这一点:“在插值时,您可能需要明确告诉 Ecto 所插值的预期类型是什么

我需要什么

对于这样的复杂类型,如何实现这一点?如何为“元组列表,每个大小为 2”键入 cast? [{:integer, :integer}] 之类的东西似乎不起作用。

如果不是上述情况,是否可以使用 Ecto Query 运行 WHERE (col1, col2) in ((val1, val2), (val3, val4), ...) 类型的查询?

【问题讨论】:

    标签: mysql erlang elixir ecto elixir-iex


    【解决方案1】:

    很遗憾,应该按照错误消息中的说明处理该错误:only literal tuples are supported

    我无法想出一些更优雅、更不脆弱的解决方案,但我们总是将大锤作为最后的手段。我们的想法是生成并执行原始查询。

    list = [{1,10}, {2, 20}, {1, 30}]
    #⇒ [{1, 10}, {2, 20}, {1, 30}]
    values =
      Enum.join(for({id, age} <- list, do: "(#{id}, #{age})"), ", ")
    #⇒ "(1, 10), (2, 20), (1, 30)"
    
    
    Repo.query(~s"""
      SELECT name FROM persons
      JOIN (VALUES #{values}) AS j(v_id, v_age)
      ON id = v_id AND age = v_age
    """)    
    

    上面应该返回{:ok, %Postgrex.Result{}}成功的元组。

    【讨论】:

    • 是的,我最终也使用了原始查询。虽然我仍然坚持使用 WHERE IN 子句
    • 嗯。上面的 SQL (应该)比WHERE IN 子句更高效;如果你坚持使用WHERE IN,它仍然很容易通过稍微更新生成字符串的代码来实现。您最好向 Ecto 提出问题,询问在 in 子句中支持动态生成元组的计划。
    • 我们正在使用 MySQL。 MySQL 不支持在 INSERT 之外使用 VALUES。真的很烦人。所有其他 SQL DB 都具有此功能。对了,为什么这个比WHERE IN好?
    • 很奇怪,现在我找不到参考;我们的 DBA 多次告诉我它更好,我只是盲目地信任他们。
    【解决方案2】:

    您可以为每个字段和unnest 使用单独的数组来做到这一点,它将数组压缩成行,每个数组都有一列:

    ids =[ 1,  2,  1]
    ages=[10, 20, 30]
    
    from x in Friends.Person, 
    inner_join: j in fragment("SELECT distinct * from unnest(?::int[],?::int[]) AS j(id,age)", ^ids, ^ages),
            on: x.id==j.id and x.age==j.age,
    select: [:name]
    

    另一种方法是使用 json:

    list = [%{id: 1, age: 10}, 
            %{id: 2, age: 20}, 
            %{id: 1, age: 30}]
    
    from x in Friends.Person,
    inner_join: j in fragment("SELECT distinct * from jsonb_to_recordset(?) AS j(id int,age int)", ^list),
            on: x.id==j.id and x.age==j.age,
    select: [:name]
    

    更新:我现在看到了标签mysql,上面是为postgres写的,但也许它可以作为一个mySql版本的基础。

    【讨论】:

      猜你喜欢
      • 2018-09-14
      • 2021-01-23
      • 2021-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多