Cypher constraint on direction of successive relationship - neo4j

short version: I need to get a path that can contain different relationships in different directions. However, I have a constraint on my path where if it contains successive relationships of a particular type, then both relationship must be in the same direction.
long version:
I am using the query below to get a path between two nodes:
MATCH p=shortestPath((n:Class { code: '1' })-[r*]-(m:Class { code: '4'})) WHERE NONE(x IN NODES(p) WHERE 'Ontology' in labels(x)) return p
The query correctly returns me a shortest path between the two nodes. However I need to further constraint this query so that it returns only path where successive relationship of a particular type are in the same direction.
For example, suppose the relationship -a-> need to be in the same direction, it should not return (1)-a->(2)<-a-(3)-b->(4) but can return (1)-a->(6)-a->(3)-b->(7)<-c-(5)<-d-(6)-e->(4) or (3)-b->(7)<-c-(4) .
The above examples were just a simplification of my real data. In my real use case, I need to find a shortest path between a node with IRI
http://elite.polito.it/ontologies/dogont.owl#Actuator and another node with IRI http://elite.polito.it/ontologies/dogont.owl#StateValue. The query below is a specific query that encodes the path I need and it returns a path, that is the path exist. I need to make it more generic using shortestpath.
MATCH p=(n:Class {iri: 'http://elite.polito.it/ontologies/dogont.owl#Actuator'})-->(a:Class)<--(b:ObjectProperty{iri:'http://elite.polito.it/ontologies/dogont.owl#hasState'})-->(c:Class{iri:'http://elite.polito.it/ontologies/dogont.owl#State'})<--(d:Class{iri:'http://elite.polito.it/ontologies/dogont.owl#hasStateValue'})-->(e:Class{iri:'http://elite.polito.it/ontologies/dogont.owl#StateValue'}) return p
Is this possible with cypher ?

This query should work if you want to capture paths that are consistent in either direction (but it has to invoke shortestPath() twice):
MATCH (n:Class {code: '1'}), (m:Class {iri: '4'})
OPTIONAL MATCH p1=shortestPath((n)-[*]->(m))
WHERE NONE(x IN NODES(p1) WHERE 'Ontology' in labels(x))
OPTIONAL MATCH p2=shortestPath((n)<-[*]-(m))
WHERE NONE(y IN NODES(p2) WHERE 'Ontology' in labels(y))
RETURN p1, p2
p1 and/or p2 will be null if there is no consistently rightward or leftward path, respectively.
However, if you know that you want a specific direction (say, rightward), then this should work:
MATCH p=shortestPath((:Class {code: '1'})-[*]->(:Class {iri: '4'}))
WHERE NONE(x IN NODES(p) WHERE 'Ontology' in labels(x))
RETURN p

Related

How to avoid specific paths in allShortestPath function?

The graph is supposed to represent a system similar to github, with commits (commit1, commit2, commit3 and commit4), documents (d1, d2) and changes on those documents (green nodes).
I am trying to use CYPHER to get all the documents values at a specific commit. In other words, I am trying to find the shortest path between the specific commit and each of the documents on my graph, but avoiding some paths.
Imagine if I am on commit4, d1 should be equal to foo2 and d2 should be equal to spain. This could be solved with the following CYPHER query:
MATCH (c:Commit {id: 'commit4'})-[:FOLLOWS|CHANGED*]->(:Value)<-[:EQUALS]-(d:Document), p = allShortestPaths((c)-[*]-(d))
RETURN p
This would give the following response:
Now, imagine that I want to be get the values on commit3. The request should not return any changes from the commit4. However, if I use the allShortestPaths function the way I do, it will go through commit4 since it is actually the shortest path to d1 and return the exact same response than if my starting node was commit4.
MATCH (c:Commit {id: 'commit3'})-[:FOLLOWS|CHANGED*]->(:Value)<-[:EQUALS]-(d:Document), p = allShortestPaths((c)-[*]-(d))
RETURN p
How could I avoid the allShortestPath function to go through a [:FOLLOWS]->(c) relationship and solve my problem?
From what you explained, I understand that you don't want to traverse the FOLLOWS edge in the opposite direction of the edge. To do so you can use cypher projection in algo.shortestPath:
MATCH (start:Commit {name:'commit4'})
MATCH (end:Document)
CALL algo.shortestPath.stream(start, end, null,{
nodeQuery:'MATCH (n) RETURN id(n) AS id',
relationshipQuery:'MATCH (n)-[r:FOLLOWS|CHANGED]->(p) RETURN id(n) AS source, id(p) AS target UNION MATCH (n)-[r:EQUALS]-(p) RETURN id(n) AS source, id(p) AS target',
graph:'cypher'})
YIELD nodeId, cost
WITH end as document, algo.asNode(nodeId) AS value WHERE "Value" in labels(value)
return document, value
Replace "commit4" with any other commit name.

neo4j shortest path works only when no nodes are specified

I am trying to obtain the node and edge ids for the shortest path between two nodes in my neo4j graph database.
If I do not specify which nodes I want, the code runs somehow and returns a path:
import py2neo
graph.run("MATCH (start:Point)-[:SOURCE_POINT]->(r:Road)-[:TARGET_POINT]->(end:Point) \
CALL apoc.algo.dijkstraWithDefaultWeight(start, end, 'Road', 'length', 10.0) \
YIELD path as path, weight as weight \
UNWIND nodes(path) as n \
RETURN DISTINCT { id : id(n), labels : labels(n), data: n} as node").to_table()
But when I run the same code and specify which nodes I want, it returns empty:
graph.run("MATCH (start:Point {id: '4984061949'})-[:SOURCE_POINT]->(r:Road)-[:TARGET_POINT]->(end:Point {id: '4984061963'}) \
...
If I simply try to match those node id's, it returns them ok - so I know they are in the db.
I'm thinking it could be because my 'cost' is a string. But I'm not sure how to cast it to float before it goes through the dijkstraWithDefaultWeight function.
You seem to have a couple of issues.
1. MATCH clause is too restrictive
The following MATCH clause would only succeed if there was a path between the specified start and end nodes consisting of just one Road:
MATCH (start:Point {id: '4984061949'})-[:SOURCE_POINT]->(r:Road)-[:TARGET_POINT]->(end:Point {id: '4984061963'})
If that MATCH clause fails, then your query would return nothing.
The following MATCH clause would succeed if there was a path between any pair of Point nodes consisting of one Road:
MATCH (start:Point)-[:SOURCE_POINT]->(r:Road)-[:TARGET_POINT]->(end:Point)
If that MATCH clause succeeds, then, of course, the Dijkstra procedure will also succeed.
Instead of either of the above, you should probably just use MATCH to get the two endpoints and let the Dijkstra algorithm do the job of finding the path:
MATCH (start:Point {id: '4984061949'}), (end:Point {id: '4984061963'})
2. Wrong procedure argument(s)
The third argument passed to apoc.algo.dijkstraWithDefaultWeight is supposed to specify relationship types and directions, not node labels. Also, the last 2 arguments are supposed to be a relationship property and default relationship property value, respectively.

How to write cypher statement to combine nodes when an OPTIONAL MATCH is null?

Background
Hi all, I am currently trying to write a cypher statement that allows me to find a set of paths on a map from a starting point. I want my search result to always return connecting streets within 5 nodes. Optionally, if there's a nearby hospital, I would like my search pattern to also indicate nearby hospitals.
Main Problem
Because there isn't always a nearby hospital to the current street, sometimes my optional match search pattern comes back as null. Here's the current cypher statement I'm using:
MATCH path=(a:Street {id: 123})-[:CONNECTED_TO*..5]-(b:Street)
OPTIONAL MATCH optionalPath=(b)-[:CONNECTED_TO]->(hospital:Hospital)
WHERE ALL (x IN nodes(path) WHERE (x:Street))
WITH DISTINCT nodes(path) + nodes(optionalPath) as n
UNWIND n as nodes
RETURN DISTINCT nodes;
However, this syntax only works if optionalPath contains nodes. If it doesn't, the statement nodes(path) + nodes(optionalPath) is an operation adding null and I get no records. This is true even the nodes(path) term does contain nodes.
What's the best way to get around this problem?
You can use COALESCE to replace a NULL with some other value. For example:
MATCH path=(:Street {id: 123})-[:CONNECTED_TO*..5]-(b:Street)
WHERE ALL (x IN nodes(path) WHERE x:Street)
OPTIONAL MATCH optionalPath=(b)-[:CONNECTED_TO]->(hospital:Hospital)
WITH nodes(path) + COALESCE(nodes(optionalPath), []) as n
UNWIND n as nodes
RETURN DISTINCT nodes;
I have also made a few other improvements:
The WHERE clause was moved up right after the first MATCH. This eliminates the unwanted path values immediately. Your original query would get all path values (even unwanted ones) and always the perform the second MATCH query, and only eliminate unwanted paths afterwards. (But, it is actually not clear if you even need the WHERE clause at all; for example, if the CONNECTED_TO relationship is only used between Street nodes.)
The DISTINCT in your WITH clause would have prevented duplicate n collections, but the collections internally could have had duplicate paths. This was probably not what you wanted.
It seems you don't really want the path, just all the street nodes within 5 steps, plus any connected hospitals. So I would simplify your query to just that, and then condense the 3 columns down to 1.
MATCH (a:Street {id: 123})-[:CONNECTED_TO*..5]-(b:Street)
OPTIONAL MATCH (b)-[:CONNECTED_TO]->(hospital:Hospital)
WITH collect(a) + collect(b) + collect(hospital) as n
UNWIND n as nodez
RETURN DISTINCT nodez;
If Streets can be indirectly connected (hospital in between), Than I'd adjust like this
MATCH (a:Street {id: 123})-[:CONNECTED_TO]-(b:Street)
WITH a as nodez, b as a
MATCH (a)-[:CONNECTED_TO]-(b:Street)
WITH nodez+collect(b) as nodez, b as a
MATCH (a)-[:CONNECTED_TO]-(b:Street)
WITH nodez+collect(b) as nodez, b as a
MATCH (a)-[:CONNECTED_TO]-(b:Street)
WITH nodez+collect(b) as nodez, b as a
MATCH (a)-[:CONNECTED_TO]-(b:Street)
WITH nodez+collect(b) as nodez, b as a
OPTIONAL MATCH (b)-[:CONNECTED_TO]->(hospital:Hospital)
WITH nodez + collect(hospital) as n
UNWIND n as nodez
RETURN DISTINCT nodez;
It's a bit more verbose, but just says exactly what you want (and also adds the start node to the hospital check list)

How can branches get pruned in a query?

suppose I have a tree where certain nodes have a relationship of a give type, how would I return all nodes in the tree except for those with the given relationship and its descendants.
I've gotten half way there with something like this (the tree is built on has links):
match (root: {Name: 'Root'})-[:has*]->(n) where not (n)-[:Exemption]-() return n
but, of course, this excludes nodes that have a relationship of type Exemption but not its descendants, so the descendants show up as unconnected nodes
how do I structure the query?
This query should work:
MATCH p=({Name: 'Root'})-[:has*]->(n)
WHERE NONE(x IN NODES(p) WHERE (x)-[:Exemption]-())
RETURN n;
It filters out any :has relationship paths with a node that (also) has an :Exemption relationship.
I would probably split the path up into two parts:
MATCH p=(:Label {Name: 'Root'})-[:has*]->(n) WHERE NOT EXISTS ((n)-[:Exemption]->())
MATCH p2 = (n)-[:has*]->(m) WHERE NOT (m)-[:has]->()
RETURN p,p2;
after some experimentation, this worked:
match (root: {Name: 'Root'})-[:has*]->(n)
match (m)-[:has*]->(n)
where not (m)-[:Exemption]-()
return n
which essentially says: find a path, for any node on that path, traverse back up to make sure that none of the ancestors show an exception. so what I'm negating here is the subpath created with the second match

Cypher Optional Match

I have a graph in that contains two types of nodes (objects and pieces) and two types of links (similarTo and contains). Some pieces are made of the pieces.
I would like to extract the path to each piece starting from a set of objects.
MATCH (o:Object)
WITH o
OPTIONAL MATCH path = (p:Piece) <-[:contains*]- (o) -[:similarTo]- (:Object)
RETURN path
The above query only returns part of the pieces. In the returned graph, some objects do not directly connect to any pieces, the latter are not returned, although they actually do!
I can change the query to:
MATCH (o:Object) -[:contains*]-> (p:Piece)
OPTIONAL MATCH (o) –[:similarTo]- (:Object)
However, I did not manage to return the whole path for that query, which I need to return collection of nodes and links with:
WITH rels(path) as relations , nodes(path) as nodes
UNWIND relations as r unwind nodes as n
RETURN {nodes: collect(distinct n), links: collect(distinct {source: id(startNode(r)), target: id(endNode(r))})}
I'd be grateful to any recommendation.
Would something like this do the trick ?
I created a small graph representing objects and pieces here : http://console.neo4j.org/r/abztz4
Execute distinct queries with UNION ALL
Here you'll combine the two use cases in one set of paths :
MATCH (o:Object)
WITH o
OPTIONAL MATCH p=(o)-[:CONTAINS]->(piece)
RETURN p
UNION ALL
MATCH (o:Object)
WITH o
OPTIONAL MATCH p=(o)-[:SIMILAR_TO]-()-[:CONTAINS]->(piece)
RETURN p

Resources