Phoenix/Ecto - 查询字符串数组中的匹配项

Phoenix/Ecto - query for matches in array of strings

在我的 Phoenix 应用程序的一个表中,我有一个字符串数组字段。我希望能够使用 where: like() 类型的查询来查看数组中的任何值是否包含查询字符串 - 但是,我不确定该怎么做。在应用程序的前一次迭代中,有问题的字段只是一个字符串字段,下面的查询完美运行:

results = from(u in User,
    where: like(u.fulltext, ^("%#{search_string}%"))
    |> Repo.all()

既然我已经将 fulltext 字段更改为字符串数组(character varying(255)[],在 Postgres 术语中),可以理解该查询失败并显示错误

ERROR 42883 (undefined_function): operator does not exist: character varying[] ~~ unknown

但我不确定如何优化查询以匹配新架构。

例如,用户的 fulltext 字段看起来像

["john smith", "john@test.com"]

并且当 search_string"john""@test""n smith" 等时应返回相关记录 - 如果 search_string 匹配任何部分列表值之一。

在简单的英语中,查询将读作 "return records where a value like search_string is found in the list u.fulltext"。

我可以想到各种 'hacky' 解决方法,比如只返回所有用户的列表,然后使用一些链式 Enum.map 函数 运行 通过它们并检查 fulltext 用于部分匹配,但如果有使用 Ecto 查询语法的更优雅的解决方案,我宁愿选择它。任何人都可以提供任何指导吗?

您可以在 PostgreSQL 中使用带有子查询的 unnest 来检查数组的任何项是否为 LIKE something:

from(p in Post, select: p.tags, where: fragment("exists (select * from unnest(?) tag where tag like ?)", p.tags, "%o%")

对于您的情况,这应该有效:

from(u in User, where: fragment("exists (select * from unnest(?) tag where tag like ?)", u.fulltext, ^("%#{search_string}%"))
iex(1)> Repo.insert! %Post{tags: ~w(foo bar baz)}                                                                              [debug] QUERY OK db=0.3ms
iex(2)> Repo.insert! %Post{tags: ~w(quux)}
iex(3)> Repo.insert! %Post{tags: ~w(hello world)}
iex(4)> query = "%o%"
"%o%"
iex(5)> Repo.all from(p in Post, select: p.tags, where: fragment("exists (select * from unnest(?) tag where tag like ?)", p.tags, "%o%"))
[debug] QUERY OK source="posts" db=3.9ms
SELECT p0."tags" FROM "posts" AS p0 WHERE (exists (select * from unnest(p0."tags") tag where tag like '%o%')) []
[["foo", "bar", "baz"], ["hello", "world"]]

您可以使用 fragmentunnest 将数组转换为连接:

user_texts = 
  from u in User, 
  select: %{id: u.id, fulltext: fragment("unnest(fulltext)")}

query = 
  from u in User, 
  join: t in subquery(user_texts), on: u.id == t.id, 
  where: like(t.fulltext, ^("%#{search_string}%")), 
  select: u,
  distinct: true

Repo.all(query)