Return nodes with only one specified relationship - neo4j

i have a graph like this:
What i wanna do, is to return actors, which only played in one specified film.
So for example if I pass an Iron Man 2 id, it should return only Robert Downey. Scarlett Johansson is skipped because she has played in 3 films, and also she has a "IS_FRIEND_WITH" relationship.
This code which i wrote, return all the actors that played in film of id 11 in this case
MATCH (a)
WHERE ID(a) = 11
MATCH (b:Actor)-[:ACTED_IN]-(a:Movie)
RETURN b
If Iron Man 2 would be of Id = 11 it would return Scarlett and Robert Downey. I just want it to return only Robert Downey.

You need to apply another filter to select only nodes that have a single ACTED_IN relationship.
MATCH (a:Movie)
WHERE ID(a) = 11
MATCH (b:Actor)-[:ACTED_IN]-(a)
// apply filter for nodes that have only a single ACTED_IN relationship
WITH b, size((b)-[:ACTED_IN]->()) as number_of_movies
WHERE number_of_movies = 1
RETURN b

I would do
WITH 11 AS movieId
MATCH (a:Actor)
WHERE [(a)-[:ACTED_IN]->(m:Movie) | id(m)] = [movieId]
RETURN a

As an alternative to the other answers you could do
MATCH (a:Movie)
WHERE ID(a) = 11
MATCH (b:Actor)-[:ACTED_IN]->(a)
// Make sure there are no ACTED_IN relationships apart from the one to a
AND NOT (:Movie)<-[:ACTED_IN]-(b)-[:ACTED_IN]->(a)
RETURN b
This might have a minor advantage in that it doesn't first collect all the :ACTED_IN relationships of the actor, but instead can quit the query as soon as it finds an additional relationship.
If you also want to remove the ones that have an IS_FRIENDS_WITH relationship then you can modify the query to
MATCH (a:Movie)
WHERE ID(a) = 11
MATCH (b:Actor)-[:ACTED_IN]->(a)
// Make sure no other relationships exist
AND NOT ()<-[]-(b)-[:ACTED_IN]->(a)
RETURN b

Here is my suggestion:
we can specify the title of the movie to filter the query results,
then we use with to specify the variables that we will pass to the next part, among the variable we pass the number of movies which an actor acted in .
In the second part of the query we verify that the actor has acted only in the given movie.
Finally we collect these actors and return them as list along with the movie.
MATCH(a:Person)-[:ACTED_IN]->(m:Movie {title:'The Matrix'})
WITH size((a)-[:ACTED_IN]->(:Movie)) AS uniqueMovieActor, m as theMovie
MATCH()
WHERE uniqueMovieActor = 1
RETURN collect(DISTINCT actor) as uniqueMovieActors, theMovie

Related

How to use the result of another query?

I was making some requests on Neo4j, and I've a little trouble at making one in particular.
These are the nodes and relationships I have on my database
Two of the elements:
I want to return the persons who are driving ("conduire") the same "Voiture" as Alice.
This is what I wrote:
MATCH ((a:Personne)-[p:Conduire]->(b:Vehicule))
WHERE b.`Type`='Voiture' AND id(b) in [MATCH ((a:Personne)-[p:Conduire]->(b:Vehicule))
WHERE a.Nom='Alice'
RETURN id(b)]
RETURN a.Nom
Here is the query to return the persons who are driving the same car as Alice. Note that this will not return Alice because the match on b will not repeat the match made on a.
MATCH (a:Personne)-[:Conduire]->(:Vehicule)<-[:Conduire]-(b:Personne)
WHERE a.Nom='Alice'
RETURN b.Nom

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

Update properties of many nodes selected by ID

This is a slightly different question from one answered here:
Update multiple nodes in a single query, each with different property / value pairs
The selected answer to that question presented a nifty query to update many nodes by their 'uuid':
UNWIND { data } AS d
MERGE (x {uuid: d.uuid})
SET x += d.props
My question is how would you accomplish this if you wanted to select by ID(x)? While most of my nodes do have a uuid property, my relationships do not and I need to update their properties as well.
Thanks!
You'll need to process your nodes and relationships separately. Relationships would process similar to this:
UNWIND { data } AS d
MATCH ()-[r]-()
WHERE id(r) = d.id
SET r += d.props
Node processing would be similar, just use MATCH (n) WHERE id(n) = d.id

Neo4J node traversal cypher where clause for each node

I've been playing with neo4j for a geneology site and it's worked great!
I've run into a snag where finding the starting node isn't as easy. Looking through the docs and the posts online I haven't seen anything that hints at this so maybe it isn't possible.
What I would like to do is pass in a list of genders and from that list follow a specific path through the nodes to get a single node.
in context of the family:
I want to get my mother's father's mother's mother. so I have my id so I would start there and traverse four nodes from mine.
so pseudo query would be
select person (follow childof relationship)
where starting node is me
where firstNode.gender == female
AND secondNode.gender == male
AND thirdNode.gender == female
AND fourthNode.gender == female
Focusing on the general solution:
MATCH p = (me:Person)-[:IS_CHILD_OF*]->(ancestor:Person)
WHERE me.uuid = {uuid}
AND length(p) = size({genders})
AND extract(x in tail(nodes(p)) | x.gender) = {genders}
RETURN ancestor
here's how it works:
match the starting node by id
match all the variable-length paths going to any ancestor
constrain the length of the path (i.e. the number of relationships, which is the same as the number of ancestors), as you can't parameterize the length in the query
extract the genders in the path
nodes(p) returns all the nodes in the path, including the starting node
tail(nodes(p)) skips the first element of the list, i.e. the starting node, so now we only have the ancestors
extract() extracts the genders of all the ancestor nodes, i.e. it transforms the list of ancestor nodes into their genders
the extracted list of genders can be compared to the parameter
if the path matched, we can return the bound ancestor, which is the end of the path
However, I don't think it will be faster than the explicit solution, though the performance could remain comparable. On my small test data (just 5 nodes), the general solution does 26 DB accesses whereas the specific solution only does 22, as reported by PROFILE. Further profiling would be needed on a larger database to compare the performances:
PROFILE MATCH p = (me:Person)-[:IS_CHILD_OF*]->(ancestor:Person)
WHERE me.uuid = {uuid}
AND length(p) = size({genders})
AND extract(x in tail(nodes(p)) | x.gender) = {genders}
RETURN ancestor
The general solution has the advantage of being a single query which won't need to be parsed again by the Cypher engine, whereas each generated query will need to be parsed.
It was more simple than I thought. Maybe there is still a better way so I'll leave this open for a bit.
the query would be
MATCH (n1:Person { Id: 'f59c40de-506d-4829-a765-7a3ae94af8d1' })
<-[:CHILDOF]-(n2 { Gender:'0'})
<-[:CHILDOF]-(n3 { Gender:'1'})
<-[:CHILDOF]-(n4 { Gender:'1'})
RETURN n4
and for each generation back would add a new row.
The equivalent query would look something like this:
MATCH (me:Person)
WHERE me.ID = ?
WITH me
MATCH (me)-[r:childof*4]->(ancestor:Person)
WITH ancestor, EXTRACT(rel IN r | endNode(rel).gender) AS genders
WHERE genders = ?
RETURN ancestor
Disclaimer, I haven't double-checked the syntax.
In Neo4j you typically find your start node first, typically by an ID of some sort (modify as required to match on a unique property). We then traverse a number of relationships to an ancestor, extract the gender property of all end nodes in the traversed relationships, and compare the genders to the expected list of genders (you'll need to make sure the argument is a bracketed list in the desired order).
Note that this approach filters down all possible results with that degree of childof relationship as opposed to walking your graph, so higher degrees of relationship (the higher the degree of ancestry you're querying), the slower the call will get.
I'm also unsure if you can parameterize the degree of the variable relationship, so that might prevent this from being a generalized solution for any degree of ancestry.
I'm not sure if you want a generic query which can work whatever the collection of genders you pass, or a specific solution.
Here's the specific solution: you match the path with the wanted length, and match each gender, as you've already noted in your own answer.
MATCH (me:Person)-[:IS_CHILD_OF]->(p1:Person)
-[:IS_CHILD_OF]->(p2:Person)
-[:IS_CHILD_OF]->(p3:Person)
-[:IS_CHILD_OF]->(p4:Person)
WHERE me.uuid = {uuid}
AND p1.gender = {genders}[0]
AND p2.gender = {genders}[1]
AND p3.gender = {genders}[2]
AND p4.gender = {genders}[3]
RETURN p4
Now, if you want to pass in a list of genders of an arbitrary length, it's actually possible. You match a variable-length path, make sure it has the right length (matching the number of genders), then match each gender in sequence.
MATCH p = (me:Person)-[:IS_CHILD_OF*]->(ancestor:Person)
WHERE me.uuid = {uuid}
AND length(p) = size({genders})
AND all(i IN range(0, size({genders}) - 1)
WHERE {genders}[i] = extract(x in tail(nodes(p)) | x.gender)[i])
RETURN ancestor
Building on #InverseFalcon's answer, you can actually compare collections, which simplifies the query:
MATCH p = (me:Person)-[:IS_CHILD_OF*]->(ancestor:Person)
WHERE me.uuid = {uuid}
AND length(p) = size({genders})
AND extract(x in tail(nodes(p)) | x.gender) = {genders}
RETURN ancestor

neo4j - match nodes and add relationships to them

how do i add relationships to nodes returned by a cypher query?
I have written a query that returns me all the person nodes who have the same surname who live at the same address. I now want to add a relationship between these person nodes to indicate they are the same person. The query below returns me 3 person nodes and I want to add a relationship from the first node (returned by the ORDER BY) to the other 2.
MATCH (a:Address) <-[LIVES_AT]-(p:Person)
WITH a as addnode, p.surname as psurname, COUNT(p.name_urn) as c
WHERE c > 1
MATCH (a2:Address{address_urn:addnode.address_urn})<-[LIVES_AT]- (p2:Person{surname:psurname})
WITH p2 as p2node
ORDER BY CASE
WHEN p2node.master_record = 'Y'
THEN
1
ELSE
2
END
WITH collect(p2node) as colp2node
RETURN colp2node
Hope this makes sense? Please advise if there is a better way of doing this.
Something like this should work for you:
MATCH (a:Address)<-[LIVES_AT]-(p:Person)
WITH a, p.surname AS psurname, COUNT(p.name_urn) AS c
WHERE c > 1
MATCH (a2:Address { address_urn:a.address_urn })<-[LIVES_AT]-(p2:Person { surname:psurname })
WITH p2
ORDER BY CASE WHEN p2.master_record = 'Y' THEN 1 ELSE 2 END
WITH collect(p2) AS colp2
WITH colp2[0] AS master, colp2[1..] AS others
UNWIND others AS other
MERGE (master)-[:HAS_ALIAS]->(other);
I use MERGE to avoid duplicate relationships.
By the way, just because two people have the same surname and live at the same address, that does not normally mean they are the same person. I hope you are sure that what you are doing is appropriate.

Resources