Reputation: 11237
I am trying to write a Cypher query which is kind of a "union" query, basically something like :
MATCH (myNodeUsedInAllSubQueries)
WITH myNodeUsedInAllSubQueries
MATCH (a)-[...]->(myNodeUsedInAllSubQueries)
RETURN a
UNION
MATCH (b)-[...]->(myNodeUsedInAllSubQueries)
RETURN b
...
I want to match (myNodeUsedInAllSubQueries)
once at the beginning because this node is used in all subqueries.
Of course, the retrieval of this node in my example is not just as simple as this (it involves relationships and a WHERE
clause, but I'll keep it simple here).
I'll try to explain this with a simple example and what I've tried so far.
Graphically, the graph structure is the following :
(John: Person) ----------[LIKES]----------> (pizza: Food)
^
(Developers: Group) ------[LIKES]------┘
(Jim: Person) --[BELONGS_TO]--> (Business: Group) --[LIKES]--> (apple: Food)
The example is accessible here in Neo4J console. The import script is here (in case you want to reproduce the issue locally) :
CREATE (john: Person{name: "John"});
CREATE (jim: Person{name: "Jim"});
CREATE (devs: Group{name: "Developers"});
CREATE (business: Group{name: "Business"});
CREATE (pizza: Food{name: "pizza"});
CREATE (apple: Food{name: "apple"});
MATCH (john: Person{name: "John"}), (pizza: Food{name: "pizza"})
CREATE (john)-[:LIKES]->(pizza);
MATCH (jim: Person{name: "Jim"}), (business: Group{name: "Business"})
CREATE (jim)-[:BELONGS_TO]->(business);
MATCH (devs: Group{name: "Developers"}), (pizza: Food{name: "pizza"})
CREATE (devs)-[:LIKES]->(pizza);
MATCH (business: Group{name: "Business"}), (apple: Food{name: "apple"})
CREATE (business)-[:LIKES]->(apple);
For that particular example, my question is : how to retrieve all Person
nodes who likes pizza? Knowing that :
Person
can like Food
(direct relation)Group
can like Food
, and transitively if person A
BELONGS_TO
a group,
he LIKES
also the food that the group LIKES
(but there is no direct relation
between Person
and Food
in that case)I'd also like to retrieve the pizza
node once. As I said in introduction, in my real example, the retrieval of pizza
could be quite complicated, so I don't want to duplicate the MATCH
in each case.
UNION
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
RETURN p
UNION
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(pizza)
RETURN p
This query returns surprinsingly 2 nodes : John
and Jim
. After investigating, I have understood that
this is because (pizza)
in the 2nd UNION
clause does not refer to the pizza
node
returned by the first MATCH
, but rather any node. We can confirm that by running :
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
RETURN p
UNION
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(anything)
WHERE anything = pizza
RETURN p
This returns an error :
Neo.ClientError.Statement.SyntaxError: Variable `pizza` not defined (line 7, column 18 (offset: 184))
"WHERE anything = pizza"
, validating the fact that pizza
node is not propagated to the second MATCH
.
WITH
and COLLECT
This fixes the problem of the pizza
node that cannot be passed from the first MATCH
to the 3nd MATCH
.
MATCH (pizza: Food{name: "pizza"})
WITH pizza
MATCH (p: Person)-[:LIKES]->(pizza)
WITH pizza, COLLECT(p) AS people
MATCH (p: Person)-[:BELONGS_TO]->(g: Group)-[:LIKES]->(pizza)
RETURN people + COLLECT(p) AS people
But this query does not return anything : this is because the 3nd MATCH
does not return anything
(there are no Person
in a Group
that LIKES
pizza
).
If I add a Person
in the Developers
Group
:
MATCH (devs: Group{name: "Developers"})
CREATE (emma: Person{name: "Emma"})-[:BELONGS_TO]->(devs)
The previous query returns successfully John and Emma because the last MATCH
returns at least one Person
.
But in my case, it is possible that this MATCH
does not return anything, so that does not work.
I hope this example is simple and clear. In this case, the query I'm looking for should return only John, because he's the only Person
here who likes pizza
.
Upvotes: 1
Views: 1031
Reputation: 66999
[UPDATED]
This should work for your simple use case:
MATCH (p:Person)-[:BELONGS_TO|LIKES*..2]->(food:Food {name:"pizza"})
RETURN food, COLLECT(p) AS people;
Upvotes: 1
Reputation: 30397
As an alternate to cybersam's answer, if your graph structure is such that MATCH (p:Person)-[:BELONGS_TO|LIKES*..2]->(food:Food)
won't work (for example if you're working on a graph where people can like each other, and you don't want to return a person who likes another person who likes pizza, which would adhere to your pattern), you can use a 0..1
length relationship to represent an optional step in the pattern:
MATCH (p:Person)-[:BELONGS_TO*0..1]->()-[:LIKES]->(food:Food)
WHERE food.name = "pizza"
RETURN COLLECT(p) AS people;
This allows matching to both of these patterns at the same time:
(p:Person)-[:LIKES]->(food:Food)
and
(p:Person)-[:BELONGS_TO]->()-[:LIKES]->(food:Food)
I've written up a knowledge base article on this approach.
Upvotes: 1