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 合并的结果继续处理,就会受限。
与其使用 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 共同演员查询基础上,可能希望查找的不仅是他参演的电影中的共同演员,还包括相似电影中的演员。
我们可以通过 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
然而这两个 MATCH 模式足够相似,实际上可以在不使用 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 能够匹配 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,则直接包含;如果它是某部电影的来源(如书籍等),该电影也会被包含在内。
如果我们还想获取 :BASED_ON 链上任意层级的电影(例如,基于游戏再基于电影的推荐书籍),可以省略关系的上限。
MATCH (me:Person {name:'Keanu Reeves'})-[:FRIENDS_WITH]-(friend)-[:RECOMMENDS]->()-[:BASED_ON*0..]->(movie:Movie)
RETURN friend, movie
此页面有帮助吗?