Reputation: 5007
When using MongoDB I'm currently doing a conditional upsert as a part of an aggregation process, on the form (simplified alot):
db.dbname.update({attr1 : value1, attr2 : value2},
{"$inc" : { avg : current_value, nr : 1}},
false (multi), true (upsert))
But I want to able to keep a maximum (and minimum) value as well, without having to retrieve the document. Something along the lines of:
db.dbname.update({ attr1 : value1, attr2 : value2},
{"$inc" : { avg : current_value, nr : 1},
"$setIfBigger" : { max : current_value}},
false (multi), true (upsert))
Is this possible in an efficient way?
My current, extremely inefficient, solution is that I check for a current aggregation document, and if it exists I update the values accordingly, and if it doesn't I create a new document. Example (again, simplified alot, but the essence is there):
var obj = db.dbname.findOne({attr1 : value1, attr2 : value2},{_id:1});
if (obj != null) {
db.dbname.update({attr1 : value1, attr2 : value2},
{"$inc" : { avg : current_value, nr : 1},
"$set" : { max : (obj.max > current_value ? obj.max : current_value}},
false (multi), true (upsert));
} else {
db.dbname.save({attr1 : value1, attr2 : value2,
avg : current_value, nr : 1,
max : current_value});
}
The actual program is written in Java and uses the mongo-API, and the aggregation process is quite complex and uses composition techniques way beyond Javascript to communicate with other servers, ergo mapreduce is not an option. Finally the end result is quite a humongous set of simple values which I want to store in the most effecient way and also store precalculated averages, maximums and minimums of certain combinations.
One solution is creating unique function-objects in JS for every single update, which I believe is not an efficient way?
The main objective is to decrease the time taken to perform an aggregation of this type, bandwidth usage is secondary.
Upvotes: 13
Views: 11810
Reputation: 9497
This can now be done much more easily in the MongoDB 2.6 release which includes Insert and Update Improvements. Specifically there are new $min and $max operators that perform a conditional update depending on the relative size of the specified value and the current value of a field.
So for example this update:
db.scores.update( { _id: 1 }, { $max: { highScore: 950 } } )
would conditionally update the specified document if 950 is greater than the current value of highScore
.
Upvotes: 15
Reputation: 45287
One solution is creating unique function-objects in JS for every single update, which I believe is not an efficient way?
May not work as you'd like it to:
https://jira.mongodb.org/browse/SERVER-458
Is this possible in an efficient way?
The issue you're facing is that you want MongoDB to perform an upsert with some more complex logic. So you want to leverage the document-level locking to effectively "trigger" multiple complex changes all at once.
You're not the first one to want this, unfortunately it's not available right now. There are several server bugs tied to this type of "more complex update behavior". You may want to watch / vote on a few of the following to check for progress.
$set
only when inserting, add only when not existing, push
and set
at the same time, coordinate a "size" with $addToSet
.
In particular, SERVER-458 seems closest to what you want.
Upvotes: 3