在本文中,我们将学习:
- 为什么特定的关系是有益的。
- 动态创建关系以特定化图中的关系。
一、使用特定关系
(一)图中的关系
Neo4j作为本机图数据库实现,以便快速实现遍历关系。
在某些情况下,基于关系类型而不是节点中的属性来查询图形更为有效。
让我们看一个新的用例:
用例#12:演员在特定年份演了哪些电影?
我们可以针对当前图执行下面的查询:
MATCH (p:Actor)-[:ACTED_IN]-(m:Movie)
WHERE p.name = 'Tom Hanks' AND
m.released STARTS WITH '1995'
RETURN m.title AS Movie
返回结果将是:Apollo 13
那么,问题来了:如果Tom Hanks在1995年演了50部电影如何查询呢?自然,查询需要检索Tom Hanks主演的所有电影,然后检查released属性的值。如果汤姆·汉克斯总共出演了1000部电影,又如何呢?那么,又需要评估所有这些电影节点。
下面是另一个新用例:
用例#13:哪些演员或导演在特定年份工作?
同样,我们可以对当前图形执行下面的查询:
MATCH (p:Person)--(m:Movie)
WHERE m.released STARTS WITH '1995'
RETURN DISTINCT p.name as `Actor or Director`
返回值将是:Tom Hanks 和 Martin Scorsese
此查询的性能更差,因为为了返回结果,必须检索所有电影节点。可以想象,如果图表包含数百万部电影,那么这将是一个非常昂贵的查询。
(二)重构以专门化关系
关系的遍历速度很快,并且不会占用图形中的大量空间。在前两个查询中,数据模型将受益于节点之间的专门关系。
因此,例如,除了ACTED_IN和DIRECTED关系之外,我们还可以添加包含特定年份信息的关系。
- ACTED_IN_1992
- ACTED_IN_1993
- ACTED_IN_1995
- DIRECTED_1992
- DIRECTED_1995
首先,对于大型电影图来说,似乎有很多关系,但如果最新的两个查询都是重要的用例,那么这是值得做的。
下面是我们的实例模型目前的样子:
在我们特定化关系的大多数情况下,我们会保留原始的泛型关系,因为现有查询仍然需要使用它们。
重构图以添加这些特定关系的代码中可以使用APOC库来实现。
下面是重构图中ACTED_IN关系的代码,将在接下来的任务中执行:
MATCH (n:Actor)-[r:ACTED_IN]->(m:Movie)
CALL apoc.merge.relationship(n,
'ACTED_IN_' + left(m.released,4),
{},
m ) YIELD rel
RETURN COUNT(*) AS `Number of relationships merged`
其中使用了apoc.merge.relationship过程,允许我们在图中动态创建关系。它使用电影节点的released属性中最左边的4个字符来创建关系的名称。
重构的结果是,前两个查询可以被重写,对于大型图来说,它们的性能肯定会更好:
以下是第一个查询的重写形式:
MATCH (p:Actor)-[:ACTED_IN_1995]-(m:Movie)
WHERE p.name = 'Tom Hanks'
RETURN m.title AS Movie
对于此查询,将遍历特定的关系,但检索到的电影节点较少。
下面是我们重写第二个查询的样子:
MATCH (p:Person)-[:ACTED_IN_1995|DIRECTED_1995]-()
RETURN p.name as `Actor or Director`
对于此查询,因为年份是关系类型,所以我们根本不需要检索任何电影节点。
二、特定化ACTED_IN和DIRECTED 关系
接下来,我们将继续修改实例模型以匹配下面的图。此图中使用了特定的ACTED_IN 和 DIRECTED关系。
实现这一目标,需要两步操作:
- 重构所有的ACTED_IN 关系
- 重构所有的 DIRECTED关系
(一)重构所有的ACTED_IN 关系
执行以下代码,根据每个节点发布属性的年份创建一组新的关系。
例如,阿波罗13号于1995年上映,因此将在阿波罗13号和电影中的任何演员之间创建一个额外的ACTED\u in\u 1995。
MATCH (n:Actor)-[:ACTED_IN]->(m:Movie)
CALL apoc.merge.relationship(n,
'ACTED_IN_' + left(m.released,4),
{},
{},
m ,
{}
) YIELD rel
RETURN count(*) AS `Number of relationships merged`;
它应该创建5个关系。
通过此重构,我们现在可以确认重写的查询适用于用例:
用例#12:演员在特定年份演了哪些电影?
为了验证查询是否已成功运行,我们可以尝试使用新创建的ACTED_IN_1995关系来查看Tom Hanks在1995年发布的电影中扮演的角色。
MATCH (p:Actor)-[:ACTED_IN_1995]->(m:Movie)
WHERE p.name = 'Tom Hanks'
RETURN m.title AS Movie
返回结果只有一部电影:Apollo 13
(二)重构所有的 DIRECTED关系
我们可以使用相同的方法在导演和电影之间创建DIRECTED_{year}关系。
修改刚刚运行的代码以匹配以下模式。
MATCH (n:Director)-[:DIRECTED]→(m:Movie)
然后修改过程调用,将关系的前缀更改为DIRECTED_。
它应该创建2个关系。
(三)测试模型
通过这次重构和之前的重构,我们现在可以确认重写的查询适用于用例:
用例#12:演员在特定年份演了哪些电影?
MATCH (p:Person)-[:ACTED_IN_1995|DIRECTED_1995]->()
RETURN p.name as `Actor or Director`
返回结果是:Tom Hanks 和 Martin Scorsese
三、特定化RATED 关系
在上面的任务中,我们向ACTED_IN和DIRECTED关系的图中添加了许多特定的关系。实现此目标需要3个步骤。
在我们当前的图中,用户节点和电影节点之间存在RATED关系。
假设我们想要改进此查询的性能,于是出现下面的新任务要求:
用例#9:哪些用户给电影的评分为5?
(一)为什么要建立特定的关系?
让我们举一个实际的例子。运行下面的Cypher代码,我们不妨用电影Apollo 13测试此用例。
MATCH (u:User)-[r:RATED]->(m:Movie)
WHERE m.title = 'Apollo 13' AND
r.rating = 5
RETURN u.name as Reviewer
返回一个用户:Sandy Jones
如果图中有数千个用户呢。此查询需要遍历所有RATED关系并评估RATED属性。对于大型图,求值越多意味着查询处理时间越长。
在本次挑战中,您将专门化RATED关系以反映RATED。与重构不同,重构中我们从节点中删除了genres 和 languages 属性,我们不会从RATED关系中删除RATED属性。这是因为我们可能需要它来进行查询,该查询具有对关系的引用,并且需要返回评级值。
下面是我们将重构的实例模型:
(二)创建特定的RATED_{rating}关系
要通过此挑战,必须使用上面介绍中获得的知识,并使用apoc.merge.relationship合并图中的关系。
您必须搜索的模式是:
MATCH (u:User)-[r:RATED]→(m:Movie)
作为第二个参数传递的关系类型应为:
'RATED_'+ r.rating