Search code examples
neo4jcypher

Get multiple level of one to many relationship results


I have a graph that looks like this

A1  A2  A3  A4  A5
  \  /    \  |  /
   S1        S2
      \    /
        E1

There can be many E nodes. But the essence from the above is: It is a one to many mapping between E and S nodes It is a one to many mapping between S and A nodes The same S1 can also point to another E node, but I want to extract the following relationship:

For each E node, get all the S nodes and for each S node we get, get all the A nodes.

I know for just E and S, I can do:

match (e:E)<--(s:S)
return e, collect(distinct s)

But I am not sure how to do this with two level of such mapping


Solution

  • Given the following stub data to represent your graph

    CREATE (e1:E {id: 'e1'})
    CREATE (e2:E {id: 'e2'})
    CREATE (s1:S {id: 's1'})
    CREATE (s2:S {id: 's2'})
    CREATE (a1:A {id: 'a1'})
    CREATE (a2:A {id: 'a2'})
    CREATE (a3:A {id: 'a3'})
    CREATE (a4:A {id: 'a4'})
    CREATE (a5:A {id: 'a5'})
    CREATE (e1)-[:TO]->(s1)
    CREATE (e1)-[:TO]->(s2)
    CREATE (s1)-[:TO]->(a1)
    CREATE (s1)-[:TO]->(a2)
    CREATE (s2)-[:TO]->(a3)
    CREATE (s2)-[:TO]->(a4)
    CREATE (s2)-[:TO]->(a5)
    CREATE (e2)-[:TO]->(s2)
    

    enter image description here

    You can retrieve paths from E to A simply by aliasing the full pattern

    MATCH path=(e:E)-->(:S)-->(:A)
    RETURN path
    

    This will give you a full path, note that a path is a sequenced list of relationships having a start and end node

    Graph result

    enter image description here

    Tabular result

    ╒═══════════════════════════════════════════════════════╕
    │"path"                                                 │
    ╞═══════════════════════════════════════════════════════╡
    │[{"id":"e1"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a4"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e1"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a5"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e1"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a3"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e1"},{},{"id":"s1"},{"id":"s1"},{},{"id":"a1"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e1"},{},{"id":"s1"},{"id":"s1"},{},{"id":"a2"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e2"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a4"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e2"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a5"}]│
    ├───────────────────────────────────────────────────────┤
    │[{"id":"e2"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a3"}]│
    └───────────────────────────────────────────────────────┘
    

    To maybe make it more clear, let's limit the result to only one path

    MATCH path=(e:E)-->(:S)-->(:A)
    RETURN path
    LIMIT 1
    

    enter image description here

    Tabular result

    ╒═══════════════════════════════════════════════════════╕
    │"path"                                                 │
    ╞═══════════════════════════════════════════════════════╡
    │[{"id":"e1"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a4"}]│
    └───────────────────────────────────────────────────────┘
    

    You can now collect paths per E node

    MATCH path=(e:E)-->(:S)-->(:A)
    RETURN e, collect(path) AS paths
    

    The graph result would be similar since it returns all nodes and rels, but the tabular result shows now the aggregation

    ╒═══════════╤══════════════════════════════════════════════════════════════════════╕
    │"e"        │"paths"                                                               │
    ╞═══════════╪══════════════════════════════════════════════════════════════════════╡
    │{"id":"e1"}│[[{"id":"e1"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a4"}],[{"id":"e1"},│
    │           │{},{"id":"s2"},{"id":"s2"},{},{"id":"a5"}],[{"id":"e1"},{},{"id":"s2"}│
    │           │,{"id":"s2"},{},{"id":"a3"}],[{"id":"e1"},{},{"id":"s1"},{"id":"s1"},{│
    │           │},{"id":"a1"}],[{"id":"e1"},{},{"id":"s1"},{"id":"s1"},{},{"id":"a2"}]│
    │           │]                                                                     │
    ├───────────┼──────────────────────────────────────────────────────────────────────┤
    │{"id":"e2"}│[[{"id":"e2"},{},{"id":"s2"},{"id":"s2"},{},{"id":"a4"}],[{"id":"e2"},│
    │           │{},{"id":"s2"},{"id":"s2"},{},{"id":"a5"}],[{"id":"e2"},{},{"id":"s2"}│
    │           │,{"id":"s2"},{},{"id":"a3"}]]                                         │
    └───────────┴──────────────────────────────────────────────────────────────────────┘
    

    So far we returned full paths. You can extract nodes only from them using the nodes() function

    MATCH path=(e:E)-->(:S)-->(:A)
    RETURN nodes(path)
    
    ╒═════════════════════════════════════╕
    │"nodes(path)"                        │
    ╞═════════════════════════════════════╡
    │[{"id":"e1"},{"id":"s2"},{"id":"a4"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e1"},{"id":"s2"},{"id":"a5"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e1"},{"id":"s2"},{"id":"a3"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e1"},{"id":"s1"},{"id":"a1"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e1"},{"id":"s1"},{"id":"a2"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e2"},{"id":"s2"},{"id":"a4"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e2"},{"id":"s2"},{"id":"a5"}]│
    ├─────────────────────────────────────┤
    │[{"id":"e2"},{"id":"s2"},{"id":"a3"}]│
    └─────────────────────────────────────┘
    

    And now, if you want to return a json tree like structure, you can use map projections

    MATCH (e:E)
    RETURN 
    e {.*, s: [(e)-->(s:S) | s{.*, a: [(s)-->(a:A) | a{.*}]}]}
    
    ╒══════════════════════════════════════════════════════════════════════╕
    │"e"                                                                   │
    ╞══════════════════════════════════════════════════════════════════════╡
    │{"s":[{"a":[{"id":"a4"},{"id":"a5"},{"id":"a3"}],"id":"s2"},{"a":[{"id│
    │":"a1"},{"id":"a2"}],"id":"s1"}],"id":"e1"}                           │
    ├──────────────────────────────────────────────────────────────────────┤
    │{"s":[{"a":[{"id":"a4"},{"id":"a5"},{"id":"a3"}],"id":"s2"}],"id":"e2"│
    │}                                                                     │
    └──────────────────────────────────────────────────────────────────────┘
    

    Let's format the first result a bit

    {
        "s": [
            {
                "a": [
                    {
                        "id": "a4"
                    },
                    {
                        "id": "a5"
                    },
                    {
                        "id": "a3"
                    }
                ],
                "id": "s2"
            },
            {
                "a": [
                    {
                        "id": "a1"
                    },
                    {
                        "id": "a2"
                    }
                ],
                "id": "s1"
            }
        ],
        "id": "e1"
    }
    

    This sounds a bit cryptic but as soon as you understand how it works it is quite powerful, I suggest reading a bit more about it here :