了解 MERGE 的工作原理
什么是 MERGE,它是如何工作的?
MERGE
子句确保图中存在一个模式。要么整个模式已经存在,要么需要创建整个模式。
这样,将 MERGE 视为尝试对模式进行 MATCH,如果未找到匹配项,则创建该模式,会很有帮助。
当指定的模式不存在且需要创建时,之前绑定到现有图元素的任何变量都将在模式中重复使用。模式的所有其他元素都将被创建。
了解哪些模式元素将使用现有图元素以及哪些元素将被创建非常重要。
对于以下示例,我们将使用一个非常简单的 :Student、:Class、:ReportCard 和 :Term 节点的图,如下所示。
(:Student)-[:ENROLLED_IN]->(:Class)
(:Student)-[:EARNED]->(:ReportCard)
(:Class)-[:FOR_TERM]->(:Term)
本文中将引用“绑定变量”,它指的是从较早的子句(通常是 MATCH 或 MERGE)绑定到图中现有元素的变量,这些变量在 MERGE 模式中被重复使用。这意味着在 MERGE 模式中,它指的是之前找到的并且当前存在于图中的元素。
相反,模式中的“新变量”指的是在模式中首次引入的变量。因此,它尚不引用图中当前存在的任何元素,但由于 MERGE(匹配现有元素或创建新元素)的结果,它将绑定到图元素。
模式中与绑定变量无关的任何元素(包括使用新变量的元素以及根本没有变量的元素)如果 MERGE 必须创建整个模式,则会导致图中创建新的元素。
没有绑定变量的 MERGE 可能会创建重复元素
最常见的 MERGE 错误是尝试对没有绑定变量的模式进行 MERGE,而您希望使用现有图元素。
例如,尝试将现有的 student
注册到现有的 class
中。
MERGE (student:Student{id:123})-[:ENROLLED_IN]->(class:Class{name:'Cypher101'})
在上面的查询中,student
和 class
是新的变量,它们之前没有绑定到图中的任何节点,这是它们在查询中的首次使用。
如果整个模式已经存在(给定的学生已注册到给定的班级),则变量将按预期绑定到图中的现有节点。
但是,如果模式尚不存在,则将创建模式的所有新元素。在这种情况下,所有元素都将被创建;将使用给定的 id 创建一个新的 :Student 节点,使用给定的名称创建一个新的 :Class 节点,并在这些全新的节点之间创建一个新的 :ENROLLED_IN 关系。
如果这样的学生已经存在或这样的班级已经存在,这可能会导致创建重复节点。如果学生或班级节点对于给定的属性存在唯一约束,则会抛出错误。否则,将创建重复节点,这可能不会被注意到,尤其是在新手用户中。
带有绑定变量的 MERGE 重复使用现有图元素
要使用图中的现有节点和关系,请首先对节点或关系进行 MATCH 或 MERGE,然后使用绑定变量在模式中进行 MERGE。
上面注册查询的正确版本将首先对 student
和 class
进行 MATCH,然后对关系进行 MERGE。
MATCH (student:Student{id:123})
MATCH (class:Class{name:'Cypher101'})
MERGE (student)-[:ENROLLED_IN]->(class)
同样,您可以在对它们之间的关系进行 MERGE 之前,对学生和班级进行 MERGE。
MERGE (student:Student{id:123})
MERGE (class:Class{name:'Cypher101'})
MERGE (student)-[:ENROLLED_IN]->(class)
这确保了学生和班级节点的存在(如果它们不存在,则将创建它们),然后在它们之间合并关系。
使用绑定和新元素的组合进行 MERGE 以满足不同的用例
虽然上述方法对于该特定用例是正确的,但它并非适用于所有用例的正确方法。为了获得正确的行为,我们可能需要在 MERGE 中使用绑定和新元素的组合。
考虑一个为学生创建成绩单的查询。
如果我们重复使用上述方法,查询可能如下所示。
MATCH (student:Student{id:123})
MERGE (reportCard:ReportCard{term:'Spring2017'})
MERGE (student)-[:EARNED]->(reportCard)
此查询中的问题在于,相同的 :ReportCard 节点被所有学生重复使用。如果查询还需要向 :ReportCard 中添加成绩,则每个后续条目都会覆盖之前添加的内容。如果未被捕获,这种方法最终会导致所有学生拥有完全相同的成绩单节点,从而导致完全相同的成绩,即最后处理的学生输入的成绩。
我们真正需要的是每个学生一个单独的 :ReportCard。我们可以通过绑定 :Student 节点但不绑定 :ReportCard 节点来实现这一点。
MATCH (student:Student{id:123})
MERGE (student)-[:EARNED]->(reportCard:ReportCard{term:'Spring2017'})
由于 student
变量绑定到一个节点,如果需要创建模式,则将使用该节点,并且 :ReportCard 将仅为此 student
创建,而不是在所有学生之间共享。
请注意,如果我们完全省略 reportCard
变量,我们将获得完全相同的行为,因为当需要创建模式时,没有变量的元素和具有新引入变量的元素的处理方式完全相同。
MATCH (student:Student{id:123})
MERGE (student)-[:EARNED]->(:ReportCard{term:'Spring2017'})
请记住,新的关系也将被创建
上述示例对于节点应该易于理解,但请记住它们也适用于关系。如果整个模式不存在,则模式中的新关系将被创建。当我们使用更大的模式时,最容易看到这一点。
考虑一下,如果我们需要将 :Student 注册到特定 :Term 的 :Class 中。假设学生、学期和班级已存在于图中。
MATCH (student:Student{id:123})
MATCH (spring:Term{name:'Spring2017'})
MATCH (class:Class{name:'Cypher101'})
MERGE (student)-[:ENROLLED_IN]->(class)-[:FOR_TERM]->(spring)
这可能看起来是正确的,并且在这些关系之前都不存在时可能表现正确,但如果这里存在任何不平衡,例如只有部分模式存在,那么这最终会导致创建重复元素。如果查询针对多个学生而不是仅针对一个学生运行,这一点将最为明显。
在第一次运行中,对于第一个学生,关系将按预期创建。
在下次运行中,对于下一个学生,班级和学期之间存在 :FOR_TERM 关系,但学生未注册到该班级。由于整个模式不存在,因此将创建整个模式(排除绑定节点),因此学生将注册到该班级,并且将在班级和学期之间创建新的(重复的):FOR_TERM 关系。
如果查询运行 30 次以将 30 名学生注册到同一班级和学期,则在我们完成时,班级和学期之间将存在 30 个 :FOR_TERM 关系。
要解决此问题,如果已知每个 :Class 及其 :Term 之间都已存在 :FOR_TERM 关系,则将模式的该部分移到 MATCH 中。
MATCH (student:Student{id:123})
MATCH (class:Class{name:'Cypher101'})-[:FOR_TERM]->(spring:Term{name:'Spring2017'})
MERGE (student)-[:ENROLLED_IN]->(class)
如果我们不知道 :FOR_TERM 关系是否已经存在,那么我们将不得不进一步分解它,对学生、班级和学期进行 MATCH,然后对 :FOR_TERM 进行 MERGE,然后对 :ENROLLED_IN 进行 MERGE。
这里的要点是,通常应避免使用较长模式的 MERGE。如果模式的一部分存在但整个模式不存在,您可能会看到重复项,因此请考虑将较大模式的 MERGE 分解为较小模式上的单独 MERGE。
在 MERGE 后使用 ON MERGE 和 ON CREATE 设置属性以符合 MERGE 行为
通常在 MERGE 后,我们需要设置模式中元素的属性,但我们可能希望根据 MERGE 是否匹配到现有模式或必须创建它来有条件地设置这些属性。例如,如果我们有一些在创建时想要设置的默认属性值。
ON MERGE 和 ON CREATE 子句为我们提供了所需的控制。这还可以重新运行查询并确保我们不会用默认值覆盖现有属性。
以下是一个设置学生成绩单和分数的示例,假设{grades}
是一个我们希望在创建新的 :ReportCard 节点时设置的分数映射参数。
MATCH (student:Student{id:123})
MERGE (student)-[:EARNED]->(reportCard:ReportCard{term:'Spring2017'})
ON CREATE SET reportCard += {grades}
MERGE 在导致模式创建时获取模式中节点和关系的锁
当 MERGE 找不到现有模式时,它会在创建模式的缺失元素之前获取模式中所有绑定节点和关系的锁。
这是为了确保在 MERGE 执行期间,MERGE 或 CREATE 不会同时创建模式(或更改现有模式的属性使其与所需模式相同),这会导致模式重复。
在锁定绑定元素后,MERGE 对模式执行另一个 MATCH 以避免竞争条件,在该条件下,模式可能会在 MERGE 确定模式不存在的时间间隔和获取锁之间创建。
除非存在唯一约束,否则对单个节点模式的 MERGE 可能会创建重复项
当对单个节点模式执行 MERGE 时,如果节点尚不存在(并且不存在唯一约束),则没有任何内容可以锁定以避免竞争条件,因此并发事务可能会导致重复节点。
例如
MERGE (student:Student{id:123})
如果没有唯一约束,则此查询(或任何其他可能正在执行涉及具有此 ID 的 :Student 节点的 MERGE 或 CREATE 的查询)的并发执行可能会导致同一学生的多个节点。
通过在:Student(id)
上添加唯一约束,模式锁将确保学生节点是唯一的,并且不会出现重复。
此页面是否有帮助?