Neo4j: How to get additional nodes from nodes in a path? - neo4j

I have nodes in a chain, like this:
(n {height:100})
|
(n)
|
(n)
|
(n)
|
(n)
I can get these nodes with this cypher query:
MATCH chain=(start :n {height:100})-[:chain*4]->(end :n)
RETURN chain
However, each node in this chain also has a single node coming off it with a specific relationship, like this:
(n)-[:single]->(o)
|
(n)-[:single]->(o)
|
(n)-[:single]->(o)
|
(n)-[:single]->(o)
|
(n)-[:single]->(o)
I would like to return each (n), as well as the (o) coming off it.
Is it possible to do this in one cypher query?

Shouldn't be a problem, though this is easier if we don't match on the path, but get all nodes in the chain (and the single node off of each) instead.
MATCH (start :n {height:100})-[rels:chain*0..4]->(chainlink :n)-[:single]->(o)
RETURN chainlink, o
ORDER BY SIZE(rels)

Okay, it seems the use of WITH(chain) and UNWIND does the trick:
MATCH chain=(start :n {height:100})-[:chain*4]->(end :n)
WITH NODES(chain) AS nodes
UNWIND nodes as node
OPTIONAL MATCH (node)-[:single]->(o :o)
RETURN nodes, COLLECT(o) as os

Related

Neo4j query with logical AND on relationship types instead of OR

UPDATE: I've changed the graphic and example queries to make the request more clear. The basic idea is the same, but now I'm showing that there really are more than just two relationships. The idea is I want TWO of them to match, not necessarily ALL of them.
Given the following Neo4j graph:
Is it possible to specify a relationship in a query that requires that TWO specific relationships be there for a match, but not necessarily all, without simply stating each full matching path separately? I want a logical AND on the relationship types, just like we have a logical OR using the | character.
This is how you would use a logical OR with the | character:
// OR on MEMBER_OF and GRANT_GROUP_COMP
MATCH (p:Person {name:'John'})-[r:MEMBER_OF|GRANT_GROUP_COMP]->(t:Team {name:'Team 1'})
RETURN p,r,t
What I'm looking for is something like this, an AND with a & or simlar that REQUIRES that both relationships be present:
// AND type functionality in the relationship I'd like
MATCH (p:Person {name:'John'})-[r:MEMBER_OF&GRANT_GROUP_COMP]->(t:Team {name:'Team 1'})
RETURN p,r,t
Without having to resort to this - which works for me just fine:
// I'd like to avoid this
MATCH (p:Person {name:'John'})-[r:MEMBER_OF]->(t:Team {name:'Team 1'}),
(p)-[r2:GRANT_GROUP_COMP]->(t)
RETURN p,r,r2,t
Any insight would be appreciated, but based on responses so far, it simply doesn't exist.
What about this?
MATCH (D:Person {name:'Donald'})-[r1:WORKS_AT]->
(o:Office {code:'279'})<-[r2:SUPPORTS]-(D)
RETURN *
Inspired version of Dave
MATCH (D:Person {name:'Donald'})-[r:WORKS_AT|SUPPORTS]->(o:Office {code:'279'})
WITH D, o, collect(r) as rels,
collect(distinct type(r)) as tmp WHERE size(tmp) >= 2
return D, o, rels
Update:
MATCH (D:Person {name:'Donald'})
- [r: MEMBER_OF
| GRANT_INDIRECT_ALERTS
| GRANT_INDIRECT_COMP
| GRANT_GROUP_ALERTS
| GRANT_GROUP_COMP
] ->
(o:Office {code:'279'})
WITH D, o, collect(r) as rels,
collect(distinct type(r)) as tmp WHERE size(tmp) >= 2 AND size(tmp) <= 5
return D, o, rels
This query will return a result if John and Team 1 have MEMBER_OF AND GRANT_GROUP_COMP relationships between them.
(This is very similar to the second answer of #stdob--, but requires the size of types to be exactly 2.)
MATCH (p:Person {name: 'John'})-[r:MEMBER_OF|GRANT_GROUP_COMP]->(t:Team {name: 'Team 1'})
WITH p, t, COLLECT(r) AS rels, COLLECT(DISTINCT type(r)) AS types
WHERE SIZE(types) = 2
RETURN p, t, rels;
You could add the second relationship type in a WHERE clause. Something like this...
MATCH (p:Person {name:'John'})-[r:GRANT_GROUP_COMP]->(t:Team {name:'Team 1'})
WHERE (p)-[:MEMBER_OF]->(t)
RETURN *
Or you could make sure that the complete set is in the collection of relationship types. Something like this...
MATCH (p:Person {name:'John'})-[r]->(t:Team {name:'Team 1'})
with p,t,collect(type(r)) as r_types
where all(r in ['MEMBER_OF','GRANT_GROUP_COMP'] where r in r_types)
RETURN p, t, r_types

how to remove Neo4j nodes with duplicate properties?

In Neo4j 2.1.6, I have nodes that are non-unique in respect of a certain property, inputID.
Using Cypher, how do I remove all nodes that are duplicates in terms of a given property, leaving only uniques?
I have tried the following...
MATCH (n:Input)
WITH n.inputID, collect(n) AS nodes
WHERE size(nodes) > 1
FOREACH (n in tail(nodes) | DELETE n)
...but it results in...
Expression in WITH must be aliased (use AS) (line 2, column 6)
"WITH n.inputID, collect(n) AS nodes"
^
Thanks,
G
You're not aliasing that WITH variable. Change this:
WITH n.inputID, collect(n) AS nodes
To this:
WITH n.inputID AS inputID, collect(n) AS nodes
As you correctly found out, using tail on a collection will let you remove the duplicates, don't forget to remove relationships before the node (DETACH) and alias the field as FrobberOfBits mentioned:
MATCH (n:Input)
WITH n.inputID AS inputID, collect(n) AS nodes
WHERE size(nodes) > 1
FOREACH (n in tail(nodes) | DETACH DELETE n)

Match Only Full Paths in Neo4J with Cypher (not sub-paths)

If I have a graph like the following (where the nesting could go on for an arbitrary number of nodes):
(a)-[:KNOWS]->(b)-[:KNOWS]->(c)-[:KNOWS]->(d)-[:KNOWS]->(e)
| |
| (i)-[:KNOWS]->(j)
|
(f)-[:KNOWS]->(g)-[:KNOWS]->(h)-[:KNOWS]->(n)
|
(k)-[:KNOWS]->(l)-[:KNOWS]->(m)
How can I retrieve all of the full-length paths (in this case, from (a)-->(m), (a)-->(n) (a)-->(j) and (a)-->(e)? The query should also be able to return the nodes with no relationships of the given type.
So far I am just doing the following (I only want the id property):
MATCH path=(a)-[:KNOWS*]->(b)
RETURN collect(extract(n in nodes(path) | n.id)) as paths
I need the paths so that in the programming language (in this case clojure) I can create a nested map like this:
{"a" {"b" {"f" {"g" {"k" {"l" {"m" nil}}
"h" {"n" nil}}}
"c" {"d" {"e" nil}
"i" {"j" nil}}}}}
Is it possible to generate the map directly with the query?
Just had to do something similar, this worked on your example, finds all nodes which do not have outgoing [:KNOWS]:
match p=(a:Node {name:'a'})-[:KNOWS*]->(b:Node)
optional match (b)-[v:KNOWS]->()
with p,v
where v IS NULL
return collect(extract(n in nodes(p) | n.id)) as paths
Here is one query that will get you started. This query will return just the longest chain of nodes when there is a single chain without forks. It matches all of the paths like yours does but only returns the longest one by using limit to reduce the result.
MATCH p=(a:Node {name:'a'})-[:KNOWS*]->(:Node)
WITH length(p) AS size, p
ORDER BY size DESC
LIMIT 1
RETURN p AS Longest_Path
I think this gets the second part of your question where there are multiple paths. It looks for paths where the last node does not have an outbound :KNOWS relationship and where the starting node does not have an inbound :KNOWS relationship.
MATCH p=(a:Node {name:'a'})-[:KNOWS*]->(x:Node)
WHERE NOT x-[:KNOWS]->()
AND NOT ()-[:KNOWS]->(a)
WITH length(p) AS size, p
ORDER BY size DESC
RETURN reduce(node_ids = [], n IN nodes(p) | node_ids + [id(n)])

Neo4j Graph Traversal excluding nodes

I am trying to find the shortest path between two nodes but I need to exclude some nodes from the path. The cypher that I am trying is
START a=node(1), b=node(2), c=node(3,4)
MATCH p=a-[rel:RELATIONSHIP*]-b
WHERE NOT (c in nodes(p))
RETURN p
ORDER BY length(p) LIMIT 1
But this is giving me a path which includes one of the nodes in c.
Is there a way to do a traversal excluding some nodes?
Thanks
The MATCH ... WHERE part should be fine, but your START clause may not do what you expect. Do the following and consider the result
START a=node(1), b=node(2), c=node(3,4)
RETURN ID(a), ID(b), ID(c)
You get back
==> +-----------------------+
==> | ID(a) | ID(b) | ID(c) |
==> +-----------------------+
==> | 1 | 2 | 3 |
==> | 1 | 2 | 4 |
==> +-----------------------+
That means the rest of the query is executed twice, once excluding (3) from the path and once excluding (4). But that also means of course that it is run once not excluding each of them, which means you can indeed get results with those nodes present on the path.
If you want to exclude both of those nodes from the path, try collecting them and filtering with NONE or NOT ANY or similar. I think something like this should do it (can't test at the moment).
START a=node(1), b=node(2), c=node(3,4)
WITH a, b, collect (c) as cc
MATCH p=a-[rel:RELATIONSHIP*]-b
WHERE NONE (n IN nodes(p) WHERE n IN cc)
RETURN p
ORDER BY length(p) LIMIT 1

Cypher query to get node and subnodes like a single object with arrays

I have this json:
{
"name":"david", //node:Person
"TAKING":[ //link
{"name":"math"}, //node:Subject
{"name":"physics"} //node:Subject
],
"FRIEND":[ //link
{"name":"andres"}, //node:Person
{"name":"luis"} //node:Person
]
}
And I have this query to extract it from neo4j
start person=node(*) match person-[:FRIEND]->friend, person-[:TAKING]->subject where person.name="Andres" return person, collect(distinct friend), collect(distinct subject);
The result is this:
==> +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
==> | person | collect(distinct friend) | collect(distinct subject) |
==> +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
==> | Node[1]{name:"Andres",title:"Developer"} | [Node[2]{name:"David",title:"Developer"},Node[3]{name:"Luis",title:"Developer"}] | [Node[5]{name:"math"},Node[6]{name:"physics"}] |
==> +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
I think this part of the query can be better:
person-[:FRIEND]->friend, person-[:TAKING]->subject
The goal is to avoid the distinc clause in the return part:
collect(distinct friend), collect(distinct subject)
I rewrite it to:
subject<-[:TAKING]-person-[:FRIEND]->friend
but same result.
Is there a better way to make this query?, and Is there a way to build the original json with cypher?
Try the following query as demonstrated in http://console.neo4j.org/?id=mlwmlt to avoid the DISTINCT keyword:
START person=node(*)
WHERE HAS (person.name) AND person.name='A'
WITH person
MATCH (subject)<-[:TAKING]-(person)
WITH person, COLLECT(subject) AS subjects
MATCH (person)-[:FRIEND]->(friend)
RETURN person, subjects, COLLECT(friend)
But in general you should not use node(*). A good idea would be using an index of names instead.

Resources