知识库

UNION 查询的替代方案

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

在本文中,我们将介绍各种示例案例,其中 UNION 不是必需的,而一个简单的 Cypher 查询就可以完成任务。

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

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

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

第一次尝试可能如下所示

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

由于 Cypher 仅允许在每个匹配路径中遍历一个关系,因此这不会将基努·里维斯作为联合演员返回。用于匹配电影节点的 :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

这允许基努·里维斯按预期出现在结果中。但是,这比需要的复杂,如果我们稍后需要处理联合结果,我们就无法继续处理它。

对于这种情况,我们不使用 UNION,而是匹配到中心节点,然后进行额外的 MATCH 到联合演员

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

通过使用第二个 MATCH,我们已经分解了所使用的路径,因此当我们匹配回电影的联合演员时,:ACTED_IN 关系不再有任何限制。到基努·里维斯的关联被视为 MATCH 中的任何其他关联,并且基努·里维斯出现在结果中。

可选连接

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

例如,基于之前关于基努·里维斯联合演员的查询,也许我们想找到不仅通过基努·里维斯出演的电影,而且通过类似电影找到联合演员。

我们可以通过 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

我们正在对 :SIMILAR 使用可变长度关系,其下限为 0。

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

这将允许 movie 匹配到基努·里维斯出演的电影,以及如果存在 :SIMILAR 关系,则匹配到任何 :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