How to iterate list in mongodb $lookup and pipeline - mongodb

I have two collections i.e parent and chilednodes.
{
"_id" : "5e6cd8c1996ddf1c28e14505",
"parentList" : [
{
"_id" : "5e6c70e8996ddf1c28e14504",
"startDate" : "2020-02-25T14:01:58.697Z",
"active_id" : "child_vesrion_1",
"child_id" : "5e5e2cd4e972a95b6c32b5bf30"
},
{
"_id" : "5e6c70e8996ddf1c28e14506",
"startDate" : "2020-02-25T14:01:58.697Z",
"active_id" : "child_vesrion_1",
"child_id" : "5e5e2cd4e972a95b6c32b5bf31"
}
]
}
And childnodes are;
{
"_id" : "5e5e2cd4e972a95b6c32b5bf31",
"startDate" : "2020-03-25T14:01:58.697Z",
"endDate" : null,
"child_vesrion_1" : {
"childName" : "test3",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "test3 text",
"type" : "test3 type"
},
"child_vesrion_2" : {
"childName" : "Test4",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "test4 text",
"type" : "test4 type"
},
"active" : "child_vesrion_1"
},
{
"_id" : "5e5e2cd4e972a95b6c32b5bf30",
"startDate" : "2020-02-25T14:01:58.697Z",
"endDate" : null,
"child_vesrion_1" : {
"childName" : "test1",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "test1 text",
"type" : "test1 type"
},
"child_vesrion_2" : {
"childName" : "test2",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "test2 text",
"type" : "test2 type"
},
"active" : "child_vesrion_1"
}
Here is my query;
db.parent.aggregate([
{ $match: { "_id": "5e6cd8c1996ddf1c28e14505" } },
{
$lookup: {
from: "childnodes",
let: { "child_id": "$parentList.child_id", "activeid": "$parentList.active_id" },
pipeline: [
{ $match: { "$expr": { $eq: ["$_id", "$$child_id"] } } },
{
$project: {
"child_id": "$_id",
"start_date": "$startDate",
"current_version_Key": "$active",
"active_child_name": {
"$reduce": {
"input": { "$objectToArray": "$$ROOT" },
"initialValue": "",
"in": {
"$cond": [{ "$eq": ["$$this.k", "$$activeid"] },
"$$this.v.childName",
"$$value"
]
}
}
},
"text": {
"$reduce": {
"input": { "$objectToArray": "$$ROOT" },
"initialValue": "",
"in": {
"$cond": [{ "$eq": ["$$this.k", "$$activeid"] },
"$$this.v.text",
"$$value"
]
}
}
},
"type": {
"$reduce": {
"input": { "$objectToArray": "$$ROOT" },
"initialValue": "",
"in": {
"$cond": [{ "$eq": ["$$this.k", "$$activeid"] },
"$$this.v.type",
"$$value"
]
}
}
}
}
}
],
as: "finalList",
},
},
{
$project: {
parentList: 0,
},
},
]);
I am expecting results like;
{
"_id": "5e6cd8c1996ddf1c28e14505",
"finalList": [
{
"child_id": "5e5e2cd4e972a95b6c32b5bf30",
"start_date": "2020-02-25T14:01:58.697Z",
"current_version_Key": "child_vesrion_1",
"active_child_name": "test1",
"text": "test1 text",
"type": "test1 type",
},
{
"child_id": "5e5e2cd4e972a95b6c32b5bf31",
"start_date": "2020-02-25T14:01:58.697Z",
"current_version_Key": "child_vesrion_1",
"active_child_name": "test3",
"text": "test3 text",
"type": "test3 type",
}
]
}
But i am not getting anything in finalList. It is returning an empty array.
I have tried with different approaches but it didn't help me. I am bit new to mongodb, any help on this would be appreciable.

You were so close. Your parentList is an array, so when you define child_id and activeid inside $lookup, they are also array.
If we add $unwind before the $lookup + $group at the end, your query works as expected.
Try this one:
db.parent.aggregate([
{
$match: {
"_id": "5e6cd8c1996ddf1c28e14505"
}
},
{
$unwind: "$parentList"
},
{
$lookup: {
from: "childnodes",
let: {
"child_id": "$parentList.child_id",
"activeid": "$parentList.active_id"
},
pipeline: [
{
$match: {
"$expr": {
$eq: [
"$_id",
"$$child_id"
]
}
}
},
{
$addFields: {
child_version: {
$arrayElemAt: [
{
$filter: {
input: {
$objectToArray: "$$ROOT"
},
cond: {
$eq: [
"$$this.k",
"$$activeid"
]
}
}
},
0
]
}
}
},
{
$project: {
"_id": 0,
"child_id": "$_id",
"start_date": "$startDate",
"current_version_Key": "$active",
"active_child_name": "$child_version.v.childName",
"text": "$child_version.v.text",
"type": "$child_version.v.type"
}
}
],
as: "finalList"
}
},
{
$unwind: "$finalList"
},
{
$group: {
_id: "$_id",
parentList: {
$push: "$finalList"
}
}
}
])
MongoPlayground

Related

Not able to access object value by dynamic key in mongoDB

I have two collections i.e parent and chilednodes.
parent collection :
{
"_id" : "5e6c70e8996ddf1c28e14504",
"startDate" : "2020-02-25T14:01:58.697Z",
"active_id" : "child_vesrion_1",
"child_id" : "5e5e2cd4e972a95b6c32b5bf30"
}
chilednodes:
{
"_id" : "5e5e2cd4e972a95b6c32b5bf30",
"startDate" : "2020-02-25T14:01:58.697Z",
"endDate" : null,
"child_vesrion_1" : {
"childName" : "xyz",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "sometext",
"type" : "notype"
},
"child_vesrion_2" : {
"childName" : "abc",
"createdDate" : "2020-02-25T14:01:58.697Z",
"text" : "sometext2",
"type" : "notype"
},
"active" : "child_vesrion_1"
}
I am trying to get parent detail along with active child version detail. Here i am using aggregation and lookup.
I am constructing "finalList" field by using mongoDB $addFields.
Here is my query;
db.parent
.aggregate([
{
$match: {
'_id' : '5e6c70e8996ddf1c28e14504'
},
},
{
$lookup: {
from: 'chilednodes',
localField: 'child_id',
foreignField: '_id',
as: 'child',
},
},
{
$addFields: {
finalList: {
$map: {
input: '$child',
as: 'c',
in: {
$let: {
vars: { "currentVersionKey": "child_vesrion_1"},
in: {
child_id:'$$c._id',
start_date : '$$c.startDate',
current_version_Key : '$$currentVersionKey',
active_child_name : '$$c.currentVersionKey.childName'
}
}
},
},
},
},
},
{
$project: {
child: 0,
},
},
])
In the response (finalList) i need active child name. I am taking a variable "currentVersionKey" and assigning value as "child_vesrion_1" and using that variable to find the value in "chilednodes". I am not able to get active childname.
NOTE : For testing i have hardcoded "currentVersionKey". But it will be populated dynamically
Here is my expected result;
{
"_id" : "5e6c70e8996ddf1c28e14504",
"startDate" : "2020-02-25T14:01:58.697Z",
"active_id" : "child_vesrion_1",
"child_id" : "5e5e2cd4e972a95b6c32b5bf30",
"finalList" : [
{
"child_id" : "5e5e2cd4e972a95b6c32b5bf30",
"start_date" : "2020-02-25T14:01:58.697Z",
"current_version_Key" : "child_vesrion_1",
"active_child_name" : "xyz"
}
]
}
But i am not getting "active_child_name" : "xyz" in the response. Any helps on this would be appreciated.
You can use $lookup pipeline,
db.parent.aggregate([
{ $match: { "_id": "5e6c70e8996ddf1c28e14504" } },
{
$lookup: {
from: "childnodes",
let: { "child_id": "$child_id", "activeid": "$active_id" },
pipeline: [
{ $match: { "$expr": { $eq: ["$_id", "$$child_id"] } } },
{
$project: {
"child_id": "$_id",
"start_date": "$startDate",
"current_version_Key": "$active",
"active_child_name": {
"$reduce": {
"input": { "$objectToArray": "$$ROOT" },
"initialValue": "",
"in": {
"$cond": [{ "$eq": ["$$this.k", "$$activeid"] },
"$$this.v.childName",
"$$value"
]
}
}
}
}
}
],
as: "finalList",
},
}
])

How to push all values in single array in mongodb

Colleges
/* 1 createdAt:5/9/2019, 7:00:04 PM*/
{
"_id" : ObjectId("5cd42b5c65b41027845938ae"),
"clgID" : "100",
"name" : "Anna University"
},
/* 2 createdAt:5/9/2019, 7:00:04 PM*/
{
"_id" : ObjectId("5cd42b5c65b41027845938ad"),
"clgID" : "200",
"name" : "National"
}
Subjects:
/* 1 createdAt:5/9/2019, 7:03:24 PM*/
{
"_id" : ObjectId("5cd42c2465b41027845938b0"),
"name" : "Hindi",
"members" : {
"student" : [
"123"
]
},
"college" : {
"collegeID" : "100"
}
},
/* 2 createdAt:5/9/2019, 7:03:24 PM*/
{
"_id" : ObjectId("5cd42c2465b41027845938af"),
"name" : "English",
"members" : {
"student" : [
"456",
"789"
]
},
"college" : {
"collegeID" : "100"
}
}
Here i am having two collection and i want to join Colleges table is clgID and Subjects table iscollege.collegeID , then i want to take members.student values and push into single array based on college.collegeID.
My Expected Output
{
"GroupDetails" : [ ],
"clgName" : "National"
},
{
"GroupDetails" : [
"123",
"456",
"789"
],
"clgName" : "Anna University"
}
My Code
db.Colleges.aggregate([
{ $match : { "clgID" : { $in : ["100", "200"] } } },
{ $lookup: { from: "Subjects", localField: "clgID", foreignField: "college.collegeID", as: "GroupDetails" } },
//{ $unwind: "$GroupDetails" },
{ $project: { '_id' : false, 'clgName' : '$name', 'GroupDetails.members.student' : true } }
])
I am getting like this
/* 1 */
{
"GroupDetails" : [ ],
"clgName" : "National"
},
/* 2 */
{
"GroupDetails" : [
{
"members" : {
"student" : [
"456"
]
}
},
{
"members" : {
"student" : [
"123"
]
}
}
],
"clgName" : "Anna University"
}
You can use below aggregation with mongodb 3.6 and above
db.Colleges.aggregate([
{ "$match": { "clgID": { "$in": ["100", "200"] } } },
{ "$lookup": {
"from": "Subjects",
"let": { "clgId": "$clgID" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$$clgId", "$college.collegeID"] } } },
{ "$group": {
"_id": "$college.collegeID",
"groupDetails": { "$push": "$members.student" }
}},
{ "$project": {
"groupDetails": {
"$reduce": {
"input": "$groupDetails",
"initialValue": [],
"in": { "$concatArrays": ["$$this", "$$value"] }
}
}
}}
],
"as": "clg"
}},
{ "$unwind": { "path": "$clg", "preserveNullAndEmptyArrays": true } },
{ "$project": {
"clgName": "$name",
"groupDetails": { "$ifNull": ["$clg.groupDetails", []] }
}}
])
MongoPlayground
Or with the mongodb 3.4 and below
db.Colleges.aggregate([
{ "$match": { "clgID": { "$in": ["100", "200"] }}},
{ "$lookup": {
"from": "Subjects",
"localField": "clgID",
"foreignField": "college.collegeID",
"as": "clg"
}},
{ "$unwind": { "path": "$clg", "preserveNullAndEmptyArrays": true }},
{ "$group": {
"_id": { "clgId": "$clg.college.collegeID", "_id": "$_id" },
"groupDetails": { "$push": "$clg.members.student" },
"clgName": { "$first": "$name" }
}},
{ "$project": {
"_id": "$_id._id",
"clgName": 1,
"groupDetails": {
"$reduce": {
"input": "$groupDetails",
"initialValue": [],
"in": { "$concatArrays": ["$$this", "$$value"] }
}
}
}}
])
MongoPlayground

Mongodb $lookup with nested document

I am trying to create a join using mongo's lookup. I have these three collections.
orderTracking
{
_id: ObejctId("59fb7815b3b8429f4750b0df"),
itemName : "Hamam Soap",
TrackLocation: [{locationId: 1, at:"2017-10-11"},
{locationId: 2,at:"2017-10-13"}],
userId : 12,
price: 20
}
locationType
{
_id: ObejctId("59b2111345cb72345a35fefd"),
locationId : 1
productTypeName: "Warehouse"
},{
_id: ObejctId("59af8ce445cb72345a35feea"),
locationId : 2
productTypeName: "On Transit"
}
User
{
_id: ObejctId("59a504eb6171b554c02292a9"),
"user ID":12,
"userName" : "Shahabaz Shafi",
"dateOfBirth" : "1992-01-01",
"addres": {
"country" : "India",
"state" : "Karnataka",
"city" : "Bengaluru"
}
}
and trying to flatten this to this kind of output.
{
"userName" : "Shahabaz Shafi",
"userId":12,
"dateOfBirth" : "1992-01-01",
"country" : "India",
"state" : "Karnataka",
"city" : "Bengaluru"
"locationType" : [ {productTypeName: "Warehouse",at:"2017-10-11"}, {productTypeName: "On Transit",at:"2017-10-13"}]
}
Edit: 15-11-2018 Updated output
Made some changes to the output columns
{
"userName":"Shahabaz Shafi",
"userId":12,
"dateOfBirth":"1992-01-01",
"country":"India",
"state":"Karnataka",
"city":"Bengaluru",
"items":[
{
"itemName":"Hamam Soap",
"userId":12,
"price":20,
"TrackLocation":[
{
"locationId":1,
"at":"2017-10-11",
"productTypeName":"Warehouse"
},
{
"locationId":2,
"at":"2017-10-13",
"productTypeName":"On Transit"
}
]
}
]
}
How do I approach this ?
PS : I am also using compass
You can use below aggregation with mongodb 3.6 and above
db.User.aggregate([
{ "$lookup": {
"from": "orderTracking",
"let": { "userId": "$userId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$userId", "$$userId"] }}},
{ "$unwind": "$TrackLocation" },
{ "$lookup": {
"from": "locationType",
"let": { "location": "$TrackLocation.locationId" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$locationId", "$$location"] }}}
],
"as": "locationType"
}},
{ "$project": {
"_id": 0,
"productTypeName": { "$arrayElemAt": ["$locationType.productTypeName", 0] },
"at": "$TrackLocation.at"
}}
],
"as": "locationType"
}},
{ "$replaceRoot": { "newRoot": { "$mergeObjects": ["$addres", "$$ROOT"] }}},
{ "$project": { "addres": 0 }}
])
Output
[
{
"_id": ObjectId("59a504eb6171b554c02292a9"),
"city": "Bengaluru",
"country": "India",
"dateOfBirth": "1992-01-01",
"locationType": [
{
"at": "2017-10-11",
"productTypeName": "Warehouse"
},
{
"at": "2017-10-13",
"productTypeName": "On Transit"
}
],
"state": "Karnataka",
"userId": 12,
"userName": "Shahabaz Shafi"
}
]

Mongo DB aggregation with $project and $filter: $add and $subtract return null

So I'm running a pretty big aggregation query in mongo shell (just for testing purpose)
in my last $project step, i use $filter to select a range of elements.
$filter: {
"input": "$users",
"as": "users",
"cond": {
$and: [
{
$lte: [
"$$users.ranking",
{$add: ["$myUser[0].ranking", 5]}
]
},
{
$gte: [
"$$users.ranking",
{$subtract: ["$myUser[0].ranking", 5]}
]
}
]
}
}
$subtract and $add both return null, any idea how i get it correct?
MongoVersion: 3.6.3, running in a docker container using the mongo 3.6.3 image.
Correct output should be:
"users" : [
{
"_id" : ObjectId("5ba3c2089a3a3e26a859f11b"),
"sgId" : ObjectId("5b76c1040c3aa5000559e6b3"),
"score" : 30,
"ranking" : NumberLong("0")
},
{
"_id" : ObjectId("5ba3c1d89a3a3e26a859f11a"),
"sgId" : ObjectId("5b76c1000c3aa500060e0fd2"),
"score" : 20,
"ranking" : NumberLong("1")
},
{
"_id" : ObjectId("5ba4fa3b71936b33e46569b9"),
"sgId" : ObjectId("5b76c8a3f7d606000566b652"),
"score" : 10,
"ranking" : NumberLong("2")
},
{
"_id" : ObjectId("5ba4fa4c71936b33e46569ba"),
"sgId" : ObjectId("5b76cafbf7d6060006270c90"),
"score" : 9,
"ranking" : NumberLong("3")
},
{
"_id" : ObjectId("5ba4fe6e71936b33e46569bb"),
"sgId" : ObjectId("5b7a4e69f7d606000566b65f"),
"score" : 8,
"ranking" : NumberLong("4")
},
{
"_id" : ObjectId("5ba4fe7471936b33e46569bc"),
"sgId" : ObjectId("5b7a4f47f7d6060006270cc4"),
"score" : 7,
"ranking" : NumberLong("5")
},
{
"_id" : ObjectId("5ba4fe8871936b33e46569bd"),
"sgId" : ObjectId("5b7a5265f7d606000566b67e"),
"score" : 6,
"ranking" : NumberLong("6")
}
]
Complete Query:
db.highscore.aggregate([
{
$sort: {score: -1}
},
{
$group: {
"_id": false,
"users": {
$push: {
"_id": "$_id",
"sgId": "$sgId",
"score": "$score",
}
}
}
},
{
$unwind: {
"path": "$users",
"includeArrayIndex": "ranking"
}
},
{
$group: {
"_id": false,
"users": {
$push: {
"_id": "$users._id",
"sgId": "$users.sgId",
"score": "$users.score",
"ranking": "$ranking"
}
}
}
},
{
$project: {
"users": "$users",
"myUser": {
$filter: {
"input": "$users",
"as": "user",
"cond": {
$eq: ["$$user.sgId", ObjectId("5b76c1000c3aa500060e0fd2")]
}
}
}
}
},
{
$project: {
"myUser": "$myUser",
"users" : {
$filter: {
"input": "$users",
"as": "users",
"cond": {
$and: [
{
$lte: [
"$$users.ranking",
{$add: ["$myUser[0].ranking", NumberLong("5")]}
]
},
{
$gte: [
"$$users.ranking",
{$subtract: ["$myUser[0].ranking", NumberLong("5")]}
]
}
]
}
}
}
}
},
])
Used Documents:
{
"_id" : ObjectId("5ba3c1d89a3a3e26a859f11a"),
"sgId" : ObjectId("5b76c1000c3aa500060e0fd2"),
"type" : "a",
"score" : 20,
"created" : ISODate("2018-09-20T17:50:48.024+02:00")
},
{
"_id" : ObjectId("5ba3c2089a3a3e26a859f11b"),
"sgId" : ObjectId("5b76c1040c3aa5000559e6b3"),
"type" : "a",
"score" : 30,
"created" : ISODate("2018-09-20T17:51:36.258+02:00")
},
{
"_id" : ObjectId("5ba4fa3b71936b33e46569b9"),
"sgId" : ObjectId("5b76c8a3f7d606000566b652"),
"type" : "a",
"score" : 10,
"created" : ISODate("2018-09-20T17:50:48.024+02:00")
},
{
"_id" : ObjectId("5ba4fa4c71936b33e46569ba"),
"sgId" : ObjectId("5b76cafbf7d6060006270c90"),
"type" : "a",
"score" : 9,
"created" : ISODate("2018-09-20T17:50:48.024+02:00")
}
Found it,
i just needed to add an $unwind before the last $project to convert the myUser Array into an object - then i was able to reach it for the add.
So full pipeline to get rankings of a highscore list and a range with your given user as source.
db.highscore.aggregate([
{
$sort: {score: -1}
},
{
$group: {
"_id": false,
"users": {
$push: {
"_id": "$_id",
"sgId": "$sgId",
"score": "$score",
}
}
}
},
{
$unwind: {
"path": "$users",
"includeArrayIndex": "ranking"
}
},
{
$group: {
"_id": false,
"users": {
$push: {
"_id": "$users._id",
"sgId": "$users.sgId",
"score": "$users.score",
"ranking": "$ranking"
}
}
}
},
{
$project: {
"users": "$users",
"myUser": {
$filter: {
"input": "$users",
"as": "user",
"cond": {
$eq: ["$$user.sgId", ObjectId("5b76c1000c3aa500060e0fd2")]
}
}
}
}
},
{
$unwind: {
path: '$myUser'
}
},
{
$project: {
"myUser": "$myUser",
"users" : {
$filter: {
"input": "$users",
"as": "users",
"cond": {
$and: [
{
$lte: [
"$$users.ranking",
{$add: ["$myUser.ranking", NumberLong("2")]}
]
},
{
$gte: [
"$$users.ranking",
{$subtract: ["$myUser.ranking", NumberLong("2")]}
]
}
]
}
}
}
}
},
], {'allowDiskUse': true})

Group Multiple Values in Aggregation

I want to group the all field of a collection with unique total. Let's assume there is collection like this:
id country state operator
121 IN HR AIRTEL
212 IN MH AIRTEL
213 US LA AT&T
214 UK JK VODAFONE
Output should be like this:
{
"country": { "IN": 2, "US":1, "UK":1 },
"state": { "HR":1, "MH":1, "LA":1, "JK": 1 },
"operator": { "AIRTEL":2, "AT&T": 1, "VODAFONE": 1 }
}
I am trying to use mongo aggregation framework, but can't really think how to do this?
I find out some similar to your output using aggregation check below code
db.collectionName.aggregate({
"$group": {
"_id": null,
"countryOfIN": {
"$sum": {
"$cond": [{
$eq: ["$country", "IN"]
}, 1, 0]
}
},
"countryOfUK": {
"$sum": {
"$cond": [{
$eq: ["$country", "UK"]
}, 1, 0]
}
},
"countryOfUS": {
"$sum": {
"$cond": [{
$eq: ["$country", "US"]
}, 1, 0]
}
},
"stateOfHR": {
"$sum": {
"$cond": [{
$eq: ["$state", "HR"]
}, 1, 0]
}
},
"stateOfMH": {
"$sum": {
"$cond": [{
$eq: ["$state", "MH"]
}, 1, 0]
}
},
"stateOfLA": {
"$sum": {
"$cond": [{
$eq: ["$state", "LA"]
}, 1, 0]
}
},
"stateOfJK": {
"$sum": {
"$cond": [{
$eq: ["$state", "JK"]
}, 1, 0]
}
},
"operatorOfAIRTEL": {
"$sum": {
"$cond": [{
$eq: ["$operator", "AIRTEL"]
}, 1, 0]
}
},
"operatorOfAT&T": {
"$sum": {
"$cond": [{
$eq: ["$operator", "AT&T"]
}, 1, 0]
}
},
"operatorOfVODAFONE": {
"$sum": {
"$cond": [{
$eq: ["$operator", "VODAFONE"]
}, 1, 0]
}
}
}
}, {
"$group": {
"_id": null,
"country": {
"$push": {
"IN": "$countryOfIN",
"UK": "$countryOfUK",
"US": "$countryOfUS"
}
},
"STATE": {
"$push": {
"HR": "$stateOfHR",
"MH": "$stateOfMH",
"LA": "$stateOfLA",
"JK": "$stateOfJK"
}
},
"operator": {
"$push": {
"AIRTEL": "$operatorOfAIRTEL",
"AT&T": "$operatorOfAT&T",
"VODAFONE": "$operatorOfVODAFONE"
}
}
}
}, {
"$project": {
"_id": 0,
"country": 1,
"STATE": 1,
"operator": 1
}
})
using $cond created groups of matched data and pushed them in second groups to combine.
An output format like you are looking for is not really suited to the aggregation framework since you are tranforming part of your data in to "key" names. The aggregation framework does not do this but rather sticks to database "best practice" as does not transform "data" to "key" names in any way.
You can perform a mapReduce operation instead with allows more flexibilty with the manipulation, but not as good performance due to the need to use JavaScript code to perform the manipulation:
db.collection.mapReduce(
function () {
var obj = {},
doc = this;
delete doc._id;
Object.keys(doc).forEach(function(key) {
obj[key] = {};
obj[key][doc[key]] = 1;
});
emit( null, obj );
},
function (key,values) {
var result = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(outerKey) {
Object.keys(value[outerKey]).forEach(function(innerKey) {
if ( !result.hasOwnProperty(outerKey) ) {
result[outerKey] = {};
}
if ( result[outerKey].hasOwnProperty(innerKey) ) {
result[outerKey][innerKey] += value[outerKey][innerKey];
} else {
result[outerKey][innerKey] = value[outerKey][innerKey];
}
});
});
});
return result;
},
{ "out": { "inline": 1 } }
)
And in the stucture that applies to all mapReduce results:
{
"results" : [
{
"_id" : null,
"value" : {
"country" : {
"IN" : 2,
"US" : 1,
"UK" : 1
},
"state" : {
"HR" : 1,
"MH" : 1,
"LA" : 1,
"JK" : 1
},
"operator" : {
"AIRTEL" : 2,
"AT&T" : 1,
"VODAFONE" : 1
}
}
}
]
}
For the aggregation framework itself, it is better suited to producing aggregation results that are more consistently structured:
db.mapex.aggregate([
{ "$project": {
"country": 1,
"state": 1,
"operator": 1,
"type": { "$literal": ["country","state","operator"] }
}},
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"type": "$type",
"key": { "$cond": {
"if": { "$eq": [ "$type", "country" ] },
"then": "$country",
"else": { "$cond": {
"if": { "$eq": [ "$type", "state" ] },
"then": "$state",
"else": "$operator"
}}
}}
},
"count": { "$sum": 1 }
}}
])
Which would output:
{ "_id" : { "type" : "state", "key" : "JK" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "UK" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "US" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "AT&T" }, "count" : 1 }
{ "_id" : { "type" : "state", "key" : "LA" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "AIRTEL" }, "count" : 2 }
{ "_id" : { "type" : "state", "key" : "MH" }, "count" : 1 }
{ "_id" : { "type" : "state", "key" : "HR" }, "count" : 1 }
{ "_id" : { "type" : "operator", "key" : "VODAFONE" }, "count" : 1 }
{ "_id" : { "type" : "country", "key" : "IN" }, "count" : 2 }
But is fairly easy to transform in client code while iterating the results:
var result = {};
db.mapex.aggregate([
{ "$project": {
"country": 1,
"state": 1,
"operator": 1,
"type": { "$literal": ["country","state","operator"] }
}},
{ "$unwind": "$type" },
{ "$group": {
"_id": {
"type": "$type",
"key": { "$cond": {
"if": { "$eq": [ "$type", "country" ] },
"then": "$country",
"else": { "$cond": {
"if": { "$eq": [ "$type", "state" ] },
"then": "$state",
"else": "$operator"
}}
}}
},
"count": { "$sum": 1 }
}}
]).forEach(function(doc) {
if ( !result.hasOwnProperty(doc._id.type) )
result[doc._id.type] = {};
result[doc._id.type][doc._id.key] = doc.count;
})
Which gives the final structure in "result":
{
"state" : {
"JK" : 1,
"LA" : 1,
"MH" : 1,
"HR" : 1
},
"country" : {
"UK" : 1,
"US" : 1,
"IN" : 2
},
"operator" : {
"AT&T" : 1,
"AIRTEL" : 2,
"VODAFONE" : 1
}
}