【问题标题】:Combine two merges into one in the Cypher language在 Cypher 语言中将两个合并合并为一个
【发布时间】:2019-11-13 16:29:08
【问题描述】:

我正在尝试制作一个 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-&gt;(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 &gt; MERGE &gt; 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"})

现在应该给我们留下这个:

现在我想代表人类 B 授予人类 A 阅读:电子邮件的权限:

// 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 也阅读:电子邮件给 Humn C。相同的查询,新的“代表”。

// 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 访问 Humn 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 全部或全部删除。

这很多,但我希望它能对问题提供一些见解。

【问题讨论】:

  • 因此,关键是确保b:B 是唯一的,但除了REL1REL2REL3 与@987654351 的连接之外,您没有其他方法可以确定它是否唯一@、BC?即您没有唯一的 uuid 来查找或创建 B?
  • @DaveBennett 正确,b:B 只能与 REL1, REL2, REL3 一起存在一次。即使我在b:B 中添加了一个 uuid,我也不知道应该合并哪个,因为关系是定义它的主要内容。
  • 最后一个例子,带有 read:phone 和 read:email 节点,看起来差不多。如果您需要删除特定的授予功能,您可以匹配相关模式,然后删除与 read:phone 权限的 :GRANTS 关系,检查 :Grant 节点的程度,如果没有更多,则仅删除该节点:存在 GRANTS 关系。或者,如果您只需要授予只有 1 个特权,您可以在授予节点上为此设置一个属性......并且要么让它单独确定它,要么两者兼而有之。这允许属性帮助您的 MERGE,然后您可以 MERGE 到特权。

标签: neo4j merge cypher


【解决方案1】:

[更新(两次)]

这个技巧可能适用于您的“3-legged-merge”(创造一个术语)。您问题中的第二个插图显示了 3-legged-merge 的期望结果示例,其中给定的 Scope 节点与 3 个特定节点有关系,并且仅与这 3 个节点有关系。

诀窍是这样的:向每个 Grant 添加 2 个属性(或 3 个,参见下面的注释),以唯一标识关联的 Scope 和关联的 Human,授权代表其执行。如果您还与实际的ScopeHuman 节点有关系,这无疑是多余的信息,但它应该确保您可以使用MERGE 为每组独特的3 条腿创建一个独特的Grant 节点。

例如,要正确执行第二个查询(在您的更新中),假设 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 属性,以便将来的 3-legged 合并能够正常工作。

  • 严格来说,您实际上不需要执行最后两个MERGEs 中的任何一个,因为Grant 节点将包含足够的信息来根据需要动态获取丢失的(虚拟)支路。例如,要代表“Human C”获取涉及“Human A”的Scopes:

    MATCH
      (:Human {name:"Human A"})-[:IS_GRANTED]->(g {for: "Human C"}),
      (scope:Scope {name: g.scope})
    RETURN scope
    

    这比拥有实际关系效率低,但可以节省存储空间。创建适当的indexes(例如,在这种情况下,在 ":Scope(name)" 上)将减少速度损失。

【讨论】:

  • 感谢您的回答@cybersam。对不起,当我简化查询时,我没有在匹配中使用逗号完全掌握这个概念,正如这个答案stackoverflow.com/questions/34089692/… 中所解释的那样。我确切地知道单个节点 A、C 和 D 是哪个。我已经更新了查询以反映这一点。我不是在处理整个图表,而是在 3 个确切的节点上工作,我需要通过一个新节点将它们连接在一起。我希望这能让我的问题更清楚。
  • 如果 MERGE 像 MATCH 一样工作,这就是我想要的查询:MERGE (a)-[:REL1]-&gt;(b:B)-[:REL2]-&gt;(c), (b)-[:REL3]-&gt;(d)
  • 我明白你为什么会这样。当我有机会通过现实生活中的例子使问题更清楚时,我需要更新我的问题。但基本上,a:A 可以有多个b:B's,但每个c:Cd:D 的组合只能指向一个。它用于访问管理系统中的模式。 identity1:A-[:IS_GRANTED]-&gt;(grantRule:B)-[:GRANTS]-&gt;(scope:C), (grantRule)-[:ON_BEHALF_OF]-&gt;(identity2:D)。我将尝试使用描述此问题的部分来更新问题。很抱歉浪费了您的时间。
  • 即使有点被黑也可以。如我所见,目前有 3 个选项可用:1. 使用 tmp 数据的解决方案,2.(可能)可选删除然后合并以确保仅存在一个,3. 拆分为 2 个查询,首先匹配并在找到时返回,然后如果没有就创建。当我测试它并查看最有效的方法时,我可能会自己添加一个答案,但我会接受你的答案,因为它是我的问题的解决方案之一。感谢您的帮助:)
  • 请注意,在我的提议中,添加的属性不是“临时的”(除非您知道您将不再需要进行任何 3 腿合并)。
猜你喜欢
  • 2023-03-16
  • 2014-02-05
  • 2023-03-07
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-18
相关资源
最近更新 更多