12

我有一个域类,它有一个名为“alias”的属性,它是一个字符串数组列表,如下所示:

private List<String> alias;

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

我希望能够使用如下所示的存储库查询进行如下查询:“我今天看到了史密斯”并获取数组值输出“亚历克斯史密斯”:

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

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

我想做一个与数组值匹配的输入查询子字符串。

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

4

2 回答 2

18

概括

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

1)WHERE ... IN ...不做正则表达式

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

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

您可以将字符串用作正则表达式,通过使用诸如ANYor之类的谓词来测试集合上的匹配项FILTER

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

将返回该节点,因为它对“Jean-Paul”进行了成功的正则表达式匹配。

但是,这将具有糟糕的性能,因为您将对数据库中的item每一个collectionProperty都运行正则表达式。: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. 忽略文字字符串的大小写——<code>'i SAW smith TOday'等。
  2. 在文字字符串前后有零个或多个字符–<code>'是的,我今天看到了 Smith,他看起来很开心。

但是添加.*不会以某种方式神奇地使模式 mean '.*Smith.*'。更重要的是,几乎不可能通过添加任何数量的正则表达式糖来表达'I saw Smith today'为子集。'Alex Smith'相反,您应该在发送查询之前处理该字符串并确定要在正则表达式中使用它的哪些部分。数据库如何知道这'Smith'是您要使用的输入字符串的一部分?但是,您知道它,您应该在发送查询之前知道它,并且只包括该相关部分。

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

  1. 您可以在模式中的每个字符之后插入一个?,以使每个字符可选

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

但是现在你的模式太松散了,它会匹配像'I sat today''sam toy'这样的字符串。此外,它不会匹配'Alex Smith' 除非你预先.*添加,但它会更加宽松 goosie 并且将匹配任何字符串。

  1. 您可以将属于一起的字符分成组,并使组和它们之间的空格可选。

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

但这也太宽泛了,无法匹配'Alex Smith',并且无论您在前面添加什么,都将匹配任何字符串.*

4) 糟糕的解决方案

我能想到的唯一“解决方案”是一个可怕的查询,它将您的字符串拆分为空格,将一些正则表达式糖连接到每个单词中,并将其作为谓词子句中的正则表达式进行比较。这真的很可怕,它假设您已经知道要匹配字符串中的单词而不是整个字符串,在这种情况下,您应该在发送查询之前而不是在 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. 为这些节点创建全文索引。有关通用案例和SDN文档,请参阅博客文档。

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

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

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

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

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

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

于 2015-08-18T14:03:24.547 回答
5

你应该使用这样的东西:

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

它检查集合是否有一个“elem1”。

更多信息

于 2015-08-17T07:32:13.173 回答