知识库

UNION 查询的替代方案

虽然 UNION 在某些情况下很有用,但通常通过对查询进行微小更改就可以完全避免它们。

在本文中,我们将展示 UNION 不必要的各种示例情况,而一个简单的 Cypher 查询就能解决问题。

起始节点 + 所有其他通过公共节点的节点

在某些情况下,您希望以某种方式连接到公共节点的所有节点,包括起始节点,并且所有这些节点都通过相同的模式连接。

一个典型的案例是,对于给定的演员,获取他们参演过的所有电影的演员,包括起始演员。

第一次尝试可能看起来像这样

MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor

由于 Cypher 在每个匹配路径中只允许一次关系遍历,这将不会返回 Keanu Reeves 作为合作演员。用于匹配电影节点的 :ACTED_IN 关系在查找合作演员时不能再次被反向遍历。

这可以通过 UNION 来解决

MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor
UNION
MATCH (p:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)
RETURN movie, p as coactor

这使得 Keanu Reeves 能够按预期出现在结果中。然而,这比实际需要的更复杂,并且如果以后需要,我们无法继续处理联合结果。

我们可以不使用 UNION,而是匹配到中心节点,然后额外 MATCH 到合作演员

MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)
MATCH (movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor

通过使用第二个 MATCH,我们打破了使用的路径,因此当我们将电影的合作演员再次匹配出去时,对于 :ACTED_IN 关系不再有任何限制。与 Keanu Reeves 的关系被视为 MATCH 中的任何其他关系,并且 Keanu Reeves 出现在结果中。

可选连接

对于某些查询,我们可能希望从两个相似的模式中获取结果,但其中一个模式可能存在一些额外的遍历,而另一个模式没有。

例如,在之前关于 Keanu Reeves 的合作演员的查询基础上,我们可能不仅想通过 Keanu Reeves 参演的电影,还想通过相似的电影来查找合作演员。

我们可以通过 UNION 查询来做到这一点

MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)
MATCH (movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor
UNION
MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(:Movie)-[:SIMILAR]-(movie:Movie)
MATCH (movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor

然而,这两个匹配模式足够相似,实际上我们可以在不使用 UNION 的情况下获得想要的结果。

MATCH (:Person {name:'Keanu Reeves'})-[:ACTED_IN]->(:Movie)-[:SIMILAR*0..1]-(movie:Movie)
MATCH (movie)<-[:ACTED_IN]-(coactor)
RETURN movie, coactor

我们正在使用一个下限为 0 的可变长度关系 :SIMILAR。

这意味着模式中两个连接的节点可能是同一个节点,它们之间没有实际的关系。

这将允许 `movie` 同时匹配 Keanu Reeves 参演的电影以及任何存在的 :SIMILAR 电影。

这种 [*0..1] 技巧基本上代表了一个可选连接,当我们需要将一个节点和连接的节点以相同的方式处理(并且如果需要,用相同的变量表示)在模式中时可以使用。

可选连接以获取正确的节点

在上面的例子中,我们的可选连接是在 :Movie 节点之间,允许我们获取同时连接到起始节点和相邻节点的节点。

当初始节点可能不是我们想要的,而是可能在其之外的相邻节点时,我们也可以使用这种方法。

考虑一个社交图,人们可以推荐许多东西,包括 :Books、:Movies、:Games 等

(:Person)-[:FRIENDS_WITH]->(:Person)
(:Person)-[:RECOMMENDS]->(:Movie)
(:Person)-[:RECOMMENDS]->(:Book)
(:Person)-[:RECOMMENDS]->(:Game)
(:Movie)-[:BASED_ON]->(:Book)
(:Movie)-[:BASED_ON]->(:Game)
(:Game)-[:BASED_ON]->(:Movie)
(:Game)-[:BASED_ON]->(:Book)
(:Book)-[:BASED_ON]->(:Game)
(:Book)-[:BASED_ON]->(:Movie)

如果我们想返回朋友推荐的电影,只返回朋友推荐的 :Movie 节点就足够简单。

但如果我们还想返回与非电影推荐相关的电影(例如基于推荐书籍或其他媒体的电影),那么查询就会复杂一些。

我们可以使用 UNION 来做到这一点

MATCH (me:Person {name:'Keanu Reeves'})-[:FRIENDS_WITH]-(friend)-[:RECOMMENDS]->(movie:Movie)
RETURN friend, movie
UNION
MATCH (me:Person {name:'Keanu Reeves'})-[:FRIENDS_WITH]-(friend)-[:RECOMMENDS]->()-[:BASED_ON]->(movie:Movie)
RETURN friend, movie

更好的方法是放弃 UNION 并使用可选连接

MATCH (me:Person {name:'Keanu Reeves'})-[:FRIENDS_WITH]-(friend)-[:RECOMMENDS]->()-[:BASED_ON*0..1]->(movie:Movie)
RETURN friend, movie

如果推荐的项目是 :Movie,它将被包含在内;如果它是一个 :Movie 所基于的东西,那么该电影也将被包含在内。

如果我们还想获取 :BASED_ON 链中任意位置的电影(例如,基于游戏的推荐书籍,而游戏又是基于电影的),我们可以省略关系的上限

MATCH (me:Person {name:'Keanu Reeves'})-[:FRIENDS_WITH]-(friend)-[:RECOMMENDS]->()-[:BASED_ON*0..]->(movie:Movie)
RETURN friend, movie
© . All rights reserved.