MERGE

简介

MERGE 子句用于匹配图中的现有节点模式并将其绑定,如果不存在,则创建新数据并将其绑定。因此,它充当了 MATCHCREATE 的组合,允许根据指定数据是“已匹配”还是“已创建”来执行特定操作。

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

出于性能原因,使用 MERGE 时强烈建议为标签或属性创建模式索引(schema index)。有关详细信息,请参阅 创建索引

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

在并发更新的情况下,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)

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

结果
labels(robert)

["Critic","Viewer"]

多个标签也可以使用“和”符号 & 分隔,其方式与 标签表达式 中的用法相同。在同一个子句中,不能混用冒号 : 和“和”符号 &

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

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

结果
labels(robert)

["Critic","Viewer"]

合并带属性的单个节点

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

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

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

结果
charlie

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

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

查询
MERGE (martin:Person {name: 'Martin Sheen', age: null})
RETURN martin
GQLSTATUS 错误链

22N31: 错误: 数据异常 - 合并模式中的属性无效。'(:Person {age: null})' 中的节点属性 age 无效。'MERGE' 不能与值为 null 的图元素属性一起使用。

22G03: 错误: 数据异常 - 无效的值类型

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

合并指定标签和属性且与现有节点匹配的单个节点时,不会创建新节点

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

匹配到 Michael Douglas,并返回 namebornIn 属性

结果
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 节点(即一个 nameNew YorkLocation 节点)。由于第一个绑定的节点没有匹配到 New York 节点,因此它被创建了。然而,对于第二个和第三个绑定的节点,新创建的 New York 节点被成功匹配并绑定。

结果
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 属性设置为 BeirutchauffeurName 属性设置为 Eric Brown。它还为 created 属性设置了时间戳。

结果
keanu.name keanu.created

"Keanu Reeves"

1655200898563

使用 ON MATCH 进行合并

合并节点并对已找到的节点设置属性

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

该查询找到所有 Person 节点,为它们设置一个属性,并返回它们

结果
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 属性上添加一个时间戳。

结果
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
结果
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 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

"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)
GQLSTATUS 错误链

22N31: 错误: 数据异常 - 合并模式中的属性无效。'(martin)-[:FATHER_OF {since: null}]→(charlie)' 中的关系属性 since 无效。'MERGE' 不能与值为 null 的图元素属性一起使用。

22G03: 错误: 数据异常 - 无效的值类型

不允许通过在同一个 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
GQLSTATUS 错误链

42I58: 错误: 语法错误或访问规则违规 - 无效的实体引用。实体 'charlie' 不能在同一个子句中被同时创建和引用。

42001:错误:语法错误或访问规则冲突 - 无效语法

合并多个关系

查询
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 节点。

结果
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 关系。所创建关系的方向是从左到右。

结果
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 关系。

结果
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',因此每种情况下都会创建一个新节点,导致有两个 name'John Brown'Chauffeur 节点,这正确地表明了尽管 name 属性可能相同,但这是两个人。这与上面 合并两个现有节点之间的关系 中的示例形成对比,在该示例中,第一个 MERGE 被用来绑定 Location 节点,并防止它们在第二个 MERGE 时被重新创建(从而导致重复)。

结果
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 属性上的节点属性唯一性约束,下面的查询将创建一个 name 属性为 Laurence Fishburne 的新 Person。如果已经存在一个 Laurence Fishburne 节点,MERGE 将匹配该现有节点。

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

"Laurence Fishburne"

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

考虑到所有 Person 节点的 name 属性上的属性唯一性约束,下面的查询将匹配 name 属性为 Oliver Stone 的预先存在的 Person 节点。

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

"Oliver Stone"

"New York"

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

当发现部分匹配时,使用属性唯一性约束的合并失败

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

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

GQLSTATUS 错误链

22N41: 错误: 数据异常 - 合并节点唯一性约束违规。'MERGE' 子句没有找到匹配的节点 michael,并且由于与现有唯一性约束冲突而无法创建新节点。

22G03: 错误: 数据异常 - 无效的值类型

要将 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

虽然有一个匹配的唯一 Person 节点名为 Oliver Stone,但也存在另一个唯一 Person 节点角色为 Gordon Gekko,因此 MERGE 匹配失败。

GQLSTATUS 错误链

22N41: 错误: 数据异常 - 合并节点唯一性约束违规。'MERGE' 子句没有找到匹配的节点 oliver,并且由于与现有唯一性约束冲突而无法创建新节点。

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

"Keanu Reeves"

"Beirut"

"Eric Brown"

使用动态节点标签和关系类型的 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

"Greta Gerwig"

["Person", "Director"]

"DIRECTED"

["Ladybird", "Little Women", "Barbie"]

行:1

性能注意事项

使用动态值的 MERGE 查询性能可能不如使用静态值的查询。Neo4j 正在积极努力提高这些查询的性能。下表列出了特定 Neo4j 版本的性能注意事项。

Neo4j 版本和性能注意事项
Neo4j 版本 性能注意事项

5.26 — 2025.07

Cypher 规划器 无法利用 索引扫描或查找 来使用 索引,必须改为使用 AllNodesScan 操作符,该操作符会读取节点存储中的所有节点,因此代价更高。

2025.08 — 2025.10

Cypher 规划器能够在动态匹配节点标签和关系类型时利用 令牌查找索引 (token lookup indexes)。这是通过引入三个新的查询计划操作符实现的:DynamicLabelNodeLookupDynamicDirectedRelationshipTypeLookupDynamicUndirectedRelationshipTypeLookup。但是,它无法利用属性值上的索引。例如,MERGE (n:$(Label) {foo: bar}) 不会使用 n.foo 上的任何索引,但可以在 $(label) 上使用 DynamicLabelNodeLookup

2025.11 — 至今

Cypher 规划器能够利用属性值上的索引,然而

  • 仅支持范围索引上的精确查找(不支持全文或空间索引)。

  • 无法利用索引顺序,因此如果查询后续需要,规划器必须插入单独的排序操作。

  • 并行运行时查找和扫描是单线程的。