How to sum the value of a key across all documents in a MongoDB collection

MongodbNosql

Mongodb Problem Overview


I have collection in MongoDB:

{ "_id" : ObjectId("4d2407265ff08824e3000001"), "subida" : 3.95 }
{ "_id" : ObjectId("4d2551b4ae9fa739640df821"), "subida" : 6.03 }
{ "_id" : ObjectId("4d255b115ff08821c2000001"), "subida" : 5.53 }
{ "_id" : ObjectId("4d25e8d55ff08814f8000001"), "subida" : 1.96 }

How I can sum the value of a key, e.g., "subida", across all documents? With the documents above, I should receive something along the lines of:

{ "subida" : 17.47 }

Mongodb Solutions


Solution 1 - Mongodb

In this case, aggregation is much simpler and much more efficient than mapReduce:

db.collection.aggregate({
    $group: {
        _id: '',
        subida: { $sum: '$subida' }
    }
 }, {
    $project: {
        _id: 0,
        subida: '$subida'
    }
})
  1. use $group with $sum to calculate sum
  2. use projection's $project operator to remove id key required by $group operator

Solution 2 - Mongodb

I'd personally perform a mapreduce on the collection :

map is a simple function emitting the "subida" field. The key should the same if you need a single sum; the result after reduce will yield the single object {<key>: <sum>}, with <key> being whatever value you supplied in the emit.

map = function() { emit(<key>, this.subida); }

reduce is also a simple function summing them :

red = function(k, v) {
  var i, sum = 0;
  for (i in v) {
    sum += v[i];
  }
  return sum;
}

You can then call mapreduce on your collection <mycollection>:

res = db.<mycollection>.mapReduce(map, red);

Which will create a temporary new collection you can manipulate like any other collection. The value returned by mapReduce holds several values regarding the mapReduce such as the time taken, status..., as well as the temp. collection name created in the "result" field. To get the values you need, you have to query that collection :

db[res.result].find()

Which should give you the object {<key>: <sum>}.

If you run MongoDB 1.7.4 or higher, you can save you a bit of hassle by asking MongoDB to return the result directly without creating a collection :

db.<mycollection>.mapReduce(map, red, {out : {inline: 1}});

Solution 3 - Mongodb

Use this simplest query to get the sum of the result

db.collection.aggregate({
    $group: {
        _id: '',
        subida: { $sum: '$subida' }
    }
 }
)

Solution 4 - Mongodb

Option 0: Use the MongoDB aggregation pipeline
[NB: this option was added a long time after this question was asked but is the right approach now]


Option 1: Query all the records, return only the subida field from Mongo and add them up by iterating over the Mongo cursor client side.

Option 2: Write a map reduce command that emits only the subdia field (same key for all) and then a reduce command that totals them.

Option 3: Use db.eval to execute javascript on the server: http://www.mongodb.org/display/DOCS/Server-side+Code+Execution

Option 4: Accumulate 'subida' values as you insert values into your collection so that you have an up-to-date total at hand whenever you need it. You can store the total in a different document and use atomic "update if current" operations to update it: http://www.mongodb.org/display/DOCS/Atomic+Operations

Solution 5 - Mongodb

You are able to use the collection as an array, just add the values in a loop.

Edit: Yes, this is a bad practice when having a big dataset.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJAMView Question on Stackoverflow
Solution 1 - MongodbIlya BuilukView Answer on Stackoverflow
Solution 2 - MongodbYochiroView Answer on Stackoverflow
Solution 3 - MongodbSathiaView Answer on Stackoverflow
Solution 4 - MongodbIan MercerView Answer on Stackoverflow
Solution 5 - MongodboscarcardosoView Answer on Stackoverflow