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 属性设置为 BeirutchauffeurName 属性设置为 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 之前已被标记为出演 华尔街,因此找到了现有关系并返回。请注意,在使用 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 节点具有 John Brownname,这正确地表明了尽管 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 属性的属性唯一性约束,以下查询将匹配预先存在的 Person 节点,其 name 属性为 Oliver Stone

查询
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

带属性唯一性约束和冲突匹配的合并

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

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

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

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

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

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

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

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

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

将映射参数与 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 不像 CREATE 那样支持映射参数。要将映射参数与 MERGE 一起使用,需要显式使用预期的属性,例如以下示例中所示。有关参数的更多信息,请参阅参数

表 18. 结果

参数
{
  "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
使用动态节点标签和关系类型的 MERGE
person.name person.bornIn person.chauffeurName

"Keanu Reeves"

"Beirut"

"Eric Brown"

在合并节点和关系时,节点标签和关系类型可以在表达式、参数和变量中动态引用。这允许更灵活的查询并降低 Cypher 注入的风险。(有关 Cypher 注入的更多信息,请参阅Neo4j 知识库 → 防止 Cypher 注入)。

动态合并节点和关系的语法

表达式必须求值为 STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL 值。在使用动态关系类型合并关系时,使用包含多个项的 LIST<STRING> 将失败。这是因为关系只能有一个类型。
MERGE (n:$(<expr>))
MERGE ()-[r:$(<expr>)]->()

使用动态节点标签和关系类型合并节点和关系

参数
{
  "nodeLabels": ["Person", "Director"],
  "relType": "DIRECTED",
  "movies": ["Ladybird", "Little Women", "Barbie"]
}
表 19. 结果
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
labels relType movies "Greta Gerwig"

["Person", "Director"]

"DIRECTED"

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

行数:1

性能注意事项

使用动态值的 MERGE 查询可能不如使用静态值的查询那样高效。这是因为 Cypher 规划器在规划查询时会使用静态可用信息来确定是否使用索引,而在使用动态值时这是不可能的。

因此,具有动态值的 MERGE 查询无法利用索引扫描或查找,而必须改用AllNodesScan 运算符,该运算符从节点存储中读取所有节点,因此成本更高。

为避免可能的性能问题,请将动态标签或关系类型放在 ON CREATEON MATCH 子句中。

ON CREATEON MATCH 子句中使用动态值合并节点

参数
{
    "onMatchLabels": ["Filmmaker", "AwardRecipient"],
    "onCreateLabels":  ["ScreenWriter", "AwardWinner"]
}
由于名为 "Greta Gerwig" 的 Person 节点已存在,此查询将只 SET 添加到 ON MATCH 子句中的动态标签。
MERGE (n:Person {name: "Greta Gerwig"})
ON MATCH
    SET n:$($onMatchLabels)
ON CREATE
    SET n:$($onCreateLabels)
RETURN labels(n) AS gretaLabels

表 20. 结果

gretaLabels
["Person", "Director", "Filmmaker", "AwardRecipient"]

MATCH OPTIONAL MATCH

性能注意事项

© . All rights reserved.