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 typesIntroduced in 5.26
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.
{
"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 | movies |
|---|---|---|---|
|
|
|
|
Rows: 1 |
|||
Performance caveats
MERGE queries that use dynamic values may not perform as well as those with static values.
Neo4j is actively working to improve the performance of these queries.
The table below outlines performance caveats for specific Neo4j versions.
| Neo4j versions | Performance caveat |
|---|---|
5.26 — 2025.07 |
The Cypher planner is not able to leverage indexes with index scans or seeks and must instead utilize the |
2025.08 — current |
The Cypher planner is able to leverage token lookup indexes when matching node labels and relationship types dynamically.
This is enabled by the introduction of three new query plan operators:
|