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 (robert:Critic)
RETURN labels(robert)
由于数据库中没有标签为 Critic
的节点,因此创建了一个新节点
labels(robert) |
---|
["Critic"] |
合并带有多个标签的单个节点
多个标签用冒号分隔
MERGE (robert:Critic:Viewer)
RETURN labels(robert)
由于数据库中没有同时标记为 Critic
和 Viewer
的节点,因此创建了一个新节点
labels(robert) |
---|
["Critic","Viewer"] |
自 Neo4j 5.18 起,多个标签也可以用符号 &
分隔,与标签表达式中的用法相同。冒号 :
和符号 &
不能在同一个子句中混合使用。
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
节点(即一个名为 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
可用于匹配或创建关系
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
子句完成。
charlie.name | type(r) | wallStreet.title |
---|---|---|
|
|
|
查询
|
自 Neo4j 5.20 起,在同一 例如,在 查询
|
在多个关系上合并
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'
— 在每种情况下都创建了一个新节点,导致有两个 Chauffeur
节点具有 John Brown
的 name
,这正确地表明了尽管 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
属性的节点属性唯一性约束,以下查询将创建一个新的 Person
节点,其 name
属性为 Laurence Fishburne
。如果 Laurence Fishburne
节点已存在,MERGE
将匹配现有节点。
MERGE (laurence:Person {name: 'Laurence Fishburne'})
RETURN laurence.name
laurence.name |
---|
|
使用节点属性唯一性约束合并匹配现有节点
给定所有 Person
节点上 name
属性的属性唯一性约束,以下查询将匹配预先存在的 Person
节点,其 name
属性为 Oliver Stone
。
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
虽然存在一个名称为 Michael Douglas
的唯一 Person
节点匹配,但没有具有 Gordon Gekko
角色的唯一节点,因此 MERGE
无法匹配。
Node already exists with label `Person` and property `name` = 'Michael Douglas'
要将 Gordon Gekko
的 role
设置为 Michael Douglas
,请改用 SET
子句
MERGE (michael:Person {name: 'Michael Douglas'})
SET michael.role = 'Gordon Gekko'
Set 1 property
上面关于节点唯一性约束的所有内容也适用于关系唯一性约束。但是,对于关系唯一性约束,还有一些额外的事项需要考虑。
例如,如果存在一个关于 ()-[: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
模式中使用绑定节点。因此,以下是更合适的查询组合
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
person.name | person.bornIn | person.chauffeurName |
---|---|---|
|
|
|
在合并节点和关系时,节点标签和关系类型可以在表达式、参数和变量中动态引用。这允许更灵活的查询并降低 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"]
}
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
labels | relType | movies | "Greta Gerwig" |
---|---|---|---|
|
|
|
|
使用动态值的 MERGE
查询可能不如使用静态值的查询那样高效。这是因为 Cypher 规划器在规划查询时会使用静态可用信息来确定是否使用索引,而在使用动态值时这是不可能的。
因此,具有动态值的 MERGE
查询无法利用索引扫描或查找,而必须改用AllNodesScan
运算符,该运算符从节点存储中读取所有节点,因此成本更高。
为避免可能的性能问题,请将动态标签或关系类型放在 ON CREATE
或 ON MATCH
子句中。
在 ON CREATE
和 ON MATCH
子句中使用动态值合并节点
{
"onMatchLabels": ["Filmmaker", "AwardRecipient"],
"onCreateLabels": ["ScreenWriter", "AwardWinner"]
}
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. 结果
["Person", "Director", "Filmmaker", "AwardRecipient"] |
---|