MERGE
Introduction
The MERGE
clause either matches existing node patterns in the graph and binds them or, if not present, creates new data and binds that.
In this way, it acts as a combination of MATCH
and CREATE
that allows for specific actions depending on whether the specified data was matched or created.
For example, MERGE
can be used to specify that a graph must contain a node with a Person
label and a specific name
property.
If there isn’t a node with the specific name
property, a new node will be created with that name
property.
For performance reasons, creating a schema index on the label or property is highly recommended when using |
When using MERGE
on full patterns, the behavior is that either the whole pattern matches, or the whole pattern is created.
MERGE
will not partially use existing patterns.
If partial matches are needed, this can be accomplished by splitting a pattern into multiple MERGE
clauses.
Under concurrent updates, |
Similar to MATCH
, MERGE
can match multiple occurrences of a pattern.
If there are multiple matches, they will all be passed on to later stages of the query.
The last part of a MERGE
clause is the ON CREATE
and/or ON MATCH
operators.
These allow a query to express additional changes to the properties of a node or relationship, depending on whether the element was matched (MATCH
) in the database or if it was created (CREATE
).
Example graph
The following graph is used for the examples below:
To recreate the graph, run the following query in an empty Neo4j database:
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 single node with a label
Merge a node with a specific label:
MERGE (robert:Critic)
RETURN labels(robert)
A new node is created because there are no nodes labeled Critic
in the database:
labels(robert) |
---|
["Critic"] |
Merge single node with multiple labels
Multiple labels are separated by colons:
MERGE (robert:Critic:Viewer)
RETURN labels(robert)
A new node is created because there are no nodes labeled both Critic
and Viewer
in the database:
labels(robert) |
---|
["Critic","Viewer"] |
As of Neo4j 5.18, multiple labels can also be separated by an ampersand &
, in the same manner as it is used in label expressions.
Separation by colon :
and ampersand &
cannot be mixed in the same clause.
MERGE (robert:Critic&Viewer)
RETURN labels(robert)
No new node is created because there was already a node labeled both Critic
and Viewer
in the database:
labels(robert) |
---|
["Critic","Viewer"] |
Merge single node with properties
Merging a node with properties that differ from the properties on existing nodes in the graph will create a new node:
MERGE (charlie {name: 'Charlie Sheen', age: 10})
RETURN charlie
A new node with the name Charlie Sheen
is created since not all properties matched those set to the pre-existing Charlie Sheen
node:
charlie |
---|
|
Query
|
Merge single node specifying both label and property
Merging a single node with both label and property matching an existing node will not create a new node:
MERGE (michael:Person {name: 'Michael Douglas'})
RETURN michael.name, michael.bornIn
Michael Douglas
is matched and the name
and bornIn
properties are returned:
michael.name | michael.bornIn |
---|---|
|
|
Merge single node derived from an existing node property
It is possible to merge nodes using existing node properties:
MATCH (person:Person)
MERGE (location:Location {name: person.bornIn})
RETURN person.name, person.bornIn, location
In the above query, three nodes labeled Location
are created, each of which contains a name
property with the value of New York
, Ohio
, and New Jersey
respectively.
Note that even though the MATCH
clause results in three bound nodes having the value New York
for the bornIn
property, only a single New York
node (i.e. a Location
node with a name of New York
) is created.
As the New York
node is not matched for the first bound node, it is created.
However, the newly-created New York
node is matched and bound for the second and third bound nodes.
person.name | person.bornIn | location |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Use ON CREATE
and ON MATCH
Merge with ON CREATE
Merge a node and set properties if the node needs to be created:
MERGE (keanu:Person {name: 'Keanu Reeves', bornIn: 'Beirut', chauffeurName: 'Eric Brown'})
ON CREATE
SET keanu.created = timestamp()
RETURN keanu.name, keanu.created
The query creates the Person
node named Keanu Reeves
, with a bornIn
property set to Beirut
and a chauffeurName
property set to Eric Brown
.
It also sets a timestamp for the created
property.
keanu.name | keanu.created |
---|---|
|
|
Merge with ON MATCH
Merging nodes and setting properties on found nodes:
MERGE (person:Person)
ON MATCH
SET person.found = true
RETURN person.name, person.found
The query finds all the Person
nodes, sets a property on them, and returns them:
person.name | person.found |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Merge with ON CREATE
and 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
Because the Person
node named Keanu Reeves
already exists, this query does not create a new node.
Instead, it adds a timestamp on the lastSeen
property.
keanu.name | keanu.created | keanu.lastSeen |
---|---|---|
|
|
|
Merge with ON MATCH
setting multiple properties
If multiple properties should be set, separate them with commas:
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 on a relationship
MERGE
can be used to match or create a relationship:
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
had already been marked as acting in Wall Street
, so the existing relationship is found and returned.
Note that in order to match or create a relationship when using MERGE
, at least one bound node must be specified, which is done via the MATCH
clause in the above example.
charlie.name | type(r) | wallStreet.title |
---|---|---|
|
|
|
Query
|
As of Neo4j 5.20, specifying a property of an entity (node or relationship) by referring to the property of another entity in the same For example, referring to Query
|
Merge on multiple relationships
MATCH
(oliver:Person {name: 'Oliver Stone'}),
(reiner:Person {name: 'Rob Reiner'})
MERGE (oliver)-[:DIRECTED]->(movie:Movie)<-[:DIRECTED]-(reiner)
RETURN movie
In the example graph, Oliver Stone
and Rob Reiner
have never worked together.
When trying to MERGE
a Movie
node between them, Neo4j will not use any of the existing Movie
nodes already connected to either person.
Instead, a new Movie
node is created.
movie |
---|
|
Merge on an undirected relationship
MERGE
can also be used without specifying the direction of a relationship.
Cypher® will first try to match the relationship in both directions.
If the relationship does not exist in either direction, it will create one left to right.
MATCH
(charlie:Person {name: 'Charlie Sheen'}),
(oliver:Person {name: 'Oliver Stone'})
MERGE (charlie)-[r:KNOWS]-(oliver)
RETURN r
As Charlie Sheen
and Oliver Stone
do not know each other in the example graph, this MERGE
query will create a KNOWS
relationship between them.
The direction of the created relationship is left to right.
r |
---|
|
Merge on a relationship between two existing nodes
MERGE
can be used in conjunction with preceding MATCH
and MERGE
clauses to create a relationship between two bound nodes m
and n
, where m
is returned by MATCH
and n
is created or matched by the earlier MERGE
.
MATCH (person:Person)
MERGE (location:Location {name: person.bornIn})
MERGE (person)-[r:BORN_IN]->(location)
RETURN person.name, person.bornIn, location
This builds on the example from Merge single node derived from an existing node property.
The second MERGE
creates a BORN_IN
relationship between each person and a location corresponding to the value of the person’s bornIn
property.
Charlie Sheen
, Rob Reiner
, and Oliver Stone
all have a BORN_IN
relationship to the same Location
node (New York
).
person.name | person.bornIn | location |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Merge on a relationship between an existing node and a merged node derived from a node property
MERGE
can be used to simultaneously create both a new node n
and a relationship between a bound node m
and n
:
MATCH (person:Person)
MERGE (person)-[r:HAS_CHAUFFEUR]->(chauffeur:Chauffeur {name: person.chauffeurName})
RETURN person.name, person.chauffeurName, chauffeur
As MERGE
found no matches — in the example graph, there are no nodes labeled with Chauffeur
and no HAS_CHAUFFEUR
relationships — MERGE
creates six nodes labeled with Chauffeur
, each of which contains a name
property whose value corresponds to each matched Person
node’s chauffeurName
property value.
MERGE
also creates a HAS_CHAUFFEUR
relationship between each Person
node and the newly-created corresponding Chauffeur
node.
As 'Charlie Sheen'
and 'Michael Douglas'
both have a chauffeur with the same name — 'John Brown'
— a new node is created in each case, resulting in two Chauffeur
nodes having a name
of 'John Brown'
, correctly denoting the fact that even though the name
property may be identical, these are two separate people.
This is in contrast to the example shown above in Merge on a relationship between two existing nodes, where the first MERGE
was used to bind the Location
nodes and to prevent them from being recreated (and thus duplicated) on the second MERGE
.
person.name | person.chauffeurName | chauffeur |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Using node property uniqueness constraints with MERGE
Cypher prevents getting conflicting results from MERGE
when using patterns that involve property uniqueness constraints.
In this case, there must be at most one node that matches that pattern.
For example, given two property node uniqueness constraints on :Person(id)
and :Person(ssn)
, a query such as MERGE (n:Person {id: 12, ssn: 437})
will fail, if there are two different nodes (one with id
12 and one with ssn
437), or if there is only one node with only one of the properties.
In other words, there must be exactly one node that matches the pattern, or no matching nodes.
Note that the following examples assume the existence of property uniqueness constraints that have been created using:
CREATE CONSTRAINT FOR (n:Person) REQUIRE n.name IS UNIQUE;
CREATE CONSTRAINT FOR (n:Person) REQUIRE n.role IS UNIQUE;
Merge node using property uniqueness constraints creates a new node if no node is found
Given the node property uniqueness constraint on the name
property for all Person
nodes, the below query will create a new Person
with the name
property Laurence Fishburne
.
If a Laurence Fishburne
node had already existed, MERGE
would match the existing node instead.
MERGE (laurence:Person {name: 'Laurence Fishburne'})
RETURN laurence.name
laurence.name |
---|
|
Merge using node property uniqueness constraints matches an existing node
Given property uniqueness constraint on the name
property for all Person
nodes, the below query will match the pre-existing Person
node with the name
property Oliver Stone
.
MERGE (oliver:Person {name: 'Oliver Stone'})
RETURN oliver.name, oliver.bornIn
oliver.name | oliver.bornIn |
---|---|
|
|
Merge with property uniqueness constraints and partial matches
Merge using property uniqueness constraints fails when finding partial matches:
MERGE (michael:Person {name: 'Michael Douglas', role: 'Gordon Gekko'})
RETURN michael
While there is a matching unique Person
node with the name Michael Douglas
, there is no unique node with the role of Gordon Gekko
and MERGE
, therefore, fails to match.
Node already exists with label `Person` and property `name` = 'Michael Douglas'
To set the role
of Gordon Gekko
to Michael Douglas
, use the SET
clause instead:
MERGE (michael:Person {name: 'Michael Douglas'})
SET michael.role = 'Gordon Gekko'
Set 1 property
Merge with property uniqueness constraints and conflicting matches
Merge using property uniqueness constraints fails when finding conflicting matches:
MERGE (oliver:Person {name: 'Oliver Stone', role: 'Gordon Gekko'})
RETURN oliver
While there is a matching unique Person
node with the name Oliver Stone
, there is also another unique Person
node with the role of Gordon Gekko
and MERGE
fails to match.
Node already exists with label `Person` and property `name` = 'Oliver Stone'
Using relationship property uniqueness constraints with MERGE
All that has been said above about node uniqueness constraints also applies to relationship uniqueness constraints. However, for relationship uniqueness constraints there are some additional things to consider.
For example, if there exists a relationship uniqueness constraint on ()-[:ACTED_IN(year)]-()
, then the following query, in which not all nodes of the pattern are bound, would fail:
MERGE (charlie:Person {name: 'Charlie Sheen'})-[r:ACTED_IN {year: 1987}]->(wallStreet:Movie {title: 'Wall Street'})
RETURN charlie.name, type(r), wallStreet.title
This is due to the all-or-nothing semantics of MERGE
, which causes the query to fail if there exists a relationship with the given year
property but there is no match for the full pattern.
In this example, since no match was found for the pattern, MERGE
will try to create the full pattern including a relationship with {year: 1987}
, which will lead to constraint violation error.
Therefore, it is advised - especially when relationship uniqueness constraints exist - to always use bound nodes in the MERGE
pattern.
The following would, therefore, be a more appropriate composition of the query:
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
Using map parameters with MERGE
MERGE
does not support map parameters the same way that CREATE
does.
To use map parameters with MERGE
, it is necessary to explicitly use the expected properties, such as in the following example.
For more information on parameters, see Parameters.
{
"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 using dynamic node labels and relationship types
Node labels and relationship types can be referenced dynamically in expressions, parameters, and variables when merging nodes and relationships. This allows for more flexible queries and mitigates the risk of Cypher injection. (For more information about Cypher injection, see Neo4j Knowledge Base → Protecting against Cypher injection).
MERGE (n:$(<expr>))
MERGE ()-[r:$(<expr>)]->()
The expression must evaluate to a STRING NOT NULL | LIST<STRING NOT NULL> NOT NULL
value.
Using a LIST<STRING>
with more than one item when merging a relationship using dynamic relationship types will fail.
This is because a relationship can only have exactly one type.
Queries using dynamic values may not be as performant as those using static values. This is because the Cypher planner uses statically available information when planning queries to determine whether to use an index or not, and this is not possible when using dynamic values. |
{
"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 | labels | relType |
---|---|---|
|
|
|