Neo4j, Match Relationship WHERE AND - neo4j

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/

Related

Neo4j find all that match X but don't match Y

I have a simple domain of Students and Courses. A student can be registered for multiple courses.
I want to find all students registered for course 1234 that were not registered for course 5678.
So I tried this attempt:
MATCH (s:Student)
MATCH (c:Course)
MATCH (s)-[r:REGISTRATION]->(c {code: "1234"})
WHERE NOT (s)-[r:REGISTRATION]->(c {code: "5678"})
RETURN s
But it seems like the last WHERE NOT makes no difference to the query? I can still find students from the query result that are also registered for the second course :/
What am I doing wrong here?
You can do:
MATCH (sUnwnated:Student)-[r:REGISTRATION]->(:Course{code: "5678"}) // get all students in "5678"
WITH COLLECT(sUnwnated) AS sUnwnated // group them to a list
MATCH (sWanted:Student)-[r:REGISTRATION]->(:Course{code: "1234"}) // get all students in "1234"
WHERE NOT sWanted IN sUnwnated // but keep only these that are not in the list
RETURN sWanted
Apparently, I was missing only one important keyword/function, exists().
This works:
MATCH (s:Student)
MATCH (c:Course)
MATCH (s)-[r:REGISTRATION]->(c {code: "1234"})
WHERE NOT exists((s)-[r:REGISTRATION]->(c {code: "5678"}))
RETURN s

Using neo4j find the current user unrelated nodes in neo4j

I have a trouble when fetch the unrelated nodes. pls refer the image attached. as per the image "Mother of john" is the current user. i want to find which nodes all are unrelated for "Mother of john"
(Eg: "Father of Mohanraj" , etc.)
here is my query
match (m:member) where id(m) = 36
with m
match (p:member)-[*]-()
with collect(p) as parents
match (w:wedding)<-[:WED_TO]-(i)
where not i in parents
return i
Thanks in advance
In your query, m is not used to filter the results of the second part.
I would go along the lines below:
// return the user
MATCH (m:member) where id(m) = 36
WITH m
// return all members that are not related to m
MATCH (p:member)
WHERE NOT (p)-[:WED_TO|PARENT_FROM*]-(m)
RETURN p

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.

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

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;

How do I return unique elements of a Union of Collections in Cypher?

I have the following problem: I want to return all unique bottles that a critic has reviewed, if any reviewed by them are sold currently in selected shops.
The problem is that I get duplicates in both collections and while I can filter them individually, I cannot filter the joined result:
START shop=node:shops('SHOP_ID:...')
MATCH (shop)-[:SELLS]->(bottle)<-[:REVIEWED]-(critic)-[:REVIEWED]->(other_bottle)
WITH critic, COLLECT(DISTINCT(bottle))) + COLLECT(DISTINCT(other_bottle))) as all_bottles
RETURN wine_critic, all_bottles
Try:
START shop=node:shops('SHOP_ID:...')
MATCH (shop)-[:SELLS]->(bottle)<-[:REVIEWED]-(critic)
WITH critic
MATCH (critic)-[:REVIEWED]->(other_bottle)
RETURN critic, COLLECT(other_bottle) as all_bottles

Resources