Reputation: 830
I've a collection that has a self-referencing structure between Employees
and Managers
, linked through _id
and managerIDs
fields. Managers
are employees themselves:
{
"_id": "61b9f07300127afb99f8c1ea",
"title": "Developer",
"firstName": "Jack",
"lastName": "Strauss",
"managerIDs": [
"61cedf84800749316306c6da"
],
"deptID": "61b9f073267500832f5d94d0"
},
{
"_id": "61cedf84800749316306c6da",
"title": "Sr. Developer",
"firstName": "Richard",
"lastName": "Haris",
"managerIDs": null,
"deptID": "61b9f073267500832f5d94d0"
},
{
"_id": "61cedf17800749316306c6cf",
"title": "Manager App Development",
"firstName": "Arnold",
"lastName": "Cliff",
"deptID": "61b9f073267500832f5d94d0"
},
{
"_id": "61d4503e1223496ab8a5ae3c",
"title": "Developer",
"firstName": "Andrew",
"lastName": "Turner",
"managerIDs": [
"61cedf17800749316306c6cf",
"61cedf84800749316306c6da"
],
"deptID": "61b9f073267500832f5d94d0"
}
managerIDs
field can have either null
, undefined
,
one or more IDs.Developers
can't be managers, so they will not be included in the
list of managers. Also, developers are the only ones getting managed.id
will appear in
managerIDs
array for developers).I've to write a query that will list all managers with their name, title and a count of developers they manage. I've tried $lookup
in MongoDB aggregation without much luck.
How can I write a query for a self-referencing structure like this in MongoDB
or Mongoose
?
Upvotes: 0
Views: 118
Reputation: 11912
If I understand who can be a manager, here's one way you could do it.
db.employees.aggregate([
{ // managers won't have any managerIDs
"$match": {
"managerIDs.0": {"$exists": false}
}
},
{ // find managed Developers
"$graphLookup": {
"from": "employees",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "managerIDs",
"as": "managedDevs",
"maxDepth": 0
}
},
{ // format output
"$project": {
"_id": 0,
"title": 1,
"firstName": 1,
"lastName": 1,
"devCount": {"$size": "$managedDevs"}
}
}
])
Sample output:
[
{
"devCount": 2,
"firstName": "Richard",
"lastName": "Haris",
"title": "Sr. Developer"
},
{
"devCount": 1,
"firstName": "Arnold",
"lastName": "Cliff",
"title": "Manager App Development"
}
]
Try it on mongoplayground.net.
Mostly out of curiosity, I "inverted" the query by getting a histogram of the manager "_id"
s present in the "managerIDs"
arrays and then "$lookup"
the manager details.
N.B.: I'm a javascript noob so my "$accumulator"
could probably be improved.
db.employees.aggregate([
{// managers won't have any managerIDs
"$match": {
"managerIDs.0": {"$exists": true}
}
},
{ // get histogram of counts per managerID
"$group": {
"_id": null,
"managerIDs": {
"$accumulator": {
"init": "function() {return new Object()}",
"accumulate": "function(state, mgrArray) {mgrArray.forEach((elem) => {state[elem] = state[elem] + 1 || 1}); return state}",
"accumulateArgs": ["$managerIDs"],
"merge": "function(state1, state2) {Object.entries(state2).forEach((key, val) => {state1[key] = state1[key] + val || val}); return state1}",
"finalize": "function(state) {const obj = Object.assign({},state); return Object.entries(obj).map(([key, val]) => {return {'_id':key, 'count':val}})}",
"lang": "js"
}
}
}
},
{"$unwind": "$managerIDs"},
{
"$lookup": {
"from": "employees",
"localField": "managerIDs._id",
"foreignField": "_id",
"as": "manager"
}
},
{
"$set": {
"manager": {"$first": "$manager"}
}
},
{ // format output
"$project": {
"_id": 0,
"title": "$manager.title",
"firstName": "$manager.firstName",
"lastName": "$manager.lastName",
"devCount": "$managerIDs.count"
}
}
])
Try it on mongoplayground.net.
Upvotes: 1