mongodbaggregation-frameworkgraphlookup

Get full tree from parent in mongodb


I use a MongoDB database with a collection, using a tree parent-children References.

Each document in the collection includes a property: parentId.The parentId property is an (ObjectId) reference to the parent document and It can be null.

Sample:

[{
  "_id": {
    "$oid": "64d9f2bb7a587e250e3acb45"
  },
  "isActive": true,
  "name": "Test1"
}, {
  "_id": {
    "$oid": "64dc481d37a38cb58de04ffb"
  },
  "isActive": true,
  "name": "Test2"
}, {
  "_id": {
    "$oid": "64d9dd9f7a587e250e3ac95b"
  },
  "isActive": true,
  "name": "Test1-1",
  "parentId": {
    "$oid": "64d9f2bb7a587e250e3acb45"
  }
}, {
  "_id": {
    "$oid": "64dca02f0ff1ddfc4e40a874"
  },
  "isActive": true,
  "name": "Test1-1-1",
  "parentId": {
    "$oid": "64d9dd9f7a587e250e3ac95b"
  }
}]

I used $graphLookup but It only fetch all childrens in one list field. That I need it to populate a recursive tree structure from parent to children such as:


{
  "_id": {
    "$oid": "64d9f2bb7a587e250e3acb45"
  },
  "isActive": true,
  "name": "Test1",
  "childrents": [
    {
      "_id": {
        "$oid": "64d9dd9f7a587e250e3ac95b"
      },
      "name": "Test1-1",
      "isActive": true,
      "childrents": [
        {
            "_id": {
                "$oid": "64dca02f0ff1ddfc4e40a874"
             },
            "isActive": true,
            "name": "Test1-1-1",
            "childrents": []
        }
      ]
    }
  ]
}, {
  "_id": {
    "$oid": "64dc481d37a38cb58de04ffb"
  },
  "isActive": true,
  "name": "Test2",
  "childrents": []
}

Thank you so much for your help, I really appreciate it!


Solution

  • After researching I found a solution below https://mongoplayground.net/p/Fp5LlivVzlX. Anyone with a better solution please let me know. Thanks

    [{
     $graphLookup: {
      from: 'collection',
      startWith: '$_id',
      connectFromField: '_id',
      connectToField: 'parentId',
      as: 'childrens',
      restrictSearchWithMatch: {
       isActive: true
      },
      depthField: 'level'
     }
    }, {
     $unwind: {
      path: '$childrens',
      preserveNullAndEmptyArrays: true
     }
    }, {
     $sort: {
      'childrens.level': -1
     }
    }, {
     $group: {
      _id: '$_id',
      parentId: {
       $first: '$parentId'
      },
      isActive: {
       $first: '$isActive'
      },
      name: {
       $first: '$name'
      },
      childrens: {
       $push: '$childrens'
      }
     }
    }, {
     $addFields: {
      childrens: {
       $reduce: {
        input: '$childrens',
        initialValue: {
         level: -1,
         presentChild: [],
         prevChild: []
        },
        'in': {
         $let: {
          vars: {
           prev: {
            $cond: [
             {
              $eq: [
               '$$value.level',
               '$$this.level'
              ]
             },
             '$$value.prevChild',
             '$$value.presentChild'
            ]
           },
           current: {
            $cond: [
             {
              $eq: [
               '$$value.level',
               '$$this.level'
              ]
             },
             '$$value.presentChild',
             []
            ]
           }
          },
          'in': {
           level: '$$this.level',
           prevChild: '$$prev',
           presentChild: {
            $concatArrays: [
             '$$current',
             [
              {
               $mergeObjects: [
                '$$this',
                {
                 childrens: {
                  $filter: {
                   input: '$$prev',
                   as: 'e',
                   cond: {
                    $eq: [
                     '$$e.parentId',
                     '$$this._id'
                    ]
                   }
                  }
                 }
                }
               ]
              }
             ]
            ]
           }
          }
         }
        }
       }
      }
     }
    }, {
     $addFields: {
      childrens: '$childrens.presentChild'
     }
    }]