Saturday, September 29, 2018

Hierarchical queries with Mongo using $graphLookup

Leave a Comment

I have an employee collection with half a million records. Each record will have the following details.

The mongo document is as follows.

{   "_id": "234463456453643563456",   "name": "Mike",   "empId": "10",   "managerId": "8",   "projects" : [ "123", "456", "789"] } 
  1. When i give any empId, it should return the complete hierarchies from that manager to the bottom level along with the following filter.

a. filter on location
b. filter on projects

The result should be like,

      10     ->>> Manager       /\      /  \     8    6  ---->> 8 & 6 reporting to manager 10     /\    /\    /  \  /  \   4    5 2   1  ---->> 4 & 5 reporting to manager 8 ... 

Any help will be appreciated for getting the hierarchical results with level?

I am not able to get the result as expected.

Sample Data :-

db.getCollection("employees").insert({"_id":"10","empId": "10","name":"Employee10","managerId":"15" });  db.getCollection("employees").insert({"_id":"8","empId": "8","name":"Employee8","managerId":"10" });  db.getCollection("employees").insert({"_id":"6","empId": "6","name":"Employee6","managerId":"10" });  db.getCollection("employees").insert({"_id":"4","empId": "4","name":"Employee4","managerId":"8" });  db.getCollection("employees").insert({"_id":"5","empId": "5","name":"Employee5","managerId":"8" });  db.getCollection("employees").insert({"_id":"2","empId": "2","name":"Employee2","managerId":"6" });  db.getCollection("employees").insert({"_id":"1","empId": "1","name":"Employee1","managerId":"6" }); 

Query :-

db.getCollection('employees').aggregate([ {     $match: {         empId : "10"     } }, {    $graphLookup: {       from: "employees",       startWith: "$empId",       connectFromField: "empId",       connectToField: "managerId",       as: "reportees",       maxDepth: 4,       depthField: "level"    } }, {    $project: {      "empId":1,      "managerId":1,      "reportees.empId":1,      "reportees.name":1,      "reportees.managerId":1,      "reportees.level":1    } } ]); 

Actual Result :-

{      "_id" : "10",      "empId" : "10",      "managerId" : "15",      "reportees" : [         {             "empId" : "1",              "name" : "Employee1",              "managerId" : "6",              "level" : NumberLong(1)         },          {             "empId" : "4",              "name" : "Employee4",              "managerId" : "8",              "level" : NumberLong(1)         },          {             "empId" : "2",              "name" : "Employee2",              "managerId" : "6",              "level" : NumberLong(1)         },          {             "empId" : "5",              "name" : "Employee5",              "managerId" : "8",              "level" : NumberLong(1)         },          {             "empId" : "6",              "name" : "Employee6",              "managerId" : "10",              "level" : NumberLong(0)         },          {             "empId" : "8",              "name" : "Employee8",              "managerId" : "10",              "level" : NumberLong(0)         }     ] } 

Expected Result :-

{      "_id" : "10",      "empId" : "10",      "managerId" : "15",      "reportees" : [         {             "empId" : "6",              "name" : "Employee6",              "managerId" : "10",              "level" : NumberLong(0),             "reportees" : [               {                "empId" : "1",                 "name" : "Employee1",                 "managerId" : "6",                 "level" : NumberLong(1)               },                {                "empId" : "2",                 "name" : "Employee2",                 "managerId" : "6",                 "level" : NumberLong(1)               }             ]         },          {             "empId" : "8",              "name" : "Employee8",              "managerId" : "10",              "level" : NumberLong(0),             "reportees" : [               {                 "empId" : "5",                  "name" : "Employee5",                  "managerId" : "8",                  "level" : NumberLong(1)               },               {                 "empId" : "4",                  "name" : "Employee4",                  "managerId" : "8",                  "level" : NumberLong(1)               }              ]         }     ] } 

Questions :-

  1. Is it possible to get the expected output with $graphLookup?
  2. Also, Is it possible to get the count at the top level and also for each sub level?
  3. How to apply projection at all level?
  4. How to apply filter on top of this?

Thanks

2 Answers

Answers 1

The official documentation on $graphLookup may provide help more or less.

https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/

Just a kind remind.

Answers 2

That's precicsely what you would $graphLookup for (the traversal bit at least). For the filtering part you could simply use $filter or $match depending on how exactly you want to filter.

Have a look at the results of this query:

db.employees.aggregate({     $graphLookup: {       from: "pets",       startWith: "$managerIds",       connectFromField: "managerId",       connectToField: "number",       as: "managers",     } }) 

UPDATE:

In order to get the hierarchical structure that you'd like to get you could do the following. However, I wouldn't call this a pretty solution since it requires you statically define the number of levels you want to go down and also to repeat sections but it does the job for your example. Not sure, if/how easily this can be extended to more levels, either. Personally, I think a client side loop solution would be more suitable for this kind of job:

db.employees.aggregate([ {     $match: {         empId : "10"     } }, // level 0 {    $graphLookup: {       from: "employees",       startWith: "$empId",       connectFromField: "empId",       connectToField: "managerId",       as: "reportees",       maxDepth: 0    } }, {     $unwind: "$reportees" // flatten }, {     $addFields: {         "reportees.level": 0 // add level field     } }, // level 1 {    $graphLookup: {       from: "employees",       startWith: "$reportees.empId",       connectFromField: "reportees.empId",       connectToField: "managerId",       as: "reportees.reportees",       maxDepth: 0    } }, {     $group: { // group previously flattened documents back together         _id: "$_id",         empId: { $first: "$empId" },         name: { $first: "$name" },         managerId: { $first: "$managerId" },         reportees: { $push: "$reportees" },     } }, {     $addFields: {         "reportees.reportees.level": 1 // add level field     } } ]) 

UPDATE 2:

The following query gets you to where you want to be from an output structure point of view (I omitted the level field but it should be easy to add). It is, however, not particularly pretty and, again, requires you to define a maximum organisational depth upfront.

db.employees.aggregate([ {     $match: {         empId : "10"     } }, {    $graphLookup: { // get the relevant documents out of our universe of employees       from: "employees",       startWith: "$empId",       connectFromField: "empId",       connectToField: "managerId",       as: "reportees"    } }, {     $project: { // add the employee we are interested in into the array of employees we're looking at         _id: 0,         reportees: { $concatArrays: [ "$reportees", [ { _id: "$_id", empId: "$empId", name: "$name", managerId: "$managerId" } ] ] }     } }, {     $project: {         reportees: {             $let: {                 vars: {                     managers: {                         $filter: { // remove employees with no reportess so keep managers only                             input: {                                 $map: {                                     input: "$reportees",                                     as: "this",                                     in: {                                         $mergeObjects: [                                             "$$this",                                             {                                                 reportees: {                                                     $filter: { // extract reportees from list of employees                                                         input: "$reportees",                                                         as: "that",                                                         cond: {                                                             $eq: [ "$$this._id", "$$that.managerId" ]                                                         }                                                     }                                                 }                                             }                                         ]                                     }                                 }                             },                             as: "this",                             cond: { $ne: [ "$$this.reportees", [] ] }                         }                     }                 },                 in: {                     $cond: [ // this is to break the processing once we have reached a top level manager                         { $eq: [ "$$managers", [] ] },                         "$reportees",                         "$$managers"                     ]                 }             }         }     } }, // second level: exactly identical to the previous stage // third level: exactly identical to the previous stage // basically, from here onwards you would need to repeat an exact copy of the previous stage to go one level deeper ]); 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment