I have this graph:
CREATE (a:Class {brickClass: 'Building',id:"building1"})
CREATE (b:Class {brickClass: 'Air Handler Unit', id:"ahu1"})
CREATE (c:Class {brickClass: 'Cooling System', id:"cools1"})
CREATE (d:Class {brickClass: 'Heat Exchanger', id:"heatex1"})
CREATE (e:Class {brickClass: 'Heat Exchanger', id:"heatex2"})
CREATE (f:Class {brickClass: 'Heat Exchanger', id:"heatex3"})
CREATE (g:Class {brickClass: 'Pump', id:"pump1"})
CREATE (h:Class {brickClass: 'Pump', id:"pump2"})
CREATE (i:Class {brickClass: 'Pump', id:"pump3"})
CREATE (a)-[r:has_equipment]->(b)
CREATE (a)-[s:has_equipment]->(c)
CREATE (b)-[t:has_equipment]->(d)
CREATE (b)-[u:has_equipment]->(e)
CREATE (c)-[w:has_equipment]->(f)
CREATE (d)-[v:has_equipment]->(g)
CREATE (e)-[x:has_equipment]->(h)
CREATE (f)-[y:has_equipment]->(i)
I would like to return all the pumps that is under a heat exchanger, which again is under a air handler unit, that is under a building. So the input is this path :
["Building","Air Handler Unit","Heat Exchanger","Pump"]
I have tried to code this and the result is this code:
WITH ["Building","Air Handler Unit","Heat Exchanger","Pump"] AS labels
UNWIND range(0, size(labels)-1) AS i
WITH labels[i] AS label1, labels[i+1] AS label2, i, labels
match (n:Class{brickClass:label1})-[*..]->(node2:Class{brickClass:label2})
WHERE i + 1 = size(labels) - 1
RETURN node2.id
This code does not return what I want. This code return all the pumps. But I would like it to return only pump1 and pump2. How can I change the code to do that? The labels list could have varying length and labels, so I can not do any hardcoding.
There is no such built-in capability in Neo4j. However with a bit of imagination using APOC you can achieve your result.
What we will do is we will generate what the Cypher query would look like if you could write it by hand, so based on your examples with your 4 items in the parameters the query would look like this :
MATCH path=(:`Class` {brickClass: "Building"})-[:has_equipment]->(:`Class` {brickClass: "Air Handler Unit"})-[:has_equipment]->(:`Class` {brickClass: "Heat Exchanger"})-[:has_equipment]->(:`Class` {brickClass: "Pump"}) RETURN path
To generate the query based on input elements, we will use some string functions in APOC, mainly apoc.text.join
WITH ["Building","Air Handler Unit","Heat Exchanger","Pump"] AS parts
// convert any item in list above into (:Class {brickClass: <value>})
WITH [x IN parts | apoc.text.format('(:`Class` {brickClass: "%s"})', [x])] AS parts
// start the query with `MATCH path=`,
// add the query parts from above joined by the relationship type
// and end with `RETURN path`
WITH 'MATCH path=' + apoc.text.join(parts, '-[:has_equipment]->') + ' RETURN path' AS query
RETURN query
This will return the following query string
MATCH path=(:`Class` {brickClass: "Building"})-[:has_equipment]->(:`Class` {brickClass: "Air Handler Unit"})-[:has_equipment]->(:`Class` {brickClass: "Heat Exchanger"})-[:has_equipment]->(:`Class` {brickClass: "Pump"}) RETURN path
Now we can use another APOC function, apoc.cypher.run
that allows us to execute a generated query and it returns a single value called .. value
WITH ["Building","Air Handler Unit","Heat Exchanger","Pump"] AS parts
WITH [x IN parts | apoc.text.format('(:`Class` {brickClass: "%s"})', [x])] AS parts
WITH 'MATCH path=' + apoc.text.join(parts, '-[:has_equipment]->') + ' RETURN path' AS query
CALL apoc.cypher.run(query, {})
YIELD value
RETURN value
And the result is what you expect
Now let's try the same for your example with 2 elements only
WITH ["Heat Exchanger", "Pump"] AS parts
WITH [x IN parts | apoc.text.format('(:`Class` {brickClass: "%s"})', [x])] AS parts
WITH 'MATCH path=' + apoc.text.join(parts, '-[:has_equipment]->') + ' RETURN path' AS query
CALL apoc.cypher.run(query, {})
YIELD value
RETURN value
And the result is there as well, so very dynamic