执行匹配交集
匹配交集是一个常见的用例,您需要搜索与所有一组输入节点都有关系的节点。
在本文的剩余部分,我们将使用内置的 movies 图示例进行演示。示例用例将是
给定一个演员姓名列表,查找包含所有这些演员的电影。
一种常见的首要(且错误)的做法如下所示
WITH ['Keanu Reeves', 'Hugo Weaving', 'Emil Eifrem'] as names
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name in names
RETURN m
上述查询返回的是至少包含给定演员之一的电影,而不是包含所有给定演员的电影。
我们需要其他方法来获得正确的结果。
通过匹配中输入节点的计数过滤共同节点
我们可以对上述查询做一些修改,以获取相关的 :Movie 节点集合。
这里的思路是,在我们的匹配中,我们想要的 :Movie 节点的不同匹配演员数量应等于输入集合的大小。
WITH ['Keanu Reeves', 'Hugo Weaving', 'Emil Eifrem'] as names
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name in names
WITH m, size(names) as inputCnt, count(DISTINCT p) as cnt
WHERE cnt = inputCnt
RETURN m
通过基于已匹配的不同节点进行过滤,即使在匹配的节点之间存在多个相同类型的关系,也能得到正确的答案。
这通常是查找匹配交集的最高效方法,但它要求输入列表中的所有元素必须唯一。
使用 WHERE ALL() 确保列表中的所有节点都与另一个节点有关联
另一种(但通常效率较低)的方法是收集输入节点,并使用 WHERE ALL() 谓词来确保所有收集的节点都与共同节点存在关系。
WITH ['Keanu Reeves', 'Hugo Weaving', 'Emil Eifrem'] as names
MATCH (p:Person)
WHERE p.name in names
WITH collect(p) as persons
MATCH (m:Movie)
WHERE ALL(p in persons WHERE (p)-[:ACTED_IN]->(m))
RETURN m
此方法的问题在于,由于最后的 MATCH 从所有 :Movie 节点开始,性能开销与 :Movie 节点的数量成正比。
我们可以通过从某个输入节点匹配到的 :Movie 节点开始,从而在一定程度上改进此查询,尽管这会增加查询的复杂性。
WITH ['Keanu Reeves', 'Hugo Weaving', 'Emil Eifrem'] as names
MATCH (p:Person)
WHERE p.name in names
WITH collect(p) as persons
WITH head(persons) as head, tail(persons) as persons
MATCH (head)-[:ACTED_IN]->(m:Movie)
WHERE ALL(p in persons WHERE (p)-[:ACTED_IN]->(m))
RETURN m
使用 APOC 对结果列表进行交集
某些用例可能会带来额外的复杂性。例如,我们希望结果节点与另一个匹配模式的结果或不同的结果集合进行交集。 在这种情况下,我们可以简单地使用 WHERE 子句来强制执行额外的模式或列表成员关系。
但当有多个列表需要交集时,仅使用 Cypher 实现会变得更加困难。
在使用 Neo4j 3.0.x 或更高版本时,结合 reduce() 函数和来自 APOC Procedures 的交集函数,我们可以对多个列表执行交集操作。
WITH ['Keanu Reeves', 'Hugo Weaving', 'Emil Eifrem'] as names
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name in names
WITH p, collect(m) as moviesPerActor
WITH collect(moviesPerActor) as movies
WITH reduce(commonMovies = head(movies), movie in tail(movies) |
apoc.coll.intersection(commonMovies, movie)) as commonMovies
RETURN commonMovies
此页面有帮助吗?