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 MERGE. See Create, show, and delete indexes for more information.

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, MERGE only guarantees the existence of the MERGE pattern, but not uniqueness. To guarantee uniqueness of nodes with certain properties, a property uniqueness constraint should be used. See Using property uniqueness constraints with MERGE.

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:

graph merge clause

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:

Query
MERGE (robert:Critic)
RETURN labels(robert)

A new node is created because there are no nodes labeled Critic in the database:

Table 1. Result
labels(robert)

["Critic"]

Merge single node with multiple labels

Multiple labels are separated by colons:

Query
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:

Table 2. Result
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.

Query
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:

Table 3. Result
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:

Query
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:

Table 4. Result
charlie

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

MERGE cannot be used for nodes with property values that are null. For example, the following query will throw an error:

Query
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 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:

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

Michael Douglas is matched and the name and bornIn properties are returned:

Table 5. Result
michael.name michael.bornIn

"Michael Douglas"

"New Jersey"

Merge single node derived from an existing node property

It is possible to merge nodes using existing node properties:

Query
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.

Table 6. Result
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"}

Use ON CREATE and ON MATCH

Merge with ON CREATE

Merge a node and set properties if the node needs to be created:

Query
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.

Table 7. Result
keanu.name keanu.created

"Keanu Reeves"

1655200898563

Merge with ON MATCH

Merging nodes and setting properties on found nodes:

Query
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:

Table 8. Result
person.name person.found

"Charlie Sheen"

true

"Martin Sheen"

true

"Michael Douglas"

true

"Oliver Stone"

true

"Rob Reiner"

true

"Keanu Reeves"

true

Merge with ON CREATE and ON MATCH

Query
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.

Table 9. Result
keanu.name keanu.created keanu.lastSeen

"Keanu Reeves"

1655200902354

1674655352124

Merge with ON MATCH setting multiple properties

If multiple properties should be set, separate them with commas:

Query
MERGE (person:Person)
ON MATCH
  SET
    person.found = true,
    person.lastAccessed = timestamp()
RETURN person.name, person.found, person.lastAccessed
Table 10. Result
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 on a relationship

MERGE can be used to match or create a relationship:

Query
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.

Table 11. Result
charlie.name type(r) wallStreet.title

"Charlie Sheen"

"ACTED_IN"

"Wall Street"

MERGE cannot be used for relationships with property values that are null. For example, the following query will throw an error:

Query
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)

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 MERGE clause is deprecated.

For example, referring to charlie.bornIn in the property definition of oliver.bornIn is deprecated.

Query
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.

Merge on multiple relationships

Query
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.

Table 12. Result
movie

(: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.

Query
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.

Table 13. Result
r

[:KNOWS]

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.

Query
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).

Table 14. Result
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 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:

Query
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.

Table 15. Result
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"})

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.

Query
MERGE (laurence:Person {name: 'Laurence Fishburne'})
RETURN laurence.name
Table 16. Result
laurence.name

"Laurence Fishburne"

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.

Query
MERGE (oliver:Person {name: 'Oliver Stone'})
RETURN oliver.name, oliver.bornIn
Table 17. Result
oliver.name oliver.bornIn

"Oliver Stone"

"New York"

Merge with property uniqueness constraints and partial matches

Merge using property uniqueness constraints fails when finding partial matches:

Query
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.

Error message
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:

Query
MERGE (michael:Person {name: 'Michael Douglas'})
SET michael.role = 'Gordon Gekko'
Result
Set 1 property

Merge with property uniqueness constraints and conflicting matches

Merge using property uniqueness constraints fails when finding conflicting matches:

Query
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.

Error message
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:

Query
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:

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.

Parameters
{
  "param": {
    "name": "Keanu Reeves",
    "bornIn": "Beirut",
    "chauffeurName": "Eric Brown"
  }
}
Query
MERGE (person:Person {name: $param.name, bornIn: $param.bornIn, chauffeurName: $param.chauffeurName})
RETURN person.name, person.bornIn, person.chauffeurName
Table 18. Result
person.name person.bornIn person.chauffeurName

"Keanu Reeves"

"Beirut"

"Eric Brown"