当前位置: 首页>编程语言>正文

Neo4j实战:Cypher分析Neo4j数据不一致

翻译自:https://neo4j.com/blog/run-cypher-to-analyze-neo4j-graph-database-inconsistencies/】
【阅读时间:9 分钟】

Neo4j实战:Cypher分析Neo4j数据不一致,第1张
Neo4j 实战

? ??????您以前是否想校验Neo4j图数据库中的数据不一致?
????????也许您正在合并来自不同来源的数据,或者项目迭代过程中变更数据模型而没有重新加载数据。或者检查Neo4j图数据库并查找问题。这篇博客将探讨一种校验数据不一致的方法。
????????Neo4j在存储数据时非常灵活,属性图模型使具有相同标签的节点具有不同的节点属性。让我用一个简短的例子进一步解释。
????????假设您有一个项目来记录著名演员的历史,您会立即想到在 Neo4j 浏览器前端输入:play movies?并加载?Cypher,快速入门。
????????以下是来自 Movies 例子中的某段Cypher:

CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967})
CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
CREATE (JoelS:Person {name:'Joel Silver', born:1952})
CREATE (Keanu)-[:ACTED_IN {roles:['Neo']}]-> (TheMatrix),
?????????(Carrie)-[:ACTED_IN {roles:['Trinity']}]-> (TheMatrix),
?????????(Laurence)-[:ACTED_IN {roles:['Morpheus']}]-> (TheMatrix),
?????????(Hugo)-[:ACTED_IN {roles:['Agent Smith']}]-> (TheMatrix),
?????????(LillyW)-[:DIRECTED]-> (TheMatrix),
?????????(LanaW)-[:DIRECTED]-> (TheMatrix),
?????????(JoelS)-[:PRODUCED]-> (TheMatrix)

? ? ? ? 当前数据模型如下:

Neo4j实战:Cypher分析Neo4j数据不一致,第2张
原数据模型

? ? ? ? 数据模型具有两个节点标签:Person?MoviePerson?Movie之间有两种关系:DIRECTED?和?ACTED_IN,本文仅关注节点标签。
? ? ? ? 标签为?Person 的节点有两个属性:born、name,标签为?Movie?的节点,具有三个属性:released、agline、title。
????????现在,团队负责人对您说,当演员开始担任主角时,人们会更感兴趣。此外,客户还要求知道某个 Person 是否赢得了奥斯卡奖。最后,您还要把 Movie 节点的 tagline 属性删除。
????????要实现这些需求,您可以修改Cypher:

CREATE (ToyStory4:Movie {title:'Toy Story 4', released:2019})
MERGE (Keanu:Person {name:'Keanu Reeves', born:1964})
SET Keanu.wonOscar = false, Keanu.filmDebut = 1985
MERGE (TomH:Person {name:'Tom Hanks', born:1956})
SET TomH.wonOscar = true, TomH.filmDebut = 1980
MERGE (TimA:Person {name:'Tim Allen', born:1953})
SET TimA.wonOscar = false, TimA.filmDebut = '1988 maybe?'
MERGE (AnnieP:Person {name:'Annie Potts', born:1952})
SET AnnieP.wonOscar = false, AnnieP.filmDebut = 1978
CREATE (Keanu)-[:ACTED_IN {roles:['Duke Caboom (voice)']}]-> (ToyStory4),
?????????(TomH)-[:ACTED_IN {roles:['Woody (voice)']}]-> (ToyStory4),
?????????(TimA)-[:ACTED_IN {roles:['Buzz Lightyear (voice)']}]-> (ToyStory4),
?????????(AnnieP)-[:ACTED_IN {roles:['Bo Peep (voice)']}]-> (ToyStory4)

????????在新的Cypher中,Movie 删除了'tagline' ,并为?Person 添加了两个新属性 :wonOscar、filmDebut
????????另外,请注意,为了避免创建重复节点,不使用?CREATE,而用?MERGE 查找和更新现有数据。
????????现在,我们的新模型如下所示:

Neo4j实战:Cypher分析Neo4j数据不一致,第3张
新数据模型

????????通过原数据模型和新数据模型的图片对比,我们可以看出模型差异。但是无法看出数据库中加载的数据符合哪种模型,以及需要更新的节点数。

1、Cypher查找属性名的变化

????????实际上,我们可以编写Cypher来检查数据是否不一致。
? ? ? ? 下面的Cypher查询将通过Neo4j图数据库的节点标签遍历节点,并返回不同属性名的节点标签。

/* Looks for Node Labels that have different sets of property keys */
WITH "MATCH (n:`$nodeLabel`)
WITH n
LIMIT 10000
UNWIND keys(n) as key
WITH n, key
ORDER BY key
WITH n, collect(key) as keys
RETURN '$nodeLabel' as nodeLabel, count(n) as nodeCount, length(keys) as keyLen, keys
ORDER BY keyLen" as cypherQuery
CALL db.labels()
YIELD label AS nodeLabel
WITH replace(cypherQuery, '$nodeLabel', nodeLabel) as newCypherQuery
CALL apoc.cypher.run(newCypherQuery, {})
YIELD value
WITH value.nodeLabel as nodeLabel, collect({ nodeCount: value.nodeCount, keyLen: value.keyLen, keys: value.keys}) as nodeInfoList
WHERE size(nodeInfoList) > 1
UNWIND nodeInfoList as nodeInfo
RETURN nodeLabel, nodeInfo.nodeCount as nodeCount, nodeInfo.keyLen as keyLen, nodeInfo.keys as keys
ORDER BY nodeLabel, keyLen

????????如果您使用?:play movies?从Neo4j示例数据集中加载的?Movie?数据?,然后执行ToyStory4 Cypher语句,则执行上面的Cypher语句将返回以下结果:

Neo4j实战:Cypher分析Neo4j数据不一致,第4张
Cypher结果集

????????您将看到,对于 MoviePerson,所有节点的属性名集合都不相同,需要您确定属性键之间的差异是否正确。
????????有时,只是某些节点确实没有该属性的数据,因此未创建该属性,通常是正确的。而有时候,这可能意味着加载的旧数据不符合数据模型中的新更改,执行此查询至少说明可能存在问题。

2、建立查询

????????上面的Cypher语句相当复杂,为了能解释其工作方式,我们将一句一句的依次创建Cypher语句并做相关说明。
????????首先,让我们仅关注带有 Movie 标签的节点。
????????我们想在数据库中的所有 Movie 节点上查找,并列出它们具有的属性键:

MATCH (n:Movie) RETURN keys(n)

????????这将返回如下内容:

Neo4j实战:Cypher分析Neo4j数据不一致,第5张
结果集

????????我们使用?keys() 返回节点的属性键。请注意,节点的属性键集的存储顺序不同,返回的结果也将不同。我们首先需要对键进行排序,以确保 [title,tagline,released] 与 [released,tagline,title] 相同:

MATCH (n:Movie)
UNWIND keys(n) as key
RETURN key
ORDER BY key

? ??????UNWIND keys(n) 将集合成员拆分成单独行返回,使用 ORDER BY 按字母顺序对键进行排序。检查输出结果,必须找出消除重复键名的方法。

Neo4j实战:Cypher分析Neo4j数据不一致,第6张
结果集

????????为了去除重复键,我们使用了两个 WITH 语句。第一个 WITH?能够使用?ORDER BY 排序,因此可以将有序键集传递给第二个 WITH,第二个 WITH 用?collect(key) 将属性键集聚合为列表。

MATCH (n:Movie)
UNWIND keys(n) as key
WITH n, key // need this to run ORDER BY
ORDER BY key
WITH n, collect(key) as keys
RETURN keys

????????注意,在每个 WITH 语句中都必须包含?WITH n?。在Cypher中,WITH?用于传递中间结果,并在使用时创建新的变量范围。您要保留的任何变量都必须在?WITH?中声明,以便后面语句可以继续使用它们。
????????第一个?WITH n?仅用于传递 n 到下一部分,以确保变量仍在范围内。
????????第二个 WITH n 用作分组,对于每个节点,n 将其属性键聚合为列表。
????????运行此查询语句返回的结果是:

Neo4j实战:Cypher分析Neo4j数据不一致,第7张
结果集

????????现在我们可以看到 keys 是有序的,并且每个?Movie?节点都获得了一组 keys 。我们要做的最后一件事是计算每个唯一 keys 集有多少个节点。
????????以下查询在 RETURN 子句中执行,Cypher 隐式地对 keys keyLen?分组,并使用?count(n) 对每个唯一键集的节点计数。

MATCH (n:Movie)
UNWIND keys(n) as key
WITH n, key
ORDER BY key
WITH n, collect(key) as keys
RETURN count(n) as nodeCount, length(keys) as keyLen, keys
ORDER BY keyLen

? ? ? ? 通过返回结果,可以看到 2 个带有 ['released','title'] 的节点和 37 个带有 ['released','tagline','title'] 的节点。

Neo4j实战:Cypher分析Neo4j数据不一致,第8张
结果集

3、执行所有节点标签的查询

????????现在,上面的查询语句适用于单个节点标签 Movie。我们的目标是使它适用于数据库中存在的所有节点标签,并且在不知道已经存在哪些节点标签的情况下执行。
????????实现此目标的第一步是执行下面的语句:

CALL db.labels()
YIELD label AS nodeLabel
RETURN nodeLabel

????????这将列出数据库中的所有节点标签。

Neo4j实战:Cypher分析Neo4j数据不一致,第9张
结果集

????????接下来是使用这些节点标签为每种节点标签创建单独的Cypher语句,我们可以使用db.labels() 返回的?nodeLabel?来创建每种标签的查询语句。

WITH "some cypher statement" as cypherQuery
CALL db.labels()
YIELD label AS nodeLabel
RETURN cypherQuery + " for " + nodeLabel

????????现在,我们必须用 Movie?Cypher语句代替“some cypher statement”,并进行以下修改:
????????1)Movie 替换为 $nodeLabel
? ? ? ? 2)添加 $nodeLabel nodeLabel to RETURN
? ? ? ? 3)CALL db.labels() 后添加 WITH replace(cypherQuery, '$nodeLabel', nodeLabel)?newCypherQuerydb.labels()
????????进行这些替换将得到以下查询。
? ? ? ? 执行查询会为每个查询生成单独的Cypher语句?NodeLabel。请注意,即使使用?$nodeLabel,这也不是实际的参数化Cypher调用,目前您无法参数化节点标签。
? ? ? ? $nodeLabel 用作字符串替换的占位符,调用 replace(…)会将 $nodeLabel 占位符更改为?db.labels()?返回的?nodeLabel 实际值?

WITH "MATCH (n:`$nodeLabel`)
UNWIND keys(n) as key
WITH n, key
ORDER BY key
WITH n, collect(key) as keys
RETURN '$nodeLabel' as nodeLabel, count(n) as nodeCount, length(keys) as keyLen, keys
ORDER BY keyLen" as cypherQuery
CALL db.labels()
YIELD label AS nodeLabel
WITH replace(cypherQuery, '$nodeLabel', nodeLabel) as newCypherQuery
RETURN newCypherQuery

? ? ? ? 执行上面的Cypher语句会返回以下结果:

Neo4j实战:Cypher分析Neo4j数据不一致,第10张
结果集

????????现在,我们对每个节点标签都有一个Cypher查询,我们可以使用 apoc.cypher.run() 来执行每个查询,同时需要您的数据库中安装APOC。如果尚未安装APOC,请阅读以下说明以安装APOC。

CALL apoc.cypher.run(newCypherQuery, {}) YIELD value

????????返回的值 apoc.cypher.run 包含已执行查询的结果。
????????对于Cypher查询中返回的每一行,value 都会生成一个映射,其中映射键是返回变量名称,而映射值是返回值,下面是示例的返回结果:

{
?"nodeCount": 2,
?"keyLen": 2,
?"nodeLabel": "Movie",
?"keys": [
?????????"released",
?????????"title"]
}

????????为了完成我们的查询,我们必须处理这些结果以确定哪些节点标签可能具有不同的属性键。
????????首先,我们将其 nodeLabel 用作分组 key 并聚合返回值,对 nodeLabel 使用 collect() ,最终每个Cypher查询会返回一行,该 nodeInfoList 变量包含从Cypher查询返回的所有数据。

WITH value.nodeLabel as nodeLabel, collect({
? ? ? ? nodeCount: value.nodeCount,
?????????keyLen: value.keyLen,
?????????keys: value.keys}) as nodeInfoList
WHERE size(nodeInfoList) > 1

????????接下来,在?WHERE?子句中使用 size(nodeInfoList) > 1 来检查每个Cypher查询中是否超过1行。如果只有1行,我们不返回。单行意味着对于带有该节点标签的所有节点都具有相同的属性键集。这意味着数据没问题,我们只想返回不同属性键集的节点标签。
????????该查询的最后一部分使用?UNWIND?来将 nodeInfoList 集合转换回单独的行 。我们还使用 ORDER BY nodeLabel?keyLen?按字母顺序对节点标签排序。

UNWIND nodeInfoList as nodeInfo
RETURN nodeLabel, nodeInfo.nodeCount as nodeCount, nodeInfo.keyLen as keyLen, nodeInfo.keys as keys
ORDER BY nodeLabel, keyLen

? ? ? ? 执行完成的查询,将产生以下结果(如前所示):

Neo4j实战:Cypher分析Neo4j数据不一致,第11张
结果集

????????整个查询中最后一个部分,我添加了以下限制:

WITH n LIMIT 10000

????????当对大型数据库执行此查询时,这提供了一种保护措施。
????????对于给定的节点标签,它将仅查看前 10,000 行。如果没有这种保护措施,则对于大型数据库,它很可能会耗尽内存。如果您不想只查看前 10,000 行,可以随意调整此限制,使用 SKIP?添加或尝试一些不同的采样技术。

4、结论

? ? ? ? 通过执行Cypher查询来检查Neo4j数据库中的数据不一致,我们只研究了相同节点标签的属性键之间的差异。根据您的特定数据和数据模型,这可能是正常的,也可能不是,您需要根据项目需求做出判断。
????????您可以创建此查询的其它一些变体:一个变体可以收回可能具有不一致数据的特定行,另一个变体可以检查特定属性键中是否存在不一致的数据值。尝试看看是否可以使用Neo4j存储过程的强大功能以及动态生成的Cypher查询的执行来自己生成变体。这些变体使用与本文中描述的完全相同的技术,只需要一点额外的逻辑。
????????在以后的博客文章中,将继续探讨检查Neo4j数据库的不同数据健康方面的其它查询。


https://www.xamrdz.com/lan/5g51994366.html

相关文章: