MERGE

简介

MERGE 子句要么匹配图中现有的节点模式并将其绑定,要么在不存在的情况下创建新的数据并将其绑定。这样,它充当 MATCHCREATE 的组合,允许根据指定数据是否匹配或创建执行特定操作。

例如,MERGE 可用于指定图必须包含一个带有 Person 标签和特定 name 属性的节点。如果不存在具有特定 name 属性的节点,则将创建一个具有该 name 属性的新节点。

出于性能原因,在使用 MERGE 时,强烈建议在标签或属性上创建模式索引。有关更多信息,请参阅 创建、显示和删除索引

在完整模式上使用 MERGE 时,行为是整个模式匹配或整个模式创建。MERGE 不会部分使用现有模式。如果需要部分匹配,可以通过将模式拆分为多个 MERGE 子句来实现。

在并发更新下,MERGE 仅保证 MERGE 模式的存在,但不保证唯一性。要保证具有某些属性的节点的唯一性,应使用 属性唯一性约束。请参阅 MERGE 中使用属性唯一性约束

MATCH 类似,MERGE 可以匹配模式的多个出现。如果有多个匹配项,则所有匹配项都将传递到查询的后续阶段。

MERGE 子句的最后一部分是 ON CREATE 和/或 ON MATCH 运算符。这些允许查询根据元素是在数据库中匹配 (MATCH) 还是创建 (CREATE),来表达对节点或关系属性的其他更改。

示例图

以下图用于下面的示例

graph merge clause

要重新创建该图,请在空的 Neo4j 数据库中运行以下查询

CREATE
  (charlie:Person {name: 'Charlie Sheen', bornIn: 'New York', chauffeurName: 'John Brown'}),
  (martin:Person {name: 'Martin Sheen', bornIn: 'Ohio', chauffeurName: 'Bob Brown'}),
  (michael:Person {name: 'Michael Douglas', bornIn: 'New Jersey', chauffeurName: 'John Brown'}),
  (oliver:Person {name: 'Oliver Stone', bornIn: 'New York', chauffeurName: 'Bill White'}),
  (rob:Person {name: 'Rob Reiner', bornIn: 'New York', chauffeurName: 'Ted Green'}),
  (wallStreet:Movie {title: 'Wall Street'}),
  (theAmericanPresident:Movie {title: 'The American President'}),
  (charlie)-[:ACTED_IN]->(wallStreet),
  (martin)-[:ACTED_IN]->(wallStreet),
  (michael)-[:ACTED_IN]->(wallStreet),
  (martin)-[:ACTED_IN]->(theAmericanPresident),
  (michael)-[:ACTED_IN]->(theAmericanPresident),
  (oliver)-[:DIRECTED]->(wallStreet),
  (rob)-[:DIRECTED]->(theAmericanPresident)

合并节点

合并具有标签的单个节点

合并具有特定标签的节点

查询
MERGE (robert:Critic)
RETURN labels(robert)

由于数据库中没有标记为 Critic 的节点,因此创建了一个新节点

表 1. 结果
labels(robert)

["Critic"]

合并具有多个标签的单个节点

多个标签用冒号分隔

查询
MERGE (robert:Critic:Viewer)
RETURN labels(robert)

由于数据库中没有同时标记为 CriticViewer 的节点,因此创建了一个新节点

表 2. 结果
labels(robert)

["Critic","Viewer"]

从 Neo4j 5.18 开始,多个标签也可以用与符号 & 分隔,方式与 标签表达式 中使用的方式相同。在同一子句中不能混合使用冒号 : 和与符号 & 分隔。

查询
MERGE (robert:Critic&Viewer)
RETURN labels(robert)

由于数据库中已经存在一个同时标记为 CriticViewer 的节点,因此没有创建新节点

表 3. 结果
labels(robert)

["Critic","Viewer"]

合并具有属性的单个节点

合并与图中现有节点的属性不同的属性的节点将创建一个新节点

查询
MERGE (charlie {name: 'Charlie Sheen', age: 10})
RETURN charlie

由于并非所有属性都与设置为预先存在的 Charlie Sheen 节点的属性匹配,因此创建了一个名为 Charlie Sheen 的新节点

表 4. 结果
charlie

(:Person {"name":"Charlie Sheen", "age":10})

MERGE 不能用于属性值为 null 的节点。例如,以下查询将抛出错误

查询
MERGE (martin:Person {name: 'Martin Sheen', age: null})
RETURN martin
Cannot merge the following node because of null property value for 'age': (:Person {age: null})

合并同时指定标签和属性的单个节点

合并一个标签和属性都与现有节点匹配的单个节点不会创建新的节点。

查询
MERGE (michael:Person {name: 'Michael Douglas'})
RETURN michael.name, michael.bornIn

Michael Douglas 匹配成功,并返回 namebornIn 属性。

表 5. 结果
michael.name michael.bornIn

"Michael Douglas"

"New Jersey"

从现有节点属性派生合并单个节点

可以使用现有节点属性合并节点。

查询
MATCH (person:Person)
MERGE (location:Location {name: person.bornIn})
RETURN person.name, person.bornIn, location

在上面的查询中,创建了三个标签为 Location 的节点,每个节点都包含一个 name 属性,其值分别为 New YorkOhioNew Jersey。请注意,即使 MATCH 子句导致三个绑定节点的 bornIn 属性值都为 New York,也只创建了一个 New York 节点(即名称为 New YorkLocation 节点)。由于第一个绑定节点没有匹配到 New York 节点,因此会创建它。但是,新创建的 New York 节点与第二个和第三个绑定节点匹配并绑定。

表 6. 结果
person.name person.bornIn location

"Charlie Sheen"

"New York"

{name:"New York"}

"Martin Sheen"

"Ohio"

{name:"Ohio"}

"Michael Douglas"

"New Jersey"

{name:"New Jersey"}

"Oliver Stone"

"New York"

{name:"New York"}

"Rob Reiner"

"New York"

{name:"New York"}

使用 ON CREATEON MATCH

使用 ON CREATE 合并

如果需要创建节点,则合并节点并设置属性。

查询
MERGE (keanu:Person {name: 'Keanu Reeves', bornIn: 'Beirut', chauffeurName: 'Eric Brown'})
ON CREATE
  SET keanu.created = timestamp()
RETURN keanu.name, keanu.created

该查询创建名为 Keanu ReevesPerson 节点,并将 bornIn 属性设置为 Beirut,并将 chauffeurName 属性设置为 Eric Brown。它还为 created 属性设置时间戳。

表 7. 结果
keanu.name keanu.created

"Keanu Reeves"

1655200898563

使用 ON MATCH 合并

合并节点并在找到的节点上设置属性。

查询
MERGE (person:Person)
ON MATCH
  SET person.found = true
RETURN person.name, person.found

该查询查找所有 Person 节点,在其上设置属性,并返回它们。

表 8. 结果
person.name person.found

"Charlie Sheen"

true

"Martin Sheen"

true

"Michael Douglas"

true

"Oliver Stone"

true

"Rob Reiner"

true

"Keanu Reeves"

true

使用 ON CREATEON MATCH 合并

查询
MERGE (keanu:Person {name: 'Keanu Reeves'})
ON CREATE
  SET keanu.created = timestamp()
ON MATCH
  SET keanu.lastSeen = timestamp()
RETURN keanu.name, keanu.created, keanu.lastSeen

因为名为 Keanu ReevesPerson 节点已经存在,所以此查询不会创建新节点。相反,它在 lastSeen 属性上添加一个时间戳。

表 9. 结果
keanu.name keanu.created keanu.lastSeen

"Keanu Reeves"

1655200902354

1674655352124

使用 ON MATCH 设置多个属性合并

如果应设置多个属性,请用逗号分隔它们。

查询
MERGE (person:Person)
ON MATCH
  SET
    person.found = true,
    person.lastAccessed = timestamp()
RETURN person.name, person.found, person.lastAccessed
表 10. 结果
person.name person.found person.lastAccessed

"Charlie Sheen"

true

1655200903558

"Martin Sheen"

true

1655200903558

"Michael Douglas"

true

1655200903558

"Oliver Stone"

true

1655200903558

"Rob Reiner"

true

1655200903558

"Keanu Reeves"

true

1655200903558

合并关系

在关系上合并

MERGE 可用于匹配或创建关系。

查询
MATCH
  (charlie:Person {name: 'Charlie Sheen'}),
  (wallStreet:Movie {title: 'Wall Street'})
MERGE (charlie)-[r:ACTED_IN]->(wallStreet)
RETURN charlie.name, type(r), wallStreet.title

Charlie Sheen 之前已被标记为在 Wall Street 中表演,因此找到并返回了现有关系。请注意,为了在使用 MERGE 时匹配或创建关系,必须指定至少一个绑定节点,这可以通过上述示例中的 MATCH 子句完成。

表 11. 结果
charlie.name type(r) wallStreet.title

"Charlie Sheen"

"ACTED_IN"

"Wall Street"

MERGE 不能用于属性值为 null 的关系。例如,以下查询将引发错误。

查询
MERGE (martin:Person {name: 'Martin Sheen'})-[r:FATHER_OF {since: null}]->(charlie:Person {name: 'Charlie Sheen'})
RETURN type(r)
Cannot merge the following relationship because of null property value for 'since': (martin)-[:FATHER_OF {since: null}]->(charlie)

从 Neo4j 5.20 开始,通过引用同一 MERGE 子句中另一个实体的属性来指定实体(节点或关系)的属性已弃用。

例如,在 oliver.bornIn 的属性定义中引用 charlie.bornIn 已弃用。

查询
MERGE (charlie:Person {name: 'Charlie Sheen', bornIn: 'New York'})-[:ACTED_IN]->(movie:Movie)<-[:DIRECTED]-(oliver:Person {name: 'Oliver Stone', bornIn: charlie.bornIn})
RETURN movie
Merging an entity (charlie) and referencing that entity in a property definition in the same MERGE is deprecated.

在多个关系上合并

查询
MATCH
  (oliver:Person {name: 'Oliver Stone'}),
  (reiner:Person {name: 'Rob Reiner'})
MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:DIRECTED]-(reiner)
RETURN movie

在示例图中,Oliver StoneRob Reiner 从未一起工作过。当尝试在它们之间 MERGE 一个 Movie 节点时,Neo4j 不会使用已连接到任一人的任何现有 Movie 节点。相反,将创建一个新的 Movie 节点。

表 12. 结果
movie

(:Movie)

在无向关系上合并

MERGE 也可以不用指定关系的方向。Cypher® 将首先尝试双向匹配关系。如果关系在任一方向都不存在,它将创建一个从左到右的关系。

查询
MATCH
  (charlie:Person {name: 'Charlie Sheen'}),
  (oliver:Person {name: 'Oliver Stone'})
MERGE (charlie)-[r:KNOWS]-(oliver)
RETURN r

由于 Charlie SheenOliver Stone 在示例图中彼此不认识,因此此 MERGE 查询将在它们之间创建一个 KNOWS 关系。创建的关系方向是从左到右。

表 13. 结果
r

[:KNOWS]

在两个现有节点之间的关系上合并

MERGE 可以与前面的 MATCHMERGE 子句结合使用,以在两个绑定节点 mn 之间创建关系,其中 mMATCH 返回,n 由前面的 MERGE 创建或匹配。

查询
MATCH (person:Person)
MERGE (location:Location {name: person.bornIn})
MERGE (person)-[r:BORN_IN]->(location)
RETURN person.name, person.bornIn, location

这基于从现有节点属性派生合并单个节点中的示例。第二个 MERGE 在每个人和与该人的 bornIn 属性值相对应的位置之间创建一个 BORN_IN 关系。Charlie SheenRob ReinerOliver Stone 都与同一个 Location 节点(New York)存在 BORN_IN 关系。

表 14. 结果
person.name person.bornIn location

"Charlie Sheen"

"New York"

(:Location {name:"New York"})

"Martin Sheen"

"Ohio"

(:Location {name:"Ohio"})

"Michael Douglas"

"New Jersey"

(:Location {name:"New Jersey"})

"Oliver Stone"

"New York"

(:Location {name:"New York"})

"Rob Reiner"

"New York"

(:Location {name:"New York"})

"Keanu Reeves"

"Beirut"

(:Location {name:"Beirut"})

在现有节点和从节点属性派生的合并节点之间的关系上合并

MERGE 可用于同时创建一个新节点 n 以及绑定节点 mn 之间的关系。

查询
MATCH (person:Person)
MERGE (person)-[r:HAS_CHAUFFEUR]->(chauffeur:Chauffeur {name: person.chauffeurName})
RETURN person.name, person.chauffeurName, chauffeur

由于 MERGE 未找到匹配项(在示例图中,没有标签为 Chauffeur 的节点,也没有 HAS_CHAUFFEUR 关系),因此 MERGE 创建了六个标签为 Chauffeur 的节点,每个节点都包含一个 name 属性,其值对应于每个匹配的 Person 节点的 chauffeurName 属性值。MERGE 还创建了每个 Person 节点与其新创建的相应 Chauffeur 节点之间的 HAS_CHAUFFEUR 关系。由于 'Charlie Sheen''Michael Douglas' 的司机名字都相同——'John Brown'——因此在每种情况下都会创建一个新节点,从而导致两个 Chauffeur 节点的 name'John Brown',正确地表示即使 name 属性可能相同,但它们是两个人。这与上面在两个现有节点之间的关系上合并中显示的示例形成对比,在该示例中,第一个 MERGE 用于绑定 Location 节点并防止它们在第二个 MERGE 中重新创建(从而重复)。

表 15. 结果
person.name person.chauffeurName chauffeur

"Charlie Sheen"

"John Brown"

(:Person {name:"John Brown"})

"Martin Sheen"

"Bob Brown"

(:Person {name:"Bob Brown"})

"Michael Douglas"

"John Brown"

(:Person {name:"John Brown"})

"Oliver Stone"

"Bill White"

(:Person {name:"Bill White"})

"Rob Reiner"

"Ted Green"

(:Person {name:"Ted Green"})

"Keanu Reeves"

"Eric Brown"

(:Person {name:"Eric Brown"})

将节点属性唯一性约束与 MERGE 结合使用

当使用涉及属性唯一性约束的模式时,Cypher 可防止从 MERGE 获取冲突的结果。在这种情况下,最多只能有一个节点匹配该模式。

例如,给定两个在 :Person(id):Person(ssn) 上的属性节点唯一性约束,如果存在两个不同的节点(一个 id 为 12,另一个 ssn 为 437),或者如果只有一个节点只有一个属性,则诸如 MERGE (n:Person {id: 12, ssn: 437}) 之类的查询将失败。换句话说,必须只有一个节点匹配该模式,或者没有匹配节点。

请注意,以下示例假定使用以下内容创建了属性唯一性约束。

CREATE CONSTRAINT FOR (n:Person) REQUIRE n.name IS UNIQUE;
CREATE CONSTRAINT FOR (n:Person) REQUIRE n.role IS UNIQUE;

如果未找到节点,则使用属性唯一性约束合并节点将创建一个新节点。

给定所有 Person 节点的 name 属性上的节点属性唯一性约束,以下查询将创建一个新的 Person,其 name 属性为 Laurence Fishburne。如果 Laurence Fishburne 节点已经存在,MERGE 将改为匹配现有节点。

查询
MERGE (laurence:Person {name: 'Laurence Fishburne'})
RETURN laurence.name
表 16. 结果
laurence.name

"Laurence Fishburne"

使用节点属性唯一性约束合并匹配现有节点。

给定所有 Person 节点的 name 属性上的属性唯一性约束,以下查询将匹配具有 name 属性 Oliver Stone 的预先存在的 Person 节点。

查询
MERGE (oliver:Person {name: 'Oliver Stone'})
RETURN oliver.name, oliver.bornIn
表 17. 结果
oliver.name oliver.bornIn

"Oliver Stone"

"New York"

使用属性唯一性约束和部分匹配合并

当找到部分匹配时,使用属性唯一性约束合并将失败。

查询
MERGE (michael:Person {name: 'Michael Douglas', role: 'Gordon Gekko'})
RETURN michael

虽然存在一个名称为 Michael Douglas 的匹配唯一 Person 节点,但没有角色为 Gordon Gekko 的唯一节点,因此 MERGE 无法匹配。

错误消息
Node already exists with label `Person` and property `name` = 'Michael Douglas'

要将 Gordon Gekkorole 设置为 Michael Douglas,请改用 SET 子句。

查询
MERGE (michael:Person {name: 'Michael Douglas'})
SET michael.role = 'Gordon Gekko'
结果
Set 1 property

使用属性唯一性约束和冲突匹配合并

当找到冲突匹配时,使用属性唯一性约束合并将失败。

查询
MERGE (oliver:Person {name: 'Oliver Stone', role: 'Gordon Gekko'})
RETURN oliver

虽然存在一个名称为 Oliver Stone 的匹配唯一 Person 节点,但也存在另一个角色为 Gordon Gekko 的唯一 Person 节点,并且 MERGE 无法匹配。

错误消息
Node already exists with label `Person` and property `name` = 'Oliver Stone'

将关系属性唯一性约束与 MERGE 结合使用

上面关于节点唯一性约束的所有内容也适用于关系唯一性约束。但是,对于关系唯一性约束,还有一些其他事项需要考虑。

例如,如果在 ()-[:ACTED_IN(year)]-() 上存在关系唯一性约束,则以下查询(其中模式的并非所有节点都已绑定)将失败。

查询
MERGE (charlie:Person {name: 'Charlie Sheen'})-[r:ACTED_IN {year: 1987}]->(wallStreet:Movie {title: 'Wall Street'})
RETURN charlie.name, type(r), wallStreet.title

这是由于 MERGE 的全有或全无语义,如果存在具有给定 year 属性的关系但模式不完全匹配,则会导致查询失败。在此示例中,由于未找到模式的匹配项,因此 MERGE 将尝试创建完整的模式,包括一个具有 {year: 1987} 的关系,这将导致约束违规错误。

因此,建议 - 特别是在存在关系唯一性约束时 - 始终在 MERGE 模式中使用绑定节点。因此,以下将是更合适的查询组合。

查询
MATCH
  (charlie:Person {name: 'Charlie Sheen'}),
  (wallStreet:Movie {title: 'Wall Street'})
MERGE (charlie)-[r:ACTED_IN {year: 1987}]->(wallStreet)
RETURN charlie.name, type(r), wallStreet.title

将映射参数与 MERGE 结合使用

MERGE 的使用方法与 CREATE 不同,不支持相同的映射参数。要在 MERGE 中使用映射参数,需要显式地使用预期的属性,如下例所示。有关参数的更多信息,请参阅参数

参数
{
  "param": {
    "name": "Keanu Reeves",
    "bornIn": "Beirut",
    "chauffeurName": "Eric Brown"
  }
}
查询
MERGE (person:Person {name: $param.name, bornIn: $param.bornIn, chauffeurName: $param.chauffeurName})
RETURN person.name, person.bornIn, person.chauffeurName
表 18. 结果
person.name person.bornIn person.chauffeurName

"Keanu Reeves"

"Beirut"

"Eric Brown"