一、消除重复数据
重复数据
应该注意避免在图形中重复数据。当一些数据库需要某种形式的非规范化来提高一组查询的速度时,图数据库并不总是这样。重复数据消除为您提供了额外的好处,允许您通过节点进行查询,例如,查找购买了特定产品的其他客户,或者根据其他用户的评分查找类似的电影。
此外,复制图中的数据会增加图的大小以及查询可能需要检索的数据量。
新用例
我们有一个必须说明的新用例,如下所示。
用例#11:给定某种特定语言,则存在哪些相应的电影?
我们当前的实例模型如下所示:
如您所见,我们在数据模型中没有考虑语言,因此我们必须添加这类数据。
重复数据示例
假设我们向名为languages的图中的每个电影节点添加一个属性,该属性表示电影可用的语言。
下面是实例模型的外观:
在这里,我们看到所有Movie影节点的语言列表中都有英语。这是重复的数据,对于一个按比例缩放的数据库来说,将意味着存在大量的重复。
添加语言数据
在上面,我们了解到复制图中的数据可能会付出很昂贵的代价。为了说明数据的重复,您将向实例模型中的每个电影节点添加一个languages属性。
执行如下的Cypher将语言属性添加到图形的电影节点上:
MATCH (apollo:Movie {title: 'Apollo 13', tmdbId: 568, released: '1995-06-30', imdbRating: 7.6, genres: ['Drama', 'Adventure', 'IMAX']})
MATCH (sleep:Movie {title: 'Sleepless in Seattle', tmdbId: 858, released: '1993-06-25', imdbRating: 6.8, genres: ['Comedy', 'Drama', 'Romance']})
MATCH (hoffa:Movie {title: 'Hoffa', tmdbId: 10410, released: '1992-12-25', imdbRating: 6.6, genres: ['Crime', 'Drama']})
MATCH (casino:Movie {title: 'Casino', tmdbId: 524, released: '1995-11-22', imdbRating: 8.2, genres: ['Drama','Crime']})
SET apollo.languages = ['English']
SET sleep.languages = ['English']
SET hoffa.languages = ['English', 'Italian', 'Latin']
SET casino.languages = ['English']
下面是相应的实例模型:
查询语言
下面是一个支持我们新用例的查询:
用例#11:以特定语言提供哪些电影?
通过此查询,我们可以找到所有意大利语电影。
执行下面的查询并回答下一个问题。
MATCH (m:Movie)
WHERE 'Italian' IN m.languages
RETURN m.title
哪部电影以意大利语对话为特色?
二、重构重复数据
查询重复数据
下面是我们当前的实例模型,其中每个电影节点都有一个languages属性:
对于我们最新的用例:
用例#11:以特定语言提供哪些电影?
此查询查找所有意大利语电影:
MATCH (m:Movie)
WHERE 'Italian' IN m.languages
RETURN m.title
此查询所做的是检索所有电影节点,然后测试languages属性是否包含意大利语。数据模型有两个问题,尤其是当图表缩放时:
- 该语言的名称在许多电影节点中重复。
- 为了执行查询,必须检索所有电影节点。
这里的一个解决方案是将属性建模为节点。
将属性重构为节点
以下是我们用于重构的步骤:
- 我们获取每个电影节点的属性值并创建一个语言节点。
- 然后,我们在电影节点和语言节点之间创建IN_LANGUAGE关系。
- 最后,我们从Movie节点中删除languages属性。
这是重构图形以将属性值转换为节点的代码:
MATCH (m:Movie)
UNWIND m.languages AS language
WITH language, collect(m) AS movies
MERGE (l:Language {name:language})
WITH l, movies
UNWIND movies AS m
WITH l,m
MERGE (m)-[:IN_LANGUAGE]->(l);
MATCH (m:Movie)
SET m.languages = null
此代码遍历所有电影节点,并为找到的每种语言创建一个语言节点,然后使用IN\u语言关系创建电影节点和语言节点之间的关系。它使用Cypher UNWIND子句将languages属性列表的每个元素分隔为一个单独的行值,该行值稍后将在查询中处理。
重构后的实例模型是这样的:
只有一个节点的语言值为English,我们将从所有电影节点中删除languages属性。这消除了图形中的大量重复。
添加语言节点
实例模型将被重构为:
创建语言节点
执行以下代码重构图形,将语言属性值转换为语言节点:
MATCH (m:Movie)
UNWIND m.languages AS language
WITH language, collect(m) AS movies
MERGE (l:Language {name:language})
WITH l, movies
UNWIND movies AS m
WITH l,m
MERGE (m)-[:IN_LANGUAGE]->(l);
MATCH (m:Movie)
SET m.languages = null
修改Cypher查询
这是我们的用例在重构之前的Cypher。
MATCH (m:Movie)
WHERE 'Italian' IN m.languages
RETURN m.title
现在可以修改此查询,以使用新创建的语言节点。
MATCH (m:Movie)-[:IN_LANGUAGE]-(l:Language)
WHERE l.name = 'Italian'
RETURN m.title
这是处理语言的唯一用例,因此我们不需要在重构后重新测试所有查询。
添加流派节点
在上面重构中,通过在languages属性中获取数据并创建与电影相关的语言节点,消除了重复。
此挑战包括三个步骤:
- 修改并运行查询,以使用电影节点的genres属性中的数据,并使用IN_GENRE关系创建Genre节点,以将Movie节点连接到Genre节点。
- 从电影节点中删除genres属性。
- 重写用例的查询:演员在哪些戏剧电影中扮演角色?
将此查询复制到Neo4j桌面应用查询窗格,重写它,然后测试此Cypher查询,以与TomHanks一起测试此用例。
MATCH (p:Actor)-[:ACTED_IN]-(m:Movie)
WHERE p.name = 'Tom Hanks' AND
'Drama' IN m.genres
RETURN m.title AS Movie
应该返回电影:Apollo 13 和 Sleepless in Seattle。
执行查询
使用查询窗口展开每个电影的Genre属性,然后创建新的节点和关系。
完成此操作后,单击“Check Database”以验证图是否已正确重构。
三、消除节点中的复杂数据
由于节点用于存储有关特定实体的数据,因此,可能已经对生产节点进行了初始建模,以包含生产公司地址的详细信息。
由于以下几个原因,在这样的节点中存储复杂数据可能没有好处:
- 重复数据。许多节点可能在特定位置都会拥有生产公司,并且这样的数据在许多节点中都会重复。
- 与节点中的信息相关的查询要求能够检索所有节点。
重构复杂数据
如果节点中存在大量重复数据,或者如果不需要检索所有节点来获取复杂数据,那么用例的关键问题会表现得更好,那么,可以考虑重构图,如下图所示。
在这种重构中,如果有查询需要按状态过滤生产公司,那么根据状态进行查询会更快。根据State.name进行查询,而不是计算Production节点的所有状态属性。
如何进行图重构以处理复杂数据,这将取决于图缩放时查询的性能。