15.3. Merge Nodes

You can merge a list of nodes onto the first one in the list.

All relationships are merged onto that node too. You can specify the merge behavior for properties globally and/or individually.

---
MATCH (p:Person)
WITH p ORDER BY p.created DESC // newest one first
WITH p.email, collect(p) as nodes
CALL apoc.refactor.mergeNodes(nodes, {properties: {name:'discard', age:'overwrite', kids:'combine', `addr.*`, 'overwrite',`.*`: 'discard'}}) YIELD node
RETURN node
---

This config option also works for apoc.refactor.mergeRelationships([rels],{config}).

type operations

discard

the first nodes' property will remain if already set, otherwise the first property in list will be written

overwrite / override

 last property in list wins

combine

if there is only one property in list, it will be set / kept as single property otherwise create an array, tries to coerce values

For mergeNodes you can Merge relationships with same type and direction, you can spacify this with property mergeRels. Relationships properties are managed with the same nodes' method, if properties parameter isn’t set relationships properties are combined.

example1 - Relationships with same start and end nodes. First of all we have to create nodes and relationships

Create (n1:Person {name:'Tom'}),
(n2:Person {name:'John'}),
(n3:Company {name:'Company1'}),
(n5:Car {brand:'Ferrari'}),
(n6:Animal:Cat {name:'Derby'}),
(n7:City {name:'London'}),
(n1)-[:WORKS_FOR{since:2015}]->(n3),
(n2)-[:WORKS_FOR{since:2018}]->(n3),
(n3)-[:HAS_HQ{since:2004}]->(n7),
(n1)-[:DRIVE{since:2017}]->(n5),
(n2)-[:HAS{since:2013}]->(n6)
return *;
apoc.refactor.mergeNodes.createDataSetFirstExample

Next step is calling the apoc to merge nodes :Person

MATCH (a1:Person{name:'John'}), (a2:Person {name:'Tom'})
WITH head(collect([a1,a2])) as nodes
CALL apoc.refactor.mergeNodes(nodes,{properties:"combine", mergeRels:true}) yield node
MATCH (n)-[r:WORKS_FOR]->(c) return *

and the result is:

apoc.refactor.mergeNodes.resultFirstExample

In this case we have relationships with same start and end nodes so relationships are merged into one and properties are combined.

Relationships with different start or end nodes

Create (n1:Person {name:'Tom'}),
(n2:Person {name:'John'}),
(n3:Company {name:'Company1'}),
(n4:Company {name:'Company2'}),
(n5:Car {brand:'Ferrari'}),
(n6:Animal:Cat {name:'Derby'}),
(n7:City {name:'London'}),
(n8:City {name:'Liverpool'}),
(n1)-[:WORKS_FOR{since:2015}]->(n3),
(n2)-[:WORKS_FOR{since:2018}]->(n4),
(n3)-[:HAS_HQ{since:2004}]->(n7),
(n4)-[:HAS_HQ{since:2007}]->(n8),
(n1)-[:DRIVE{since:2017}]->(n5),
(n2)-[:HAS{since:2013}]->(n6)
return *;
apoc.refactor.mergeNodes.createDataSetSecondExample

Next step is calling the apoc to merge nodes :Person

MATCH (a1:Person{name:'John'}), (a2:Person {name:'Tom'})
WITH head(collect([a1,a2])) as nodes
CALL apoc.refactor.mergeNodes(nodes,{properties:"combine", mergeRels:true}) yield node
MATCH (n)-[r:WORKS_FOR]->(c) return n.name,r.since,c.name;

and the result is:

apoc.refactor.mergeNodes.resultSecondExample
apoc.refactor.mergeNodes.resultSecondExampleData

In this case we have relationships with different end nodes so all relationships are maintained and properties are combined into all relationships.

MATCH (p:Person)
WITH p ORDER BY p.created DESC // newest one first
WITH p.email, collect(p) as nodes
CALL apoc.refactor.mergeNodes(nodes, {properties:'combine', mergeRels: true}) YIELD node
RETURN node