我正在尝试创建一个Cypher,它合并的边缘比我在Cypher语言的ASCII艺术中无法管理的更多。
TLDR; 如何完成合并:
MERGE (a)-[:REL1]->(b:B)-[:REL2]->(c), (b)-[:REL3]->(d)
我有以下简化的密码查询来描述问题:
// ensure required nodes exists
MATCH (a:A {id: "<uuid1>"})
MATCH (c:C {id: "<uuid2>"})
MATCH (d:D {id: "<uuid3>"})
// Make B connect the nodes
MERGE (a)-[:REL1]->(b:B)-[:REL2]->(c)
MERGE (b)-[:REL3]->(d) // <- thats the main problem - a seperate merge to make this relation, but it should be part of the first merge.
// Conclude
RETURN a,b,c,d
此查询将起作用,但是当多次调用它时,b将被重用。我的意思是,这些关系的多个是由相同的b:(b)-:REL3->(d)
构成的。这在我的系统中是不允许的,因为我应该能够删除b,并且只影响第一个调用所创建的内容。
为确保b唯一,我可以这样做:
// ensure required nodes exists
MATCH (a:A {id: "<uuid1>"})
MATCH (c:C {id: "<uuid2>"})
MATCH (d:D {id: "<uuid3>"})
// ensure unique B
CREATE (b:B)
// Make B connect the nodes
MERGE (a)-[:REL1]->(b)-[:REL2]->(c)
MERGE (b)-[:REL3]->(d)
// Conclude
RETURN a,b,c,d
这个问题是,每次调用都会创建一个新的B节点,即使该路径已经存在。现在,这只是重复的数据,我也不想要。
我可以通过添加WITH/WHERE
语句来解决该问题
// ensure required nodes exists
MATCH (a:A {id: "<uuid1>"})
MATCH (c:C {id: "<uuid2>"})
MATCH (d:D {id: "<uuid3>"})
OPTIONAL MATCH (a)-[:REL1]->(existingB:B)-[:REL2]->(c)
OPTIONAL MATCH (b)-[:REL3]->(d)
WITH a,exisingB,c,d
WHERE existingB is null // query ends here and I end up with zero rows returned
// ensure unique B
CREATE (b:B)
// Make B connect the nodes
MERGE (a)-[:REL1]->(b)-[:REL2]->(c)
MERGE (b)-[:REL3]->(d)
// Conclude
RETURN a,b,c,d
但是,现在查询不返回a,b,c,d-我希望它返回。
总而言之,我想要一个查询:
b node
,它将a,c和d组合在一起。在处理简单合并时,这非常简单:MATCH > MERGE > RETURN
。唯一让我困惑的是,我看不到如何用一个MERGE命令来做到这一点。
据我所知,不可能合并多个MERGE命令,但我希望有人对此有解决方案。
让我们首先在访问管理示例中创建所需的节点:
// create required nodes
CREATE (:Human {name:"Human A"})
CREATE (:Human {name:"Human B"})
CREATE (:Human {name:"Human C"})
CREATE (:Scope {name:"read:email"})
现在,我要授予人类A代表人类B访问read:email的权限:
// grant "Human A" access to "read:email" on behalf of "Human B" - aka let Human A read Human B's email address
MATCH (humanA:Human {name:"Human A"})
MATCH (readEmail:Scope {name:"read:email"})
MATCH (humanB:Human {name:"Human B"})
MERGE (humanA)-[:IS_GRANTED]->(gr:Grant:Rule)-[:GRANTS]->(readEmail)
MERGE (gr)-[:ON_BEHALF_OF]->(humanB)
到目前为止,一切都很好。我可以重新运行查询,并保留相同的确切状态。
现在,我希望人类A也阅读:也发送电子邮件给HuamnC。相同的查询,新的“代表”。
// grant "Human A" access to "read:email" on behalf of "Human C" - aka let Human A read Human C's email address
MATCH (humanA:Human {name:"Human A"})
MATCH (readEmail:Scope {name:"read:email"})
MATCH (humanC:Human {name:"Human C"})
MERGE (humanA)-[:IS_GRANTED]->(gr:Grant:Rule)-[:GRANTS]->(readEmail)
MERGE (gr)-[:ON_BEHALF_OF]->(humanC)
授予规则正在重用,这是一个有多个原因的问题,但仅说明一个明显的问题:当我要删除人员A对人员B的电子邮件的访问权限时,它也会也移至人员C,因为它们共享相同的规则。
现在有人会说,为什么不先合并“代表”以避免这个问题? 让我们尝试重新开始,但是添加另一个范围“ read:phone”:
// create required nodes
CREATE (:Human {name:"Human A"})
CREATE (:Human {name:"Human B"})
CREATE (:Human {name:"Human C"})
CREATE (:Scope {name:"read:email"})
CREATE (:Scope {name:"read:phone"})
并尝试移动它:
// grant "Human A" access to "read:email" on behalf of "Human B" - aka let Human A read Human B's email address
MATCH (humanA:Human {name:"Human A"})
MATCH (readEmail:Scope {name:"read:email"})
MATCH (humanB:Human {name:"Human B"})
MERGE (humanA)-[:IS_GRANTED]->(gr:Grant:Rule)-[:ON_BEHALF_OF]->(humanB)
MERGE (gr)-[:GRANTS]->(readEmail)
就像上次一样,我们最终得到一个正确的状态:
现在,我想授予人类A访问Huamn B的read:phone的权限:
// grant "Human A" access to "read:phone" on behalf of "Human B" - aka let Human A read Human B's phone number
MATCH (humanA:Human {name:"Human A"})
MATCH (readPhone:Scope {name:"read:phone"})
MATCH (humanB:Human {name:"Human B"})
MERGE (humanA)-[:IS_GRANTED]->(gr:Grant:Rule)-[:ON_BEHALF_OF]->(humanC)
MERGE (gr)-[:GRANTS]->(readPhone)
现在,这给了我们:
那是不对的。现在,我只能删除人类A到人类B的全部或全部。
很多,但是我希望它能为这个问题提供一些见识。
答案 0 :(得分:3)
[更新(两次)]
此技巧可能适用于您的“三足合并”(创造一个术语)。问题中的第二个插图显示了三足合并的预期结果的示例,其中给定的Scope
节点与3个特定节点有关系,而只有这3个节点。
诀窍是这样:向每个Grant
添加2个属性(或3个,请参见下面的注释),以唯一地标识关联的Scope
和关联的Human
在演戏。如果您还与实际的Scope
和Human
节点有关系,那么这无疑是冗余信息,但是它应确保您可以使用MERGE
创建唯一的Grant
节点每组独特的3条腿。
例如,假设name
的值是唯一的,以正确执行第二次查询(在您的更新中):
MATCH (humanA:Human {name:"Human A"})
MATCH (readEmail:Scope {name:"read:email"})
MATCH (humanC:Human {name:"Human C"})
MERGE (humanA)-[:IS_GRANTED]->(g:Grant:Rule {for: humanC.name, scope: readEmail.name})
MERGE (g)-[:ON_BEHALF_OF]->(humanC)
MERGE (g)-[:GRANTS]->(readEmail)
注意:
第一个MERGE
确保g
节点仅与“人类A”相关联,因此无需向具有唯一标识符的g
添加第三个属性对于“人类A”-并且仅当时,您始终以IS_GRANTED
关系开始三足合并。
但是,如果您有时可以创建以其他“一条腿”之一开头的Grant
节点,那么您需要为每条腿都具有一个属性。
即使在创建关联关系之后,也必须保留Grant
属性,以便将来的三足路合并将正常工作。
严格来说,您实际上不需要执行最后两个MERGE
中的任何一个,因为Grant
节点将包含足够的信息来动态获取缺失的(虚拟)分支,如所须。例如,要代表“人类C”获取涉及“人类A”的Scope
:
MATCH
(:Human {name:"Human A"})-[:IS_GRANTED]->(g {for: "Human C"}),
(scope:Scope {name: g.scope})
RETURN scope
这比具有实际关系的效率低,但节省了存储空间。创建适当的indexes(例如,在这种情况下,例如在“:Scope(name)”上)将减少速度损失。