Here's another way to do it by using `"$reduce"`. Comments are in the aggregation pipeline.
```mongodb
db.collection.aggregate([
{
"$set": {
// rewrite fruits
"fruits": {
"$reduce": {
"input": "$fruits",
"initialValue": [],
"in": {
"$let": {
"vars": {
// get fruit index in $$value : will be -1 if not there
"idx": {"$indexOfArray": ["$$value.fruit", "$$this.fruit"]}
},
"in": {
"$cond": [
// is fruit not in $$value yet
{"$eq": ["$$idx", -1]},
// new fruit so put in $$value and make "type" an array
{
"$concatArrays": [
"$$value",
[{"$mergeObjects": ["$$this", {"type": ["$$this.type"]}]}]
]
},
// fruit already in $$value, so map $$value with "type" update
{
"$map": {
"input": "$$value",
"as": "val",
"in": {
"$cond": [
// is this array element not the right fruit?
{"$ne": ["$$val.fruit", "$$this.fruit"]},
// nope, leave the element as-is
"$$val",
// this element needs to be updated
{
"$mergeObjects": [
"$$val",
{
"type": {
"$cond": [
// is this "type" already in array?
{"$in": ["$$this.type", "$$val.type"]},
// yes, so leave it as-is
"$$val.type",
// this is a new "type", so add it to array
{"$concatArrays": ["$$val.type", ["$$this.type"]]}
]
}
}
]
}
]
}
}
}
]
}
}
}
}
}
}
}
])
```
Try it on [mongoplayground.net](https://mongoplayground.net/p/QsO_1Y5Cnve "Click me!").
For your actual form, and therefore presuming that you actually know the possible values for "type" then you can do this with two [**`$group`**][1] stages and some use of the [**`$cond`**][2] operator:
<!-- language-all: lang-js -->
db.types.aggregate([
{ "$group": {
"_id": {
"stat": "$stat",
"type": "$type"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.stat",
"type1": { "$sum": { "$cond": [
{ "$eq": [ "$_id.type", 1 ] },
"$count",
0
]}},
"type2": { "$sum": { "$cond": [
{ "$eq": [ "$_id.type", 2 ] },
"$count",
0
]}},
"type3": { "$sum": { "$cond": [
{ "$eq": [ "$_id.type", 3 ] },
"$count",
0
]}}
}}
])
Which gives exactly:
{ "_id" : "foobar", "type1" : 2, "type2" : 1, "type3" : 1 }
I actually prefer the more dynamic form with two [**`$group`**][1] stages though:
db.types.aggregate([
{ "$group": {
"_id": {
"stat": "$stat",
"type": "$type"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.stat",
"types": { "$push": {
"type": "$_id.type",
"count": "$count"
}}
}}
])
Not the same output but functional and flexible to the values:
{
"_id" : "foobar",
"types" : [
{
"type" : 3,
"count" : 1
},
{
"type" : 2,
"count" : 1
},
{
"type" : 1,
"count" : 2
}
]
}
Otherwise if you need the same output format but need the flexible fields then you can always use mapReduce, but it's not exactly the same output.
db.types.mapReduce(
function () {
var obj = { };
var key = "type" + this.type;
obj[key] = 1;
emit( this.stat, obj );
},
function (key,values) {
var obj = {};
values.forEach(function(value) {
for ( var k in value ) {
if ( !obj.hasOwnProperty(k) )
obj[k] = 0;
obj[k]++;
}
});
return obj;
},
{ "out": { "inline": 1 } }
)
And in typical mapReduce style:
"results" : [
{
"_id" : "foobar",
"value" : {
"type1" : 2,
"type2" : 1,
"type3" : 1
}
}
],
But those are your options
[1]: http://docs.mongodb.org/manual/reference/operator/aggregation/group/
[2]: http://docs.mongodb.org/manual/reference/operator/aggregation/cond/