Neo4j match multiple relationships - neo4j

How can I write a query that gets nodes that have relationships to ALL nodes of a set. For example:
START n=node:people("username:*"),
g=node:groups("groupname:A groupname:B")
MATCH n-[:M]->g
RETURN n
This returns users that have relationships to A or B. But I want users that have relationships to A and B. I can't figure out how to do it though.
Edit:
I need to do this for an arbitrary number of groups, not just A and B. And the reason I'm using index syntax is that this is from user input, so it could be this:
START n=node:people("username:*"),
g=node:groups("groupname:*")
MATCH n-[:M]->g
RETURN n
And I would need to return users that have the M relationship with ALL groups.

START n=node:people("username:*"),
g=node:groups("groupname:*")
with n, collect(g) as groups
MATCH n-[:M]->ug
with n, collect(ug) as user_groups
where ALL(g in groups WHERE g in user_groups)
RETURN n
it might even work like this (should be faster)
START n=node:people("username:*"),
g=node:groups("groupname:*")
with n, collect(g) as groups
MATCH n-[:M]->ug
WHERE ug in groups
with n, count(*) as found, length(groups) as group_count
WHERE found = group_count
RETURN n

Separate groups A and B into distinct variables and then ensure each match exists individually.
START n=node:people("username:*"),
gA=node:groups("groupname:A"),
gB=node:groups("groupname:B")
MATCH n-[:M]->gA, n-[:M]->gB
RETURN n

Related

Separating matching nodes in a query result

I defined the directed relation Know on person nodes. For example, if Sara knows Alice then Sara-> Alice. I wrote this Cypher query to find all the people who know both the right and left side of the directed relation.
match ((n:Person)-[:Know]-> (m:Person)),(p:Person)
where EXISTS ((m)<-[:Know]-(p)-[:Know]->(n))
RETURN m,n,p
I need to get subgraphs with 3 nodes in the query's result but the result I get is a graph with many nodes. Is there any method to change the query to generate subgraphs with just 3 nodes (for example, a subgraph of Alex-> Sara, Alex-> Alice, Sara-> Alice and if Sara has the same condition on two other people it is shown in another subgraph). This requires repeating some nodes in the output.
MATCH clauses are more flexible than that. Try this:
MATCH (n:Person)-[:Know]->(m:Person)<-[:Know]-(p:Person)-[:Know]->(n)
WHERE NOT EXISTS (()-[:Know]->(p))
AND NOT EXISTS {
WITH m, n, p
MATCH (q:Person)-[:Know]->(m)
WHERE q <> n
AND q <> p
}
AND NOT EXISTS {
WITH m, n, p
MATCH (q:Person)-[:Know]->(n)
WHERE q <> p
}
RETURN m, n, p
You might have to use a unique ID property, and I'm not sure if the WITH clause will work here as I've gotten it; but with subqueries, you are generally able to import variables from above using WITH.

How to get all nodes from "multi match" cypher

I'm trying to make a cypher query to make nodes list which is using "multi match" as follows:
MATCH (N1:Node)-[r1:write]->(P1:Node),
(P1:Node)-[r2:access]->(P2:Node)<-[r3:create]-(P1)
WHERE r1.Time <r3.Time and r1.ID = r2.ID and r3.Time < r2.Time
return nodes(*)
I expect the output of the Cypher to be all nodes of the result, but Cypher doesn't support nodes(*).
I know that there is a way to resolve this like thisa;
MATCH G1=(N1:Node)-[r1:write]->(P1:Node),
G2=(P1:Node)-[r2:access]->(P2:Node)<-[r3:create]-(P1)
WHERE r1.Time <r3.Time and r1.ID = r2.ID and r3.Time < r2.Time
return nodes(G1), nodes(G2)
But the match part could be changed frequently so I want to know the way to get nodes from multi-match without handling variable for every match.
Is it possible?
Your specific example is easy to consolidate into a single path, as in:
MATCH p=(:Node)-[r1:write]->(p1:Node)-[r2:access]->(:Node)<-[r3:create]-(p1)
WHERE r1.Time < r3.Time < r2.Time AND r1.ID = r2.ID
RETURN NODES(p)
If you want the get distinct nodes, you can replace RETURN NODES(p) with UNWIND NODES(p) AS n RETURN DISTINCT n.
But, in general, you might be able to use the UNION clause to join the results of multiple disjoint statements, as in:
MATCH p=(:Node)-[r1:write]->(p1:Node)-[r2:access]->(:Node)<-[r3:create]-(p1)
WHERE r1.Time < r3.Time < r2.Time AND r1.ID = r2.ID
UNWIND NODES(p) AS n
RETURN n
UNION
MATCH p=(q0:Node)-[s1:create]->(q1:Node)-[s2:read]->(q0)
WHERE s2.Time > s1.Time AND s1.ID = s2.ID
UNWIND NODES(p) AS n
RETURN n
The UNION clause would combine the 2 results (after removing duplicates). UNION ALL can be used instead to keep all the duplicate results. The drawback of using UNION (ALL) is that variables cannot be shared between the sub-statements.

Nodes with relationship to multiple nodes

I want to get the Persons that know everyone in a group of persons which know some specific places.
This:
MATCH (:Place {name:'Breiter Weg'})<-[:knows]-(b:Person)-[:knows]->(:Place {name:'Buchhandel'})
WITH collect(DISTINCT b) as persons
Match (a:Person)
WHERE ALL(b in persons WHERE (a)-[:knows]->(b))
RETURN a
works, but for the second part does a full nodelabelscan, before applying the where clause, which is extremely slow - in a bigger db it takes 8~9 seconds. I also tried this:
MATCH (:Place {name:'Breiter Weg'})<-[:knows]-(b:Person)-[:knows]->(:Place {name:'Buchhandel'})
Match (a:Person)-[:knows]->(b)
RETURN a
This only needs 2ms, however it returns all persons that know any person of group b, instead of those that know everyone.
So my question is: Is there a effective/fast query to get what i want?
We have a knowledge base article for this kind of query that show a few approaches.
One of these is to match to :Persons known by the group, and then count the number of times each of those persons shows up in the results. Provided there aren't multiple :knows relationships between the same two people, if the count is equal to the collection of people from your first match, then that person must know all of the people in the collection.
MATCH (:Place {name:'Breiter Weg'})<-[:knows]-(b:Person)-[:knows]->(:Place {name:'Buchhandel'})
WITH collect(b) as persons
UNWIND persons as b // so we have the entire list of persons along with each person
WITH size(persons) as total, b
MATCH (a:Person)-[:knows]->(b)
WITH total, a, count(a) as knownCount
WHERE total = knownCount
RETURN a
Here is a simpler Cypher query that also compares counts -- the same basic idea used by #InverseFalcon.
MATCH (:Place {name:'Breiter Weg'})<-[:knows]-(b:Person)-[:knows]->(:Place {name:'Buchhandel'}), (a:Person)-[:knows]->(b)
WITH COLLECT({a:a, b:b}) as data, COUNT(DISTINCT b) AS total
UNWIND data AS d
WITH total, d.a AS a, COUNT(d.b) AS bCount
WHERE total = bCount
RETURN a

cypher to combine nodes and relationships into a single column

So as a complication to this question, I basically want to do
MATCH (n:TEST) OPTIONAL MATCH (n)-[r]->() RETURN DISTINCT n, r
And I want to return n and r as one column with no repeat values. However, running
MATCH (n:TEST) OPTIONAL MATCH (n)-[r]->() UNWIND n+r AS x RETURN DISTINCT x
gives a "Type mismatch: expected List but was Relationship (line 1, column 47)" error. And this query
MATCH (n:TEST) RETURN DISTINCT n UNION MATCH ()-[n]->() RETURN DISTINCT n
Puts nodes and relationships in the same column, but the context from the first match is lost in the second half.
So how can I return all matched nodes and relationships as one minimal list?
UPDATE:
This is the final modified version of the answer query I am using
MATCH (n:TEST)
OPTIONAL MATCH (n)-[r]->()
RETURN n {.*, rels:collect(r {properties:properties(r), id:id(r), type:type(r), startNode:id(startNode(r)), endNode:id(endNode(r))})} as n
There are a couple ways to handle this, depending on if you want to hold these within lists, or within maps, or if you want a map projection of a node to include its relationships.
If you're using Neo4j 3.1 or newer, then map projection is probably the easiest approach. Using this, we can output the properties of a node and include its relationships as a collected property:
MATCH (n:TEST)
OPTIONAL MATCH (n)-[r]->()
RETURN n {.*, rels:collect(r)} as n
Here's what you might do if you wanted each row to be its own pairing of a node and a single one of its relationships as a list:
...
RETURN [n, r] as pair
And as a map:
...
RETURN {node:n, rel:r} as pair
EDIT
As far as returning more data from each relationship, if you check the Code results tab, you'll see that the id, relationship type, and start and end node ids are included, and accessible from your back-end code.
However, if you want to explicitly return this data, then we just need to include it in the query, using another map projection for each relationship:
MATCH (n:TEST)
OPTIONAL MATCH (n)-[r]->()
RETURN n {.*, rels:collect(r {.*, id:id(r), type:type(r), startNode:startNode(r), endNode:endNode(r)})} as n

Neo4j Aggregation Subtraction Nodes and properties

I'm starting with Neo4j and using graphs, and I'm trying to get the following:
I have to find the subtraction(difference) between the number of users (each user is a node) and the number of differents names they have. I have 16 nodes, and each one has his own name (name is one of the properties it has), but some of them have the same name (for example the node A has (Name:Amanda,City:Roma) and node B has (Name:Amanda, City:Paris), so I will have less name's count because some of them are repeated.
I have tried this:
match (n) with n, count(n) as c return sum(c)
That gives me the number of nodes. And then I tried this
match (n) with n, count(n) as nodeC with n, count( distinct n.Name) as
nameC return sum(nodeC) as sumN, sum(nameC) as sumC, sumN-sumC
But it doesn't work (I'm not sure if even i'm getting the names well, because when I try it, separated, it doesn't work neither).
I think this is what you are looking for:
MATCH (n)
RETURN COUNT(n) - COUNT(DISTINCT n.name) AS diff;

Resources