MERGE
简介
MERGE 子句用于匹配图中的现有节点模式并将其绑定,如果不存在,则创建新数据并将其绑定。因此,它充当了 MATCH 和 CREATE 的组合,允许根据指定数据是“已匹配”还是“已创建”来执行特定操作。
例如,MERGE 可用于指定图必须包含带有 Person 标签和特定 name 属性的节点。如果不存在具有该特定 name 属性的节点,则会创建一个带有该 name 属性的新节点。
|
出于性能原因,使用 |
当对完整模式使用 MERGE 时,其行为是:要么整个模式匹配,要么整个模式被创建。MERGE 不会部分使用现有模式。如果需要部分匹配,可以通过将模式拆分为多个 MERGE 子句来实现。
|
在并发更新的情况下, |
与 MATCH 类似,MERGE 可以匹配模式的多次出现。如果存在多次匹配,所有匹配项都将传递到查询的后续阶段。
MERGE 子句的最后一部分是 ON CREATE 和/或 ON MATCH 操作符。根据数据库中元素是“已匹配”(MATCH)还是“已创建”(CREATE),这些操作符允许查询对节点或关系的属性表达额外的变更。
示例图
以下图表用于下方的示例
要重建该图,请在空的 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 nodes)
合并带标签的单个节点
合并带特定标签的节点
MERGE (robert:Critic)
RETURN labels(robert)
由于数据库中没有标签为 Critic 的节点,因此创建了一个新节点
| labels(robert) |
|---|
["Critic"] |
合并带多个标签的单个节点
多个标签由冒号分隔
MERGE (robert:Critic:Viewer)
RETURN labels(robert)
由于数据库中没有同时标记为 Critic 和 Viewer 的节点,因此创建了一个新节点
| labels(robert) |
|---|
["Critic","Viewer"] |
多个标签也可以使用“和”符号 & 分隔,其方式与 标签表达式 中的用法相同。在同一个子句中,不能混用冒号 : 和“和”符号 &。
MERGE (robert:Critic&Viewer)
RETURN labels(robert)
没有创建新节点,因为数据库中已经存在一个同时标记为 Critic 和 Viewer 的节点
| labels(robert) |
|---|
["Critic","Viewer"] |
合并带属性的单个节点
合并一个属性与图中现有节点不同的节点将创建一个新节点
MERGE (charlie {name: 'Charlie Sheen', age: 10})
RETURN charlie
创建了一个名为 Charlie Sheen 的新节点,因为并非所有属性都与预先存在的 Charlie Sheen 节点的属性匹配
| charlie |
|---|
|
合并指定标签和属性的单个节点
合并指定标签和属性且与现有节点匹配的单个节点时,不会创建新节点
MERGE (michael:Person {name: 'Michael Douglas'})
RETURN michael.name, michael.bornIn
匹配到 Michael Douglas,并返回 name 和 bornIn 属性
| michael.name | michael.bornIn |
|---|---|
|
|
合并源自现有节点属性的单个节点
可以使用现有节点属性来合并节点
MATCH (person:Person)
MERGE (location:Location {name: person.bornIn})
RETURN person.name, person.bornIn, location
在上述查询中,创建了三个标记为 Location 的节点,每个节点都包含一个 name 属性,其值分别为 New York、Ohio 和 New Jersey。请注意,尽管 MATCH 子句导致三个绑定的节点在 bornIn 属性上都有值 New York,但只创建了一个 New York 节点(即一个 name 为 New York 的 Location 节点)。由于第一个绑定的节点没有匹配到 New York 节点,因此它被创建了。然而,对于第二个和第三个绑定的节点,新创建的 New York 节点被成功匹配并绑定。
| person.name | person.bornIn | location |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
使用 ON CREATE 和 ON 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 Reeves 的 Person 节点,其 bornIn 属性设置为 Beirut,chauffeurName 属性设置为 Eric Brown。它还为 created 属性设置了时间戳。
| keanu.name | keanu.created |
|---|---|
|
|
使用 ON MATCH 进行合并
合并节点并对已找到的节点设置属性
MERGE (person:Person)
ON MATCH
SET person.found = true
RETURN person.name, person.found
该查询找到所有 Person 节点,为它们设置一个属性,并返回它们
| person.name | person.found |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
结合 ON CREATE 和 ON 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 Reeves 的 Person 节点已经存在,所以此查询不会创建新节点。相反,它会在 lastSeen 属性上添加一个时间戳。
| keanu.name | keanu.created | keanu.lastSeen |
|---|---|---|
|
|
|
使用 ON MATCH 设置多个属性
如果需要设置多个属性,请用逗号分隔它们
MERGE (person:Person)
ON MATCH
SET
person.found = true,
person.lastAccessed = timestamp()
RETURN person.name, person.found, person.lastAccessed
| person.name | person.found | person.lastAccessed |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
合并关系 (Merge relationships)
合并关系
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 子句完成的。
| charlie.name | type(r) | wallStreet.title |
|---|---|---|
|
|
|
|
查询
|
|
不允许通过在同一个 例如,不允许在 查询
|
合并多个关系
MATCH
(oliver:Person {name: 'Oliver Stone'}),
(reiner:Person {name: 'Rob Reiner'})
MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:DIRECTED]-(reiner)
RETURN movie
在示例图中,Oliver Stone 和 Rob Reiner 从未合作过。当试图在他们之间 MERGE 一个 Movie 节点时,Neo4j 不会使用已经与其中任何一个人相连的现有 Movie 节点。相反,会创建一个新的 Movie 节点。
| movie |
|---|
|
合并无向关系
MERGE 也可以在不指定关系方向的情况下使用。Cypher® 将首先尝试在两个方向上匹配该关系。如果关系在任何方向上都不存在,它将创建一个从左到右的关系。
MATCH
(charlie:Person {name: 'Charlie Sheen'}),
(oliver:Person {name: 'Oliver Stone'})
MERGE (charlie)-[r:KNOWS]-(oliver)
RETURN r
由于 Charlie Sheen 和 Oliver Stone 在示例图中互不认识,这个 MERGE 查询将在他们之间创建一个 KNOWS 关系。所创建关系的方向是从左到右。
| r |
|---|
|
合并两个现有节点之间的关系
MERGE 可以与之前的 MATCH 和 MERGE 子句结合使用,在两个已绑定节点 m 和 n 之间创建关系,其中 m 由 MATCH 返回,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 Sheen、Rob Reiner 和 Oliver Stone 都与“同一个” Location 节点 (New York) 建立了 BORN_IN 关系。
| person.name | person.bornIn | location |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
合并现有节点与源自节点属性的合并节点之间的关系
MERGE 可用于同时创建新节点 n 以及已绑定节点 m 和 n 之间的关系
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',因此每种情况下都会创建一个新节点,导致有两个 name 为 'John Brown' 的 Chauffeur 节点,这正确地表明了尽管 name 属性可能相同,但这是两个人。这与上面 合并两个现有节点之间的关系 中的示例形成对比,在该示例中,第一个 MERGE 被用来绑定 Location 节点,并防止它们在第二个 MERGE 时被重新创建(从而导致重复)。
| person.name | person.chauffeurName | chauffeur |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
将节点属性唯一性约束与 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 属性上的节点属性唯一性约束,下面的查询将创建一个 name 属性为 Laurence Fishburne 的新 Person。如果已经存在一个 Laurence Fishburne 节点,MERGE 将匹配该现有节点。
MERGE (laurence:Person {name: 'Laurence Fishburne'})
RETURN laurence.name
| laurence.name |
|---|
|
使用节点属性唯一性约束的合并匹配现有节点
考虑到所有 Person 节点的 name 属性上的属性唯一性约束,下面的查询将匹配 name 属性为 Oliver Stone 的预先存在的 Person 节点。
MERGE (oliver:Person {name: 'Oliver Stone'})
RETURN oliver.name, oliver.bornIn
| oliver.name | oliver.bornIn |
|---|---|
|
|
使用属性唯一性约束的合并与部分匹配
当发现部分匹配时,使用属性唯一性约束的合并失败
MERGE (michael:Person {name: 'Michael Douglas', role: 'Gordon Gekko'})
RETURN michael
虽然有一个匹配的唯一 Person 节点名为 Michael Douglas,但没有唯一的节点角色为 Gordon Gekko,因此 MERGE 无法匹配。
22N41: 错误: 数据异常 - 合并节点唯一性约束违规。'MERGE' 子句没有找到匹配的节点 22G03: 错误: 数据异常 - 无效的值类型 |
要将 Gordon Gekko 的 role 设置为 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
虽然有一个匹配的唯一 Person 节点名为 Oliver Stone,但也存在另一个唯一 Person 节点角色为 Gordon Gekko,因此 MERGE 匹配失败。
22N41: 错误: 数据异常 - 合并节点唯一性约束违规。'MERGE' 子句没有找到匹配的节点 22G03: 错误: 数据异常 - 无效的值类型 |
将关系属性唯一性约束与 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
| person.name | person.bornIn | person.chauffeurName |
|---|---|---|
|
|
|
使用动态节点标签和关系类型的 MERGE
在合并节点和关系时,可以在表达式、参数和变量中动态引用节点标签和关系类型。这允许更灵活的查询,并减轻 Cypher 注入的风险。(有关 Cypher 注入的更多信息,请参阅 Neo4j 知识库 → 防止 Cypher 注入)。
MERGE (n:$(<expr>))
MERGE ()-[r:$(<expr>)]->()
表达式必须求值为 STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL 值。在使用动态关系类型合并关系时,使用包含多于一项的 LIST<STRING> 将会失败。这是因为一个关系只能有且仅有一个类型。
{
"nodeLabels": ["Person", "Director"],
"relType": "DIRECTED",
"movies": ["Ladybird", "Little Women", "Barbie"]
}
MERGE (greta:$($nodeLabels) {name: 'Greta Gerwig'})
WITH greta
UNWIND $movies AS movieTitle
MERGE (greta)-[rel:$($relType)]->(m:Movie {title: movieTitle})
RETURN greta.name AS name, labels(greta) AS labels, type(rel) AS relType, collect(m.title) AS movies
| 名称 (name) | 标签 | relType | movies |
|---|---|---|---|
|
|
|
|
行:1 |
|||
性能注意事项
使用动态值的 MERGE 查询性能可能不如使用静态值的查询。Neo4j 正在积极努力提高这些查询的性能。下表列出了特定 Neo4j 版本的性能注意事项。
| Neo4j 版本 | 性能注意事项 |
|---|---|
5.26 — 2025.07 |
Cypher 规划器 无法利用 索引扫描或查找 来使用 索引,必须改为使用 |
2025.08 — 2025.10 |
Cypher 规划器能够在动态匹配节点标签和关系类型时利用 令牌查找索引 (token lookup indexes)。这是通过引入三个新的查询计划操作符实现的: |
2025.11 — 至今 |
Cypher 规划器能够利用属性值上的索引,然而
|