Search code examples
neo4jcypher

generate subgraphs from special match rules


Say I have the following graph:

(a1:A) -> (b1:B) -> (c1:C) -> (d1:D)
   \                          /
     - -> (x1:X) - --> (y1:Y)

(a2:A) -> (b2:B) -> (c2:C) -> (d2:D)

(a3:A) -> (x3:X) -> (y3:Y) -> (d3:D)

The actual graph also contains other relationships between node label A and D. But I am only interested in these relationships between A and D. So I have to force some rules on the path. see query below

match p1=((:A)-->(:B)-->(:C)->(:D))
return p1

match p2=((:A)-->(:X)-->(:Y)->(:D))
return p2

This will return me four rows

a1-b1-c1-d1
a1-x1-y1-d1
a2-b2-c2-d2
a3-x3-y3-d3

But I would like to return an array of subgraphs and merge paths based on the common node attribute D.name. I.e., d1, d2, d3 are differentiated by their node attribute called "name". So the output I would like to have is

   // query logic
   // return subgraphs, each subgraph is a row. each subgraph contains the unique node and relationships  from A to D. If there are multiple paths to the same D, then merge these paths to the same subgraph

More concretely, the return of the above example becomes

   row1: nodes(a1, b1, c1, d1, x1, y1), relationships(a1, b1, c1, d1, x1, y1)
   row2: nodes(a2, b2, c2, d2), relationships(a2, b2, c2, d2)
   row3: nodes(a3, x3, y3, d3), relationships(a3, x3, y3, d3)

What would be the query?

UPDATE: Test graph

merge (a1:A{name: 'a1'})
merge (b1:B{name: 'b1'})
merge (c1:C{name: 'c1'})
merge (d1:D{name: 'd1'})
merge (x1:X{name: 'x1'})
merge (y1:Y{name: 'y1'})

merge (a2:A{name: 'a2'})
merge (b2:B{name: 'b2'})
merge (c2:C{name: 'c2'})
merge (d2:D{name: 'd2'})

merge (a3:A{name: 'a3'})
merge (x3:X{name: 'x3'})
merge (y3:Y{name: 'y3'})
merge (d3:D{name: 'd3'})

merge(a1)-[:TESTS]->(b1)
merge(b1)-[:TESTS]->(c1)
merge(c1)-[:TESTS]->(d1)
merge(a1)-[:TESTS]->(x1)
merge(x1)-[:TESTS]->(y1)
merge(y1)-[:TESTS]->(d1)

merge(a2)-[:TESTS]->(b2)
merge(b2)-[:TESTS]->(c2)
merge(c2)-[:TESTS]->(d2)

merge(a3)-[:TESTS]->(x3)
merge(x3)-[:TESTS]->(y3)
merge(y3)-[:TESTS]->(d3)

Solution

  • It seems that not just the :D nodes are different, but also the :A nodes. But, assuming that you want group by d.name, I guess this does it.

    MATCH p=((:A)-[*]->(d:D))    
    RETURN d.name AS dName,
           apoc.coll.toSet(
                apoc.coll.flatten(
                     COLLECT(nodes(p))
                )
           ) AS nodes,
    apoc.coll.toSet(
                apoc.coll.flatten(
                     COLLECT(relationships(p))
                )
           ) AS relationships
    

    In case you want to filter for specific paths, you can do something like:

    MATCH (a:A), (d:D)
    OPTIONAL MATCH p1=((a)-->(:B)-->(:C)-->(d))
    OPTIONAL MATCH p2=((a)-->(:X)-->(:Y)-->(d))
    
    WITH d.name AS dName,
           apoc.coll.toSet(
                apoc.coll.flatten(
                     COLLECT(COALESCE(nodes(p1),[]) + COALESCE(nodes(p2),[]))
                )
           ) AS nodes,
    apoc.coll.toSet(
                apoc.coll.flatten(
                     COLLECT(COALESCE(relationships(p1),[]) + COALESCE(relationships(p2),[]))
                )
           ) AS relationships
    WHERE nodes <> []
    
    RETURN nodes, relationships