如果任何一个有效,Cypher将查询多个关系

时间:2017-01-30 18:11:46

标签: neo4j cypher

我的图表设置如下:

(:User)-[:LIVES_IN_COUNTRY]->(:Country)
(:User)-[:LIVES_IN_STATE]->(:State)
(:User)-[:LIVES_IN_CITY]->(:City)

我尝试构建一个动态密码查询来检查所有关系,但只返回那些有效的结果

例如,如果我只知道国家名称和州名,那么我只传递国家和州的有效名称和"不可用"对于城市。我尝试过这样的事情:

Match(n:User)
Match(n)-[:LIVES_IN_COUNTRY]->(:Country {name:'Canada'})
Match(n)-[:LIVES_IN_STATE]->(:State {name:'Ontario'}) 
Match(n)-[:LIVES_IN_CITY]->(:City {name:'Not Available'}) 

但是,在这种情况下,不会返回任何内容。如果城市名称不可用,我如何才能获得加拿大和安大略省的用户?

也许有可能使用单独的密码查询,但我想知道是否可以在一个密码查询中处理

我尝试使用可选项,但它会返回所有国家/地区,州等所有用户。不确定是否可以使用可选项或查询

中的错误
Match(n:User) 
OPTIONAL Match (n)-[:LIVES_IN_COUNTRY]->(:Country{name:'Canada'}) 
OPTIONAL Match (n)-[:LIVES_IN_STATE]->(:State {name:'Ontario'}) 
OPTIONAL Match (n)-[:LIVES_IN_CITY]->(:City {name:'Not Available'}) 
Return n.username,n.country, n.state, n.city

1 个答案:

答案 0 :(得分:3)

尝试使用OPTIONAL MATCH代替国家,州和城市的MATCH。这将处理不匹配,而不会从结果中删除行。

修改

我错过了这是一个通用查询,而不是您从特定用户查询的查询。

我认为最好的方法是在数据不可用时完全取消匹配(如果你正在用Java或其他语言组装查询)。

但如果你不能这样做,我们可以使用另一种方法。

首先,我会将您的关系类型更改为:LIVES_IN,这将让我们稍后更轻松地进行匹配。

这个的方法是首先在国家,州和城市使用OPTIONAL MATCH,然后将它们收集到一个列表中(删除数据不可用的空节点),然后对一个人进行匹配同一个人对集合中的所有节点都有一个LIVES_IN关系。

WITH {country:'Canada', state:'Ontario', city:'Not Available'} as params
OPTIONAL MATCH (c:Country{name:params.country})
OPTIONAL MATCH (s:State{name:params.state})
OPTIONAL MATCH (ci:City{name:params.city})
WITH FILTER(loc in [c, s, ci] WHERE loc IS NOT null) as locations
WITH locations, LAST(locations) as place
MATCH (n:User)-[:LIVES_IN]->(place)
WHERE ALL(loc in locations WHERE (n)-[:LIVES_IN]->(loc))
RETURN n

不是匹配所有用户然后执行WHERE ALL,而是将我们潜在的用户组缩小到居住在最窄位置的用户(如果可用(城市,州,然后是国家)。

编辑 - 将查询扩展到其他节点/关系

对上述查询的警告,它确实需要至少一个位置(国家,州或城市)。如果这些都不可用,则查询将失败。如果您需要考虑这种可能性,请在评论中告诉我。

至于将其扩展到其他节点和不同的关系,它是可能的,但它确实需要更多的思考来找出方法。如果有几个东西属于同一类型,并且如果一个类型的所有东西都在db中由相同标签的节点表示,那么它会有所帮助。

在您的体育示例中,您很可能会传递查询的运动名称列表,并且您有几个:每项运动的运动节点。

修改后的查询可能如下所示:

WITH {country:'Canada', state:'Ontario', city:'Not Available', sports:['Hockey', 'Karate']} as params
WITH params, [(s:Sport)-[*0]-() WHERE s.name in params.sports | s] as sports
// keep the location section of your query the same
OPTIONAL MATCH (c:Country{name:params.country})
OPTIONAL MATCH (s:State{name:params.state})
OPTIONAL MATCH (ci:City{name:params.city})
WITH sports, FILTER(loc in [c, s, ci] WHERE loc IS NOT null) as locations
WITH sports, locations, LAST(locations) as place
MATCH (n:User)-[:LIVES_IN]->(place)
WHERE ALL(loc in locations WHERE (n)-[:LIVES_IN]->(loc))
// now do additional filtering on sports played
AND ALL(sport in sports WHERE (n)-[:PLAYS]->(sport))
RETURN n

最复杂的部分是第二行的模式理解(仅在Neo4j 3.1及更高版本中可用):

[(s:Sport)-[*0]-() WHERE s.name in sports | s] as sports

虽然单独使用(s:Sport)会更容易,但Cypher并没有认识到这是一种模式,所以我们必须通过使用0长度关系的模式来欺骗它(节点匹配只对自己而言)。

这样做的原因是它需要你的体育名称列表参数,并返回一个相关的列表:体育节点。

如果您传入一个空的体育列表,此模式理解将导致一个空列表,您可以继续查询。

如果我们尝试了不同的方法,请将您的列表UNWIND到体育名称行(意图匹配:体育与这些名称),如果在空列表上执行它将导致没有行,这将阻止查询从执行任何进一步。

编辑 - 所有字段都是可选的

所有字段都是可选的,这会带来效率问题。

在我的查询中,我使用可能匹配的最小地理位置匹配某个位置的用户。这有助于减少:此级别的用户是一个固定时间操作,因为我们实际上不必检查所有用户和过滤器,我们只需遵循从位置到相关集合的关系用户。随着每次后续匹配或过滤,匹配用户的集合继续下降,这有利于提高效率。

但如果集合中没有位置,则匹配和查询将失败。如果我改为使用OPTIONAL MATCH,我会得到一个null,这对于在其余的模式匹配中重用是没用的。

唯一的方法是与所有用户匹配:用户优先,然后从整个用户集中进行过滤,因此查询的性能将受到影响,并且随着用户节点数量的增长而减慢。只要每个字段都是可选的,没有可靠的起始位置来匹配用户,就可能无法解决这个问题。

WITH {country:'Canada', state:'Ontario', city:'Not Available', sports:['Hockey', 'Karate']} as params
WITH params, [(s:Sport)-[*0]-() WHERE s.name in params.sports | s] as sports
OPTIONAL MATCH (c:Country{name:params.country})
OPTIONAL MATCH (s:State{name:params.state})
OPTIONAL MATCH (ci:City{name:params.city})
WITH sports, FILTER(loc in [c, s, ci] WHERE loc IS NOT null) as locations
// now start matching using the set of all :Users
MATCH (n:User)
WHERE CASE WHEN SIZE(locations) <> 0 THEN
    ALL(loc in locations WHERE (n)-[:LIVES_IN]->(loc))
  ELSE true END
// now do additional filtering on sports played
AND CASE WHEN SIZE(sports) <> 0 THEN 
    ANY(sport in sports WHERE (n)-[:PLAYS]->(sport))
  ELSE true END
RETURN n

EDIT - 帮助处理可选参数的案例陈述

我意识到如果没有位置,或者没有传入任何运动(由于空列表上的ALL()函数,这将是假的),我给出的查询将失败,所以我编辑了上面的查询以便使用CASE语句仅在集合不为空时才执行这些评估。

我还将体育功能从ALL()更改为ANY(),以满足您回放玩过任何体育运动的用户的要求,而不是那些参加所有体育运动的用户。

编辑 - 添加属性处理。

如果我们希望用户年龄介于18到30岁之间,我们可以添加更多过滤

WITH {country:'Canada', state:'Ontario', city:'Not Available', sports:['Hockey', 'Karate'], ageMin:18, ageMax:25} as params
WITH params, [(s:Sport)-[*0]-() WHERE s.name in params.sports | s] as sports
OPTIONAL MATCH (c:Country{name:params.country})
OPTIONAL MATCH (s:State{name:params.state})
OPTIONAL MATCH (ci:City{name:params.city})
WITH sports, FILTER(loc in [c, s, ci] WHERE loc IS NOT null) as locations, params.ageMin as ageMin, params.ageMax as ageMax
// now start matching using the set of all :Users
MATCH (n:User)
WHERE CASE WHEN SIZE(locations) <> 0 THEN
    ALL(loc in locations WHERE (n)-[:LIVES_IN]->(loc))
  ELSE true END
// now do additional filtering on sports played
AND CASE WHEN ageMin <> 0 THEN
    n.age >= ageMin
  ELSE true END
AND CASE WHEN ageMax <> 0 THEN
    n.age <= ageMax
  ELSE true END 
AND CASE WHEN SIZE(sports) <> 0 THEN 
    ANY(sport in sports WHERE (n)-[:PLAYS]->(sport))
  ELSE true END
RETURN n

修改

您原来的要求是您要传递有效的地名或&#34;不可用&#34;,但让我们看看我们可以做些什么来处理无效的地名,但仍保留使用&#34;不可用&#34;

的正确行为

为了帮助我们,让我们创建一个:Invalid节点,我们将其用于“#spike&#34;我们的位置以防止任何进一步的匹配。

CREATE (:Invalid)

现在我们将对以下内容执行可选匹配:无效节点并将其包含在位置中,但我们会在其上添加谓词,以便只有当其中一个城市,国家/地区,或状态OPTIONAL MATCHES失败,但相关参数不适用&#39;。

如果你试图选择与艾伯塔省相匹配的话。对于状态但节点不存在,则状态节点的变量将为空,并且由于关联参数不是“不适用”,因此:无效节点将为出现在位置列表中,并且会阻止任何人匹配(因为如果位置列表非空,则人必须具有:与列表中所有元素的LIVES_IN关系)

WITH {country:'Canada', state:'Ontario', city:'Not Available', sports:['Hockey', 'Karate'], ageMin:18, ageMax:25} as params
WITH params, [(s:Sport)-[*0]-() WHERE s.name in params.sports | s] as sports
OPTIONAL MATCH (c:Country{name:params.country})
OPTIONAL MATCH (s:State{name:params.state})
OPTIONAL MATCH (ci:City{name:params.city})
OPTIONAL MATCH (inv:Invalid)
 WHERE (c is null AND params.country <> 'Not Available') OR
       (s is null AND params.state <> 'Not Available') OR
       (ci is null AND params.city <> 'Not Available')
WITH sports, FILTER(loc in [c, s, ci, inv] WHERE loc IS NOT null) as locations, params.ageMin as ageMin, params.ageMax as ageMax
// now start matching using the set of all :Users
MATCH (n:User)
WHERE CASE WHEN SIZE(locations) <> 0 THEN
    ALL(loc in locations WHERE (n)-[:LIVES_IN]->(loc))
  ELSE true END
// now do additional filtering on sports played
AND CASE WHEN ageMin <> 0 THEN
    n.age >= ageMin
  ELSE true END
AND CASE WHEN ageMax <> 0 THEN
    n.age <= ageMax
  ELSE true END 
AND CASE WHEN SIZE(sports) <> 0 THEN 
    ANY(sport in sports WHERE (n)-[:PLAYS]->(sport))
  ELSE true END
RETURN n