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

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

Related

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

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

ActiveRecord #includes, with condition, but load all relations, not just those matching condition

I have ActiveRecord models Parent and Kid. Parents have many Kids.
I have a Parent whose name is Dad, with 3 Kids, whose names are Abel, Bobby, and Cain. I want to find the Parent based on the name of only 1 of the Kids.
parent = Parent.includes(:kids).
find_by(kids: { name: 'Bobby' })
The above query provides me the Parent I want, but parent.kids only includes Bobby. I want Abel and Cain to be included in parent.kids as well, just like if I did:
Parent.find_by(name: 'Dad').kids
I could do:
Parent.includes(:kids).
find_by(kids: { name: 'Bobby' }).
tap { |parent| parent&.kids.reload }
But is there not a “better” way of fetching the rest of the kids? For instance, is there a way I could load all the kids of the parent while querying for the parent (via one SQL statement)?
How about making the Parent.includes(:kids).find_by(kids: { name: 'Bobby' } a subquery
Parent.includes(:kids).where(id: Parent.includes(:kids).find_by(kids: { name: 'Bobby' })
You want an INNER JOIN:
parents = Parent.joins(:kids).where(kids: { name: 'Bobby' })
This will only include rows from parents with a match in the kids table. You can apply it as a subquery to avoid removing the rest of the joined rows:
parents = Parent.where(
id: Parent.joins(:kids).where(kids: { name: 'Bobby' })
).includes(:kids)

Neo4j - Build Prioritization Rule

We are trying to build a recommendation engine for knowledge management system (KMS) using Neo4j. The idea is to get the published articles that are relevant to a user based on certain criteria and then prioritize the articles to decide the sequence of articles.
For Example - There are a total of 100 articles published in my KMS, out of those 50 are relevant to me based on the metadata properties attached to every article (I have queries that can get me this information), but, I only have a placeholder to show 10 articles.
For this, I have a prioritization matrix that decides the priority of all the 50 articles relevant to me and get the 10 (highly prioritized) that I want to show to the user.
Prioritization matrix is as follows -
Background - Every article will have certain actions - comment, share, like and download. And every colleague will have its own organizational hierarchy like manager, peers, manager of manager etc.
What is required - Every time an action is performed on the article, a score is attached to it. If the action is performed by my manager, it will have a higher score as compared to the action performed by my peer.
Here is the score list:
Actions (Score)-
like (1),
comment (2),
share (5),
download (3)
Association (Multiplication factor) -
Peer (2),
Manager (3),
MOM (3)
Article score formula - ActionScore * AssociationFactor
Scenario -
Article 1 (Liked by my manager and commented by 3 peers) - Score is 1*3 + 3(2*2) = 15
Article 2 (3 Comments added by MOM, liked by 5 peers) - Score is 3(2*3) + 5(1*2) = 28
Article 3 (Commented by 4 peers, liked by MOM and liked by 2 peers) - Score is 4(2*2) + 1*3 + 2(1*2) = 23
According to the score chart, the sequence of my articles should be -
Article 2
Article 3
Article 1
I need to calculate these scores on the fly against all the articles relevant to a colleague and then, return them according to the score attached. How can this be done in Neo4j?
Editted -
Here is the sample code to create two articles, similarly we can create more articles with the desired metadata -
CREATE (knowledge:Article { name: 'Article1' }),(bu:BU { name: 'ARS' }),(date:Date { name: 'Date' }),(practice:Practice { name: 'Practice' }),(category:Category { name: 'Category' }),(place:Place { name: 'Place' }),(subcat:Subcategory { name: 'Subacategory' }),(geo:Geography { name: 'US' }),(lang:Language { name: 'Language' }),(trans:Translation { name: 'Translation' }),(keyword:Keyword { name: 'Keyword' }),(colleague1:Colleague { name: 'GS',collid:'567890',BU:'ASC',Country:'India' }),(colleague2:Colleague { name: 'DN',collid:'765432',BU:'ASC',Country:'India' }),(colleague3:Colleague { name: 'PK',collid:'324567',BU:'ASC',Country:'India' }),(colleague4:Colleague { name: 'PMM',collid:'717865',BU:'ARS',Country:'US' }),(knowledge)-[:ASSOCIATED_TO]->(bu),(knowledge)-[:PUBLISHED_ON]->(date),(knowledge)-[:UPDATED_ON]->(date),(knowledge)-[:LIKED_ON]->(date),(knowledge)-[:COMMENTED_ON]->(date),(knowledge)-[:PUBLISHED_IN]->(place),(knowledge)-[:LOCATED_IN]->(geo),(knowledge)-[:IS_OF_SUBTYPE]->(subcat),(knowledge)-[:HAS_ORIGINAL_LANG]->(lang),(knowledge)-[:TRANSLATED_TO]->(trans),(trans)-[:IN_LANGUAGE]->(lang),(knowledge)-[:IS_SUBASSOCIATED_TO]->(practice),(knowledge)-[:IS_OF_TYPE]->(category),(subcat)-[:IS_PART_OF]->(category),(knowledge)-[:WRITTEN_BY]->(colleague1),(knowledge)-[:CONTACT_TO]->(colleague1),(knowledge)-[:UPDATED_BY]->(colleague3),(knowledge)-[:LIKED_BY]->(colleague1),(knowledge)-[:COMMENTED_BY]->(colleague1),(colleague1)-[:IS_A_MANAGER_OF]->(colleague2),(colleague1)-[:FOLLOWS]->(colleague2),(colleague1)-[:IS_A_MANAGER_OF]->(colleague4),(colleague1)-[:IS_A_MANAGER_OF]->(colleague3)
MERGE (col1:Colleague { name: 'MK', collid:'98762',BU:'ASC',Country:'US'})
MERGE (col2:Colleague { name: 'AA', collid:'01234567',BU:'ASC',Country:'US'})
MERGE (col3:Colleague { name: 'PM', collid:'45722',BU:'ASC',Country:'US'})
MATCH (col2:Colleague {name:'AA'}),(col1:Colleague { name: 'PM'})
CREATE (col2)-[:IS_A_MANAGER_OF]->(col1)
MATCH (col2:Colleague {name:'PM'}),(col1:Colleague { name: 'MK'})
CREATE (col2)-[:IS_A_MANAGER_OF]->(col1)
MATCH (col2:Colleague {name:'PM'}),(col1:Colleague { name: 'GS'})
CREATE (col2)-[:IS_A_MANAGER_OF]->(col1)
MATCH (colleague:Colleague { name: 'DN' })
MATCH (coll:Colleague { name: 'PK' })
MERGE (knowledge:Article { name: 'Article2' })
MERGE (bu:BU { name: 'ASC' })
MERGE (date:Date { name: 'Date' })
MERGE (practice:Practice { name: 'Practice' })
MERGE (category:Category { name: 'Category' })
MERGE (place:Place { name: 'Place' })
MERGE (subcat:Subcategory { name: 'Subacategory' })
MERGE (geo:Geography { name: 'India' })
MERGE (lang:Language { name: 'English' })
MERGE (trans:Translation { name: 'Translation' })
MERGE (keyword:Keyword { name: 'Keyword' })
MERGE (knowledge)-[:ASSOCIATED_TO]->(bu)
MERGE (knowledge)-[:PUBLISHED_ON]->(date)
MERGE (knowledge)-[:UPDATED_ON]->(date)
MERGE (knowledge)-[:LIKED_ON]->(date)
MERGE (knowledge)-[:COMMENTED_ON]->(date)
MERGE (knowledge)-[:PUBLISHED_IN]->(place)
MERGE (knowledge)-[:LOCATED_IN]->(geo)
MERGE (knowledge)-[:IS_OF_SUBTYPE]->(subcat)
MERGE (knowledge)-[:HAS_ORIGINAL_LANG]->(lang)
MERGE (knowledge)-[:TRANSLATED_TO]->(trans)
MERGE (trans)-[:IN_LANGUAGE]->(lang)
MERGE (knowledge)-[:IS_SUBASSOCIATED_TO]->(practice)
MERGE (knowledge)-[:IS_OF_TYPE]->(category)
MERGE (subcat)-[:IS_PART_OF]->(category)
MERGE (knowledge)-[:WRITTEN_BY]->(colleague)
MERGE (knowledge)-[:CONTACT_TO]->(colleague)
MERGE (knowledge)-[:UPDATED_BY]->(colleague)
MERGE (knowledge)-[:HAVING]->(keyword)
MERGE (knowledge)-[:LIKED_BY]->(coll)
MERGE (knowledge)-[:COMMENTED_BY]->(coll)
Query to get the org chart of the user -
MATCH (me:Colleague)
WHERE me.collid = '567890'
Return me.name as ColleagueName,me.collid as ColleagueID,0 as Level,true as MyData
Union
MATCH (s:Colleague)<-[:IS_A_MANAGER_OF]-(me)
WHERE me.collid = '567890'
Return s.name as ColleagueName,s.collid as ColleagueID,-1 as Level,false as MyData
Union
Match (peers:Colleague)<-[:IS_A_MANAGER_OF]-(mang:Colleague)-[:IS_A_MANAGER_OF]->(me:Colleague)
Where me.collid = '567890'
Return peers.name as ColleagueName,peers.collid as ColleagueID,0 as Level,false as MyData
Union
MATCH path = (m:Colleague)-[:IS_A_MANAGER_OF*]->(me)
WHERE me.collid = '567890'
Return m.name as ColleagueName,m.collid as ColleagueID,length(path) as Level,false as MyData
Below is the criteria to retrieve articles.
Criteria is to get all articles written by my manager and peers-
Match (n:Article)-[:WRITTEN_BY]->(mang:Colleague)-[:IS_A_MANAGER_OF]->(me:Colleague)
Where me.collid='717865'
Return n.name as ArticleName,mang.name as Author,me.name as DataForColleague
Union
Match (n:Article)-[:WRITTEN_BY]->(peers:Colleague)<-[:IS_A_MANAGER_OF]-(mang:Colleague)-[:IS_A_MANAGER_OF]->(me:Colleague)
Where me.collid='717865'
Return n.name as ArticleName,me.name as DataForColleague,peers.name as Author
Here, say as an example, if we had a total of 1000 articles (instead of 2 in this example) and the above query would return 50 articles, then, I would need a prioritization logic as explained above to prioritize the articles and get the top 10 articles on the basis of scores calculated.

neo4j relationship creating multiple nodes

I am new to neo4j and been trying things.
I created two nodes successfully
CREATE (sally:Person { name: 'Sally', age: 32 })
CREATE (john:Person { name: 'John', age: 27 })
With this command:
CREATE (sally)-[:FRIEND_OF { since: 1357718400 }]->(john)
ends up successful but creates two other nodes with different ids.
So, I end up with 4 nodes.
Identifiers for nodes and relationships are only meaningful within a single Cypher query. If you had combined your two queries into one, then sally and john would not have been re-created:
CREATE (sally:Person { name: 'Sally', age: 32 })
CREATE (john:Person { name: 'John', age: 27 })
CREATE (sally)-[:FRIEND_OF { since: 1357718400 }]->(john);
If you really needed to have 2 separate queries, your second query would have to find the sally and john nodes before re-using them. You new query could also use some different identifiers for them (like a and b):
CREATE (sally:Person { name: 'Sally', age: 32 })
CREATE (john:Person { name: 'John', age: 27 })
MATCH (a:Person { name: 'Sally'}), (b:Person { name: 'John'})
CREATE (a)-[:FRIEND_OF { since: 1357718400 }]->(b);
In the second query, I assume that a Person node's name is sufficient to find a unique node.

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