Neo4j:属性 数组上的 Cypher 查询

Neo4j: Cypher query on property array

我有一个域 class,它有一个 属性 的名称 "alias",它是一个字符串数组列表,如下所示:

private List<String> alias;

别名包含以下值:{"John Doe","Alex Smith","Greg Walsh"}

我希望能够进行如下查询:"I saw Smith today" 使用下面显示的我的存储库查询并获取数组值输出 "Alex Smith":

@Query("MATCH (p:Person) WHERE {0} IN p.alias RETURN p")    
Iterable<Person> findByAlias(String query);

我尝试了一堆不同的查询,如上所示,但这只有在输入查询与数组值完全匹配时才会匹配。

我想对输入查询子字符串与数组值进行匹配。

例如: 输入查询:"I saw Smith today" 输出:"Alex Smith"

你应该使用这样的东西:

MATCH (n:Test)
WHERE single(x IN n.prop WHERE x = "elem1")
RETURN n

它检查集合是否恰好有一个 "elem1"。

More info.

总结

做你想做的有点可能,但查询会非常丑陋和缓慢。您最好使用节点和关系而不是集合属性:这将使您的查询更明智,并允许您使用全文索引。在将查询发送到数据库之前,您还应该弄清楚要查找的 'input string' 的哪一部分。就目前而言,您将正则表达式模式与它应该匹配的数据混淆了,即使可以将您的意图表达为正则表达式,在发送查询之前处理您的应用程序会更好。

1) WHERE ... IN ... 不执行正则表达式

WHERE x IN y 不会将 x 视为正则表达式,它将采用 x 的值并寻找完全匹配。 WHERE ... IN ... 在这个意义上类似于 WHERE ... = ...,您需要类似 =~ 的集合,例如 IN~。 Cypher 中没有这样的结构。

2) 您可以对带有谓词的集合执行正则表达式,但效率很低

您可以使用字符串作为正则表达式,通过使用 ANYFILTER.

等谓词来测试集合中的匹配项
CREATE (p:Person {collectionProperty:["Paulo","Jean-Paul"]})

WITH "(?i).*Paul" as param
MATCH (p:Person)
WHERE ANY(item IN p.collectionProperty WHERE item =~ param)
RETURN p

将 return 节点,因为它在 "Jean-Paul".

上进行了成功的正则表达式匹配

但是,这会带来糟糕的性能,因为您将在数据库中的每个 collectionProperty 中对每个 item 中的每个 item 对每个 :Person 进行正则表达式。解决方法是使用全文索引,但是他的查询不能使用索引有两个原因:

  1. 您查询的值在数组中
  2. 您正在使用正则表达式过滤结果,而不是执行索引查询

3) 您根本无法使用您的输入类型对集合执行正则表达式

您的查询最大的问题是您试图通过添加一些正则表达式糖将 "I saw Smith today" 变成 "Smith"。你打算怎么做?如果您将字符串用作正则表达式,则这些字符中的每一个都是预期在您匹配它的数据中的文字字符。您对 .* 感到困惑,它在 'Smith.*' 中使用时将匹配 'Smith' 加上数据 中的零个或多个附加字符 。但是您尝试用它来表示零个或多个字符可能会跟随模式中的某些内容

在评论中查询:

MATCH (p:Person)
WHERE '(?i).*I saw Smith today.*' IN p.alias
RETURN p

正则表达式'(?i).*I saw Smith today.*'将匹配

  1. 忽略文字字符串的大小写–'i SAW smith TOday',等等
  2. 文字串前后零个或多个字符–'Yes, I saw Smith today and he looked happy.'

但是添加 .* 不会神奇地使模式意味着 '.*Smith.*'。更重要的是,几乎不可能通过添加任何数量的正则表达式糖将 'I saw Smith today' 表示为 'Alex Smith' 的子集。相反,您应该在发送查询之前处理该字符串并弄清楚要在正则表达式中使用它的哪些部分。数据库如何知道 'Smith' 是您要使用的输入字符串的一部分?不管怎样,你知道它,你应该在发送查询之前知道它,并且只包括相关部分。

旁白:添加的正则表达式糖不起作用的示例以及原因

  1. You could insert a ? after each character in the pattern to make each character optional

    RETURN "Smith" =~ "I? ?s?a?w? ?S?m?i?t?h? ?t?o?d?a?y?"
    

But now your pattern is way too loosie goosie, and it will match strings like 'I sat today' and 'sam toy'. Moreover, it won't match 'Alex Smith' unless you prepend .*, but then it is even more loosie goosie and will match any string whatever.

  1. You could divide characters that belong together into groups and make the groups and the spaces between them optional.

    RETURN "Smith" =~ "(I)? ?(saw)? ?(Smith)? ?(today)?"
    

But this also is too broad, fails to match 'Alex Smith' and will match any string whatever if you prepend .*.

4) 错误的解决方案

我能想到的唯一 'solution' 是一个可怕的查询,它在空格处拆分字符串,将一些正则表达式糖连接到每个单词中,并将其作为谓词子句中的正则表达式进行比较。这真的很可怕,它假设你已经知道你想要匹配字符串中的单词而不是整个字符串,在这种情况下你应该在发送你的查询而不是在 Cypher 中。看着这丑陋的东西哭泣

WITH "I saw Paul today" AS paramString
MATCH (p:Person)
WHERE ANY (param IN split(paramString, ' ') 
           WHERE ANY (item IN p.collectionProperty 
                      WHERE item =~('(?i).*' + param)))
RETURN p

5) 结论

结论如下:

1) 更改模型。

  1. 像这样为每个别名保留一个节点

    CREATE (a:Alias)
    SET a.aliasId = "Alex Smith"
    
  2. 为这些节点创建全文索引。请参阅 blog and docs for the generic case and docs 了解 SDN。

  3. 将集合中现在具有别名的节点 属性 连接到具有关系的新别名节点。

  4. 查找所需的别名节点并遵循与 'has' 别名的节点的关系。一个节点仍然可以有很多别名,但您不再将它们存储在一个集合中 属性——您的查询逻辑将更加直接,并且您将受益于全文 lucene 索引。使用cypher时查询START n=node:indexName("query"),SDN中使用findAllByQuery()。这是查询使用全文索引所必需的。

  5. 您的查询最终可能看起来像

    START n=node:myIndex("aliasId:*smith")
    MATCH n<-[:HAS_ALIAS]-smith
    RETURN smith
    

2) 不要在数据库中完成所有工作。

如果您的程序应该接收像 'I saw Smith today' 这样的字符串并根据 'Smith' 上的模式匹配返回一个节点,那么不要发送 'I saw''today' 到数据库。您最好将 'Smith' 标识为应用程序中字符串的相关部分——当您发送查询时,您应该已经知道您想要什么。