Cypher match nodes which have relationships to ALL nodes matching some criteria - neo4j

Say I have a pizza menu where each type of pizza is represented as a node with label Pizza, and each topping is a node with label Topping. To get all the pizza's with pepperoni I write the following query
MATCH (p:Pizza)-[:HAS]->(t:Topping{type : "pepperoni"}) return p.
Then say I have a set of users who can specify their favorite pizzas.
MATCH (u:User)-[:HAS_FAVORITE]->(p:Pizza).
What is the best way to find the users who like ALL the pizzas having pepperoni ?
Thanks in advance for your time.

The following method compares the count of the user's favorites having pepperoni with the count of all pizzas with pepperoni. It performs well because it matches using an index and relationship traversals to collect only user favorites having pepperoni. The answer posted by cybersam is slow because it has to do a full scan of all favorites.
MATCH (p:Pizza)-[:HAS]->(t:Topping { type : "pepperoni" })
WITH COUNT(p) AS pCnt, COLLECT(p) AS pCol
UNWIND pCol as pPep
MATCH (u:User)-[:HAS_FAVORITE]->(pPep)
WITH u, COUNT(*) as fCnt, pCnt
WHERE fCnt = pCnt
RETURN u

This is how to find all distinct users who like ANY pizza(s) with pepperoni topping:
MATCH (u:User)-[:HAS_FAVORITE]->(p:Pizza)-[:HAS]->(t:Topping {type : "pepperoni"})
RETURN DISTINCT u;
This is one way to find all distinct users who like ALL pizza(s) with pepperoni topping:
MATCH (p:Pizza)-[:HAS]->(t:Topping { type : "pepperoni" })
WITH COLLECT(p) AS ps
MATCH (u:User)-[:HAS_FAVORITE]->(q:Pizza)
WITH u, COLLECT(q) AS qs, ps
WHERE ALL (x IN ps WHERE x IN qs)
RETURN u;

Related

Cypher sort by amount of same relationship?

How can I match using Cypher by any relation amount? For example, in a database there are Person. If this person has any son, they will be related by the relation [:SON].
This query will return each Person that has a son:
match (p:Person)-[:SON]->(:Person)
return p
Knowing this, how can I match the number of Person that only has 2 sons?
You can use size() in the where clause, like so:
match (p:Person) where size((p)-[:SON]->())=2 return p
You can use the count function to get the count of sons for each person. Then filter out the persons based on the count.
MATCH (p:Person)-[r:SON]->(:Person)
WITH p, count(r) as sons
WHERE sons=2
RETURN p

Cypher return a node with the given neighbors

I'm trying to write a query in Cypher which is able to find a single node with the label :CONVERSATION having the given neighbor nodes. The neighbor nodes are users with the label :USER and a property called "username".
In the query a list of "username"s is given and a desired is to find a conversation node which has as its neighbor all the users with the username in the given list.
I have tried some queries but they don't return what I want. Is there anyone who has any idea how the query may look like?
Assuming you are passing the given usernames as a {users} parameters and the relationship between your users and the conversations is named IN_CONVERSATION :
MATCH (c:Conversation)
WHERE ALL( x IN {users} WHERE (:User {name:x} )-[:IN_CONVERSATION]->(c) )
RETURN c
If you want to test the query by passing the usernames in the neo4j browser for eg, you can simulate the parameters with WITH :
WITH ["adam","john","sally"] AS users
MATCH (c:Conversation)
WHERE ALL( x IN users WHERE (:User {name:x} )-[:IN_CONVERSATION]->(c) )
RETURN c
Another solution is to match first the users also :
MATCH (u:User) WHERE u.name IN {users}
MATCH (c:Conversation)
WHERE ALL( x IN collect(u) WHERE (x)-[:IN_CONVERSATION]->(c) )
RETURN c

increase weight between relationship on finding match - cypher

I have a query in cypher , I create a relationship between :
MATCH match (u:user {id : "10662"})-[r:has_profile]->(p:profile)
, (u2:user)-[r2:has_profile]->(p2:profile)
WHERE
p.user_id <> p2.user_id
AND abs(u2.date_birth - u.date_birth) >= 94608000000
merge u-[r:matches_with { weight: rand() }]->u2
RETURN collect(u2.id) as id;
In this particular situation, I am trying to create a relationship between u and u2 only on a match, when a previous relationship between u and u2 is found. It will just increment the weight in the relationship.
And I am trying to return the matches based on weight.
Can someone please suggest an approach for incrementing the weight on finding a match, with this query. Thank you.
Here is a query that looks for all matches_with relationships starting from a specific user, increments the weight of the relationship, and returns the results sorted by weight.
MATCH (u:user { id : "10662" })-[r:matches_with]->(u2)
SET r.weight = r.weight + 1
RETURN u, r, u2
ORDER BY r.weight;
I found a mechanism , by which you can alter the weight..
I added the merge clause before the return statement:
MATCH (u:user { id : "10662" })-[r:matches_with]->(u2)
merge (u)-[m:matches_with]->(u2)
SET m.weight=
CASE WHEN NOT (HAS (m.weight))
THEN toFloat(0.125)
ELSE LAST(m.weight)/2
END
RETURN u.id, m, u2.id
ORDER BY m.weight;
Thank you #cybersam.

Neo4j, Match Relationship WHERE AND

Hello I am trying to match neo4j relationships using 'WHERE AND'
My example relationiship is: 'User Visits Country'
I create it as so...
MATCH (c:Country{Name:Country}) MERGE (u:User{Email:Email,UserID: UserID}) MERGE (u)-[r:Visits]->(c)
//Countries are previously created and Users may or may not exist
Then I query (This Works):
MATCH (u:User)-[r:Visits]->(c:Country) where c.Name='France' or c.Name='Spain' return u
Result: Shows me all users who have visited Spain OR France, even if they have only visited one of two countries.
BUT What Im trying to do is the same exact query, but with 'AND' instead of 'OR'. In which I can get users that have visited both 'France' and 'Spain'.
MATCH (u:User)-[r:Visits]->(c:Country) where c.Name='France' AND c.Name='Spain' return u
Result: 0 nodes and relationship found..
What can I do?
In your query you are matching a single country node and saying the name of that node has to be France and has to be Spain.
What you want is to find all of the users that have vistied both France and Spain. There are a couple of ways you can go...
//match both countries against the same user and identify them separtely
//making two matches in a single query
MATCH (u:User)-[:VISITS]->(c1:Country), (u)-[:VISITS]->(c2:Country)
WHERE c1.name = "France"
AND c2.name = "Spain"
RETURN u.name
//match all users that have been to either and only return the one that have been to both
MATCH (u:User)-[r:VISITS]->(c:Country)
WHERE (c.name IN [ "France", "Spain"])
WITH u, count(*) AS num
WHERE num = 2
RETURN u.name, num
It think number one is better as it is more precise and probably more efficient.
If you only care about 2 countries. this query would also work, in addition to the options provided by #DaveBennett.
MATCH (c1)<-[:VISITS]-(u)-[:VISITS]->(c2)
WHERE c1.name = "France" AND c2.name = "Spain"
RETURN u.name;
What's wrong in your query
MATCH (u:User)-[r:Visits]->(c:Country)
where c.Name='France'
AND c.Name='Spain'
return u
This will always return no rows because you are trying to check two values for the same node's property.
Solution
MATCH (c1:Country)<-[r:Visits]-(u:user)-[r1:Visits]->(c2:Country)
WHERE c1.name = 'France' AND c2.name = 'Spain'
RETURN u.name;
This will return what you need.
Here is one short and useful reference doc of Neo4j:
http://neo4j.com/docs/2.1.2/cypher-refcard/

Select nodes that has all relationships in Neo4j

Suppose I have two kinds of nodes, Person and Competency. They are related by a KNOWS relationship. For example:
(:Person {id: 'thiago'})-[:KNOWS]->(:Competency {id: 'neo4j'})
How do I query this schema to find out all Person that knows all nodes of a set of Competency?
Suppose that I need to find every Person that knows "java" and "haskell" and I'm only interested in the nodes that knows all of the listed Competency nodes.
I've tried this query:
match (p:Person)-[:KNOWS]->(c:Competency) where c.id in ['java','haskell'] return p.id;
But I get back a list of all Person that knows either "java" or "haskell" and duplicated entries for those who knows both.
Adding a count(c) at the end of the query eliminates the duplicates:
match (p:Person)-[:KNOWS]->(c:Competency) where c.id in ['java','haskell'] return p.id, count(c);
Then, in this particular case, I can iterate the result and filter out results that the count is less than two to get the nodes I want.
I've found out that I could do it appending consecutive match clauses to keep filtering the nodes to get the result I want, in this case:
match (p:Person)-[:KNOWS]->(:Competency {id:'haskell'})
match (p)-[:KNOWS]->(:Competency {id:'java'})
return p.id;
Is this the only way to express this query? I mean, I need to create a query by concatenating strings? I'm looking for a solution to a fixed query with parameters.
with ['java','haskell'] as skills
match (p:Person)-[:KNOWS]->(c:Competency)
where c.id in skills
with p.id, count(*) as c1 ,size(skills) as c2
where c1 = c2
return p.id
One thing you can do, is to count the number of all skills, then find the users that have the number of skill relationships equals to the skills count :
MATCH (n:Skill) WITH count(n) as skillMax
MATCH (u:Person)-[:HAS]->(s:Skill)
WITH u, count(s) as skillsCount, skillMax
WHERE skillsCount = skillMax
RETURN u, skillsCount
Chris
Untested, but this might do the trick:
match (p:Person)-[:KNOWS]->(c:Competency)
with p, collect(c.id) as cs
where all(x in ['java', 'haskell'] where x in cs)
return p.id;
How about this...
WITH ['java','haskell'] AS comp_col
MATCH (p:Person)-[:KNOWS]->(c:Competency)
WHERE c.name in comp_col
WITH comp_col
, p
, count(*) AS total
WHERE total = length(comp_col)
RETURN p.name, total
Put the competencies you want in a collection.
Match all the people that have either of those competencies
Get the count of compentencies by person where they have the same number as in the competency collection from the start
I think this will work for what you need, but if you are building these queries programatically the best performance you get might be with successive match clauses. Especially if you knew which competencies were most/least common when building your queries, you could order the matches such that the least common were first and the most common were last. I think that would chunk down to your desired persons the fastest.
It would be interesting to see what the plan analyzer in the sheel says about the different approaches.

Resources