Adding relationships between an array of array of objects in Neo4j Cypher - neo4j

I have three arrays of objects as shown below:
const org = [
{
id: "orgId1",
name: "first organization"
},
{
id: "orgId2",
name: "second organization"
}
]
const location = [
{
id: "loc1",
name: "Texas"
},
{
id: "loc2",
name: "New York"
}
]
const contacts = [
{
id: "contact1",
name: "James"
},
{
id: "contact2",
name: "John"
}
]
What is the optimal way to add relationships between them? Note that the arrays are of the same length.
I need a Cypher query that can loop through a range from 0 to orgs.length, and add corresponding relationships between each element at i. e.g org[i], contacts[i], location[i]
I tried the following, but it gives me an explosive combination where the first org maps to all the entries in location array and contact array, when I want is a one-to-one mapping.
UNWIND $orgs as orgs
UNWIND $locations as locs
UNWIND $contacts as contacts
FOREACH (i IN range(0, size(orgs) - 1)
| MERGE (:Organization { id: orgs[i].id })-[r:LOCATED_AT]->(:Location {id: locs[i].id})
| MERGE (:Organization { id: orgs[i].id })-[r:CONTACT_AT]->(:Contact {id: contacts[i].id})
)
Any help would be appreciated. Thanks in advance.

I don't think you need to UNWIND all arrays
WITH $orgs AS orgs,
$locs AS locs,
$contacts AS contacts
UNWIND $orgs as orgs
FOREACH (i IN range(0, size(orgs) - 1) |
MERGE (org:Organization { id: orgs[i].id })
MERGE (loc:Location {id: locs[i].id})
MERGE (contact:Contact {id: contacts[i].id})
MERGE (org)-[:LOCATED_AT]->(loc)
MERGE (org)-[:CONTACT_AT]->(contact)
)
should do it

A solution was posted in the official Neo4j site. I'm sharing here as well.
UNWIND range(0, size($orgs) - 1) as i
with i
MERGE (o:Organization { id: $orgs[i].id })-[r:LOCATED_AT]->(l:Location {id: $locs[i].id})
with o, i
MERGE (o)-[r2:CONTACT_AT]->(l2:Contact {id: $contacts[i].id})
Link to original answer

Related

How to match nodes and their relationships with the main node?

I have a graph like this:
({id: 1, name: 'Winston'}) --[{name: 'Bill'}]--> ({id: 2, name: 'Max'})
({id: 3, name: 'Steve'})
I want to match nodes with id = 2 or 3 and replace property name in them by name from relationship with node which has id = 1:
{
"id": 2,
"name": "Bill"
}
{
"id": 3,
"name": "Steve"
}
How can i do this?
For one, you should be using labels on your nodes. For example's sake let's use :Person. As your graph gets larger you should also be adding indexes on the labels/properties used for node lookup.
For this case, only adding/replacing the name property of nodes based on a relationship property when connected to a starting node with id:1, we can do the following:
MATCH (:Person {id: 1})-[r]->(u:Person)
SET u.name = r.name

Neo4j Batch update of data

How can I do multiple Nodes update in neo4j cypher?
Now I'm trying to do like this:
MATCH (user151325288158:User{userId:151325288158}),
(user88245:User{userId:88245})
SET user151325288158.balance=2902833.4219789803
SET user88245.balance=146701.0299999991
RETURN user151325288158.balance,user88245.balance;
But here I have problem that if such user is absent in DB, nobody will be updated.
Other problem is performance, such queries are slow.
Is there some method to do such bulk updates?
Assuming you have pairs of userIds and new balance values in an array of maps/dicts, like this:
[
{
"userId": 151325288158,
"balance": 146701.09
},
{
"userId": 887436512344,
"balance": 22453.34
},
{
"userId": 873927654232,
"balance": 300002.22
}
]
You can pass this array as a parameter to a Cypher query to MATCH on the userId and update the balance property like this:
WITH {data} AS pairs
UNWIND pairs AS p
MATCH (u:User) WHERE u.userId = p.userId
SET u.balance = p.balance
By following William Lyon's answer, I was able to implement a batch update of multiple properties as follows:
UNWIND [{id: 123456, name: "John", age: 27}, {id: 789012, name: "Jane", age: 24}] AS updateUser
MATCH (user: User)
WHERE user.id = updateUser.id
SET user += updateUser
RETURN user

match property on nodes in path of cypher shortestPath

For simplicity sake, let's say I have a graph like this (using actors/movies as an example):
Nodes:
Actor(id:1)
Actor(id:2)
Actor(id:3)
Movie(id:4,rating:'PG-13')
Movie(id:5,rating:'PG-13')
Relationships:
Actor(id:1) APPEARS_IN Movie(id:4)
Actor(id:2) APPEARS_IN Movie(id:4)
Actor(id:2) APPEARS_IN Movie(id:5)
Actor(id:3) APPEARS_IN Movie(id:5)
Cypher to create the sample graph:
create (a1:Actor {id: 1, name: 'Actor 1'})
create (a2:Actor {id: 2, name: 'Actor 2'})
create (a3:Actor {id: 3, name: 'Actor 3'})
create (m1:Movie {id: 4, rating:'PG-13', name: 'Movie 1' } )
create (m2:Movie {id: 5, rating:'PG-13', name: 'Movie 2' } )
create (a1)-[:APPEARS_IN]->(m1)
create (a2)-[:APPEARS_IN]->(m1)
create (a2)-[:APPEARS_IN]->(m2)
create (a3)-[:APPEARS_IN]->(m2)
return *
So now we have want to find the shortest path between Actor(id:1) and Actor(id:3). That's easy enough, we might try a cypher query like:
MATCH p=shortestPath((a:Actor { id: 1 })-[:APPEARS_IN*0..14]-(b:Actor { id: 3 })) RETURN NODES(p)
And we would get a result.
This is where my question comes in How can I put a requirement on the nodes in between (specifically the movie nodes) to only include rating:'R' as part of its path?
This query will only return the nodes of the shortest path between the two actors where all of the movies are R rated.
It filters out the Movie nodes and then checks to make sure that the rating of each movie node in the collection is R.
MATCH p=shortestPath((a:Actor { id: 1 })-[:APPEARS_IN*0..14]-(b:Actor { id: 3 }))
WHERE all ( movie in filter( m in nodes(p) where 'Movie' in labels(m)) where movie.rating = 'R')
RETURN nodes(p)

Neo4j: Match three or more relationships to a single dynamic node

I am trying out Neo4j for a personal project to implement a recommendation system. The system takes several strings as Input and Outputs a recommendation. The system has nodes in the form of Animals and Groups. The relationship between animals and groups is that an animal belongs to a group. An animal can belong to multiple groups.
The input can be any number of animals. The question I am attempting to answer is "Which animals are present in the groups that contain all the input animals?"
An example of correct output:
Input: Lion, Parrot, Giraffe
Output: Elephant, Zebra
The lion, parrot and giraffe all belong to group 2 and 3. The elephant belongs to group 2 and the zebra belongs to group 3, so they are outputted.
My current solution:
Match (:Animal { name: "Parrot" })
-[:BELONGS_TO]->(matchingGroup:Group)
<-[:BELONGS_TO]-(:Animal { name: "Lion" }),
(:Animal { name: "Giraffe" })
-[:BELONGS_TO]->matchingGroup
<-[:BELONGS_TO]-(animalsInMatchingGroup:Animal)
Return animalsInMatchingGroup.name AS name, count(animalsInMatchingGroup.name) as matches
ORDER BY count(animalsInMatchingGroup.name) DESC
The Problem:
The problem arises when I have more than two animals in my query. In the query above I am querying the graph using Match statements equal to the number of input animals - 1. I am wondering if anyone knows of a better solution to this problem that will prevent querying the graph multiple times.
This is the graph.
http://s29.postimg.org/inhhvqcd3/Screen_Shot_2014_10_05_at_8_09_23_PM.png
Create statement
CREATE
(elephant:Animal { name: 'Elephant' }),
(lion:Animal { name: 'Lion' }),
(tiger:Animal { name: 'Tiger' }),
(giraffe:Animal { name: 'Giraffe' }),
(parrot:Animal { name: 'Parrot' }),
(zebra:Animal { name: 'Zebra' }),
(group1:Group { name: 'Group 1' }),
(group2:Group { name: 'Group 2' }),
(group3:Group { name: 'Group 3' }),
(group4:Group { name: 'Group 4' }),
(group5:Group { name: 'Group 5' }),
elephant-[:BELONGS_TO]->group2,
elephant-[:BELONGS_TO]->group3,
lion-[:BELONGS_TO]->group1,
lion-[:BELONGS_TO]->group2,
lion-[:BELONGS_TO]->group3,
parrot-[:BELONGS_TO]->group2,
parrot-[:BELONGS_TO]->group3,
parrot-[:BELONGS_TO]->group5,
giraffe-[:BELONGS_TO]->group2,
giraffe-[:BELONGS_TO]->group3,
giraffe-[:BELONGS_TO]->group4,
tiger-[:BELONGS_TO]->group5,
zebra-[:BELONGS_TO]->group4,
zebra-[:BELONGS_TO]->group3
Thanks for your help.
Cheers, Cam.
You can try this:
WITH ['Parrot', 'Lion', 'Giraffe'] AS names
MATCH (:Animal { name: head(names)})-[:BELONGS_TO]->(g:Group)
WITH g,names
MATCH (g)<-[:BELONGS_TO]-(a:Animal)
WITH g,collect(a.name) AS animals,names
WHERE ALL (n IN names
WHERE n IN animals)
RETURN g.name, animals,names,size(animals)
See this console: http://console.neo4j.org/r/vd2mba

Filtering nodes related to specific nodes in cypher

How can one filter all nodes connected to some set of nodes efficiently?
I have this for now:
MATCH (:City { Id: 10 })<-[:LIVES_IN]-(p:Person),
p-[:KNOWS]->(:Person { Name: 'John' }),
p-[:KNOWS]->(:Person { Name: 'Mark' }),
p-[:KNOWS]->(:Person { Name: 'Jane' }),
p-[:KNOWS]->(:Person { Name: 'Mary' })
RETURN p
And this does what I want but it's very slow. It takes about 900ms on a database with approx. 500 nodes labeled as Person.
The problem I want to solve is even bigger
MATCH (:City { Id: 10 })<-[:LIVES_IN]-(p:Person)-[:HAS_ITEM]->(i:Item),
p-[:KNOWS]->(:Person { Name: 'John' }),
p-[:KNOWS]->(:Person { Name: 'Mark' }),
p-[:KNOWS]->(:Person { Name: 'Jane' }),
p-[:KNOWS]->(:Person { Name: 'Mary' })
RETURN i, count(p)
This query finds people that have items and for each item, it returns the count of all people that have that item and also know certain people. This query never finishes (or I didn't wait long enough).
When I remove the 'know certain people' part, the query finishes in about 400ms.
MATCH (:City { Id: 10 })<-[:LIVES_IN]-(p:Person)-[:HAS_ITEM]->(i:Item),
RETURN i, count(p)
What am I doing wrong?
Try reordering it to minimize the number of results it has to search through at a time. Something like this--if knows groups are smaller than the number of people that live in the city and the number of people who have items, then this might even be better (if the people who have items is pretty low, can swap it around):
MATCH (p:Person)-[:KNOWS]->(:Person { Name: 'John' }) // start with john's friends
WITH p
MATCH (:City { Id: 10 })<-[:LIVES_IN]-(p)-[:HAS_ITEM]->(i:Item) // only friends that live in city 10 and have the item...
WITH p, i
MATCH p-[:KNOWS]->(:Person { Name: 'Mark' })
WITH p, i
MATCH p-[:KNOWS]->(:Person { Name: 'Jane' })
WITH p, i
MATCH p-[:KNOWS]->(:Person { Name: 'Mary' })
RETURN i, count(p)
Also, make sure you have an index on :Person(Name) and :City(Id)

Resources