I have the following collection with c
array inside each document
{
{
id: 1,
k: 2.2,
type: "dog",
c: [ {parentId:1, p:2.2}, {parentId:1, p:1.4} ]
},
{
id: 2,
k: 4.3,
type:"cat",
c: [ {parentId:2, p:5.2}, {parentId:2, p:4.5} ]
}
}
parentId
inside each subdocument in c
is id of containing document.
I want to group all docs by type
and in each group know sum of k
and sum of all p
in all arrays of the group.
Currently I do summation of k
in the group stage but summation of p
in the result array in the application. I want to do summation of p
in DB!
This is what I do currently:
db.myCol.aggregate([
{
$group: {
_id: { type: '$type'},
k: {$sum: '$k'}, // sum k values, very easy!
// p: {$sum: '$c.0.p'} <==== Does not work, too
c: {$addToSet: '$c'} // add to each group all c arrays of group's members
}
}
], function(err, res) {
// go over c-arrays and sum p values
var accP = 0; // accumulator for p values
for ( var i=0; i<res.length; i++ ) {
var c = res[i].c;
for (var j=0;j<c.length; j++) {
var c2 = c[j];
for ( var k=0; k<c2.length; k++) { // finally got to objects c array
accP += c2[k].p;
}
}
res[i].c = accP; // replace array with accumulated p value
}
});
You need to first $group
your documents by "type", use the $sum
accumulator operator to return the sum of "k" and use the $push
which returns a 2D array of "c". Now you need two "$unwind" stage where you denormalize the "c" 2D array. Your last stage in the pipeline is another $group
stage where you calculate the sum of "p" using "dot notation"
db.collection.aggregate([
{ '$group': {
'_id': '$type',
'k': { '$sum': '$k' }, 'c': { '$push': '$c' }
} },
{ '$unwind': '$c' },
{ '$unwind': '$c' },
{ '$group': {
'_id': '$_id',
'k': { '$first': '$k' },
'c': { '$sum': '$c.p' }
}}
])
Which yields:
{ "_id" : "dog", "k" : 2.2, "c" : 3.6 }
{ "_id" : "cat", "k" : 4.3, "c" : 9.7 }
Starting in version 3.2, the following accumulator expressions, previously only available in the
$group
stage, are now also available in the$project
stage.
Which means that we can take advantage of that and use the $sum
accumulator operator in $project
. Of course the $map
operator returns an array of "p" for each document.
db.collection.aggregate([
{ '$project': {
'type': 1,
'k': 1,
'c': {
'$sum': {
'$map': {
'input': '$c',
'as': 'subc',
'in': '$$subc.p'
}
}
}
}},
{ '$group': {
'_id': '$type',
'k': { '$sum': '$k' },
'c': { '$sum': '$c' }
}}
])
Which returns:
{ "_id" : "cat", "k" : 4.3, "c" : 9.7 }
{ "_id" : "dog", "k" : 2.2, "c" : 3.6 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With