用Cypher语言将两个合并合并为一个

时间:2019-11-13 16:29:08

标签: neo4j merge cypher

我正在尝试创建一个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-我希望它返回。

总而言之,我想要一个查询:

  1. 总是返回。
  2. 如果还不存在,则创建一个新的b node,它将a,c和d组合在一起。
  3. 如果确实存在,找到它并返回它。

在处理简单合并时,这非常简单: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"})

现在,应该这样处理: Required nodes for example

现在,我要授予人类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)

这使我们处于以下状态: Human A granted read:email to Human B

到目前为止,一切都很好。我可以重新运行查询,并保留相同的确切状态。

现在,我希望人类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)

现在问题来了: Reuse of grant rule problem

授予规则正在重用,这是一个有多个原因的问题,但仅说明一个明显的问题:当我要删除人员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"})

Required nodes for example

并尝试移动它:

// 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)

就像上次一样,我们最终得到一个正确的状态:

Human A granted read:email to Human B

现在,我想授予人类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)

现在,这给了我们:

reusing of grant rule

那是不对的。现在,我只能删除人类A到人类B的全部或全部。

很多,但是我希望它能为这个问题提供一些见识。

1 个答案:

答案 0 :(得分:3)

[更新(两次)]

此技巧可能适用于您的“三足合并”(创造一个术语)。问题中的第二个插图显示了三足合并的预期结果的示例,其中给定的Scope节点与3个特定节点有关系,而只有这3个节点。

诀窍是这样:向每个Grant添加2个属性(或3个,请参见下面的注释),以唯一地标识关联的Scope和关联的Human在演戏。如果您还与实际的ScopeHuman节点有关系,那么这无疑是冗余信息,但是它应确保您可以使用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)”上)将减少速度损失。