知识库

执行匹配交集

匹配交集是一种常见的用例,您需要搜索与一组输入节点都具有关系的节点。

在本文的其余部分,我们将使用内置的电影图进行演示。示例用例将是

给定一个演员姓名列表,查找包含所有给定演员的电影。

一种常见的第一个(错误的)方法是这样的

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 过程 的交集函数,我们可以跨多个列表执行交集。

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