1
votes

I am using UNWIND to create multiple nodes in NEO4j. The problem is if one of the node is a duplicate it will be rejected and the entire query fails. I want to be able to create multiple relationship between the same nodes if they already exist..e.g a friend could receive multiple invitations from the same person. So I have an array of objects [{email: [email protected]},{email:[email protected]},...] to be invited and the email of the sponsor sponsorEmail. There is constraint on email so attempts to create a duplicate will fail and reject the entire query. The following works fine when there is no duplicate.

MATCH (s {email: '[email protected]'})
UNWIND $arrayOfObjects as invitees
CREATE (i:Invitee) MERGE (s)-[r:INVITED {since: timestamp()}]->(i)
SET i=invitees

I have tried substituting MERGE the CREATE thinking that the MERGE would find a MATCH and proceed to CREATE the relationship but it did not work..I still get duplicate error. Short of cleaning the arrayOfObjects before executing the query is there another way to do this? What I want is for the duplicate not to fail but to create a relationship with the existing Invitee node.

3

3 Answers

3
votes

You need to MERGE the :Invitee node along with the invitee email. As it is now, you are creating empty :Invitee nodes, and only after they are created setting the email address. You need to MERGE them with the email address. (also you should use a label on your first MATCH, otherwise it does an AllNodesScan...I'll assume it's :Invitee for now, but please replace with whatever label makes sense).

MATCH (s:Invitee {email: '[email protected]'})
UNWIND $arrayOfObjects as invitee
MERGE (i:Invitee {email:invitee.email}) 
CREATE (s)-[r:INVITED {since: timestamp()}]->(i)
1
votes

[EDITED]

Your MERGE could match any existing Invitee, and your SET could try to change its email value to a non-unique value. This is probably why you get the constraint violation.

If you want only a single INVITED relationship (with the latest timestamp) for each invitee, this query may do what you want:

MATCH (s {email: '[email protected]'})
UNWIND $arrayOfObjects as invitee
MERGE (i:Invitee {email: invitee.email})
ON CREATE SET i = invitee
MERGE (s)-[r:INVITED]->(i)
ON CREATE SET r.since = timestamp()

This query assumes that every map in arrayOfObjects contains a unique email property value. (You should also create an index or uniqueness constraint on :Invitee(email) to speed up the first MERGE.)

The MERGE (s)-[r:INVITED {since: timestamp()}]->(i) clause (which specifies the current timestamp) is flawed, since it would not detect an existing relationship with an older since value -- so it would almost always create a new relationship. The MERGE (s)-[r:INVITED]->(i) clause would only create a relationship if none exists.

Or, if you want to keep track of the timestamps of every invitation, you could make the since value be an array of timestamps, like this:

MATCH (s {email: '[email protected]'})
UNWIND $arrayOfObjects as invitee
MERGE (i:Invitee {email: invitee.email})
ON CREATE SET i = invitee
MERGE (s)-[r:INVITED]->(i)
ON CREATE SET r.since = [timestamp()]
ON MATCH SET r.since = r.since + timestamp()
0
votes

Both answer submitted are good answers. I am submitting this answer with a few nuances for those who come along later. The main objective of the question was to be able to create multiple invitations to a friend who has not yet accepted and be able to visualize those invitations. The following is what I settled on:

WITH ['[email protected]', '[email protected]',[email protected]'] AS coll
UNWIND coll AS invitee
WITH DISTINCT invitee
MATCH (s:Sponsor {email: '[email protected]'})
MERGE (i:Invitee {email: invitee})
CREATE (s)-[r:INVITED {since: timestamp()}]->(i)
RETURN r;   

This allowed me to create multiple relationships for each invite sent to the same person but only if sent at different times....which I can easily view.