Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive search on a collection in MongoDB

I have a list of documents in MongoDB with tree structure, where Model Tree Structures with Parent References pattern used. I want a single aggregation query which returns ancestor list(till the root), given the 'name' property.

Structure:

{
  '_id': '1',
  'name': 'A',
  'parent': '',
},
{
  '_id': '2',
  'name': 'B',
  'parent': 'A',
},
{
  '_id': '3',
  'name': 'C',
  'parent': 'B',
},
{
  '_id': '4',
  'name': 'D',
  'parent': 'C',
}

Aggregation result:(Given, name = 'D')

{
  '_id': '4',
  'name': 'D',
  'ancestors': [{name:'C'}, {name:'B'}, {name:'A'}]
}

Note: I can't change the document structure now. It will cause many problems. I saw many solutions which suggest to use Model Tree Structures with an Array of Ancestors. But I cannot use it now. Is there any way to achieve it with the above pattern using single aggregation query? Thank you

like image 221
RaR Avatar asked Nov 10 '16 11:11

RaR


1 Answers

Starting from MongoDB 3.4, we can do this with the Aggregation Framework.

The first and most important stage in our pipeline is the $graphLookup stage. $graphLookup allows us to recursively match on the "parent" and "name" field. As result, we get the ancestors of each "name".

The next stage in the pipeline is the $match stage where we simply select the "name" we are interested in.

The final stage is the $addFields or $project stage where we apply an expression to the "ancestors" array using the $map array operator.

Of course with the $reverseArray operator we reverse our array in order to get the expected result.

db.collection.aggregate(
    [ 
        { "$graphLookup": { 
            "from": "collection", 
            "startWith": "$parent", 
            "connectFromField": "parent", 
            "connectToField": "name", 
            "as": "ancestors"
        }}, 
        { "$match": { "name": "D" } }, 
        { "$addFields": { 
            "ancestors": { 
                "$reverseArray": { 
                    "$map": { 
                        "input": "$ancestors", 
                        "as": "t", 
                        "in": { "name": "$$t.name" }
                    } 
                } 
            }
        }}
    ]
)
like image 86
styvane Avatar answered Nov 16 '22 04:11

styvane