How does mongodb update work internally? - mongodb

Lets say I have a document like this
{ "_id" : ObjectId("544946347db27ca99e20a95f"), "name" : "Foo Bar",'firstName':"foo", "lastName":"bar" }
If I perform two separate updates like this
update({'_id':'544946347db27ca99e20a95f'},{$set:{'lastName':'BARBAR'}})
update({'_id':'544946347db27ca99e20a95f'},{$set:{'name':'Foo BARBAR'}})
Is it like two separate transactions or does it aggregate both of them and do a single write?
If I have to learn more about the internal workings, what should I be searching for?
Thank you for your help.

It is two different update operations.
In almost all MongoDB Operators you can use many fields. So the best way is:
db.collection.update({ '_id':'544946347db27ca99e20a95f'}, {
'$set': { 'lastName': 'BARBAR', 'name': 'Foo BARBAR' }
})
You can learn more about Operators in the official documentation. For example the $set operator has the following syntax:
{ $set: { <field1>: <value1>, ... } }
You may want to use the "Bulk" API and in this case rewrite your function so that the can take a bulk object argument.
var bulk = db.collection.initializeOrderedBulkOp();
function foo(bulk){
// do something
bulk.find({ "_id": "544946347db27ca99e20a95f" }).updateOne({
"$set": { "lastName": "BARBAR" }
// do another thing
});
}
function bar(bulk){
// do something
bulk.find({ "_id": "544946347db27ca99e20a95f" }).updateOne({
"$set": { "name": "Foo BARBAR" }
// do another thing
});
}
foo(bulk);
bar(bulk);
bulk.execute();

Related

How to upsert an element in mongodb array?

Suppose our document looks like this
{
a:1,
b:[
{c:120,d:100},
{c:121,d:110}
]
}
Now how could I upsert new objects in this array?
Suppose I want to perform update on the above document and add {c:200,d:120} to b so my expected result looks like this
{
a:1,
b:[
{c:120,d:100},
{c:121,d:110},
{c:200,d:120}
]
}
Also the update will of $inc, meaning suppose I want to increment d by 200 if c is present(lets say c is 200 and it is already present in the above document), if not present then I want to upsert the document itself.
Any help would be much appreciated.
It can be achieved using the $push update command.
db.<Collection-Name>.updateOne({"a": 1}, {"$push": {"b": {"c":200, "d":120}}})
Note: Use $addToSet if you don't want duplicates elements inside the array
UPDATE:
I don't think your precise requirement can't be achieved by a single command.
Your exact requirement can be achieved by the below script:
if (db.test9.findOne({"_id": ObjectId("5f19402abbc59a3864783fc7"), "b.c": 200}, {"_id": 1}) != null) {
db.test9.updateOne({
"_id": ObjectId("5f19402abbc59a3864783fc7"),
"b.c": 200,
}, {
"$set": {
"b.$.d": 120
}
})
} else {
db.test9.updateOne({
"_id": ObjectId("5f19402abbc59a3864783fc7"),
}, {
"$push": {
"b": {
"c":200, "d":120
}
}
});
}

Ensuring exactly N items with value X remain in an array with mongodb

Assuming we have a document in my MongoDB collection like the following:
{
"_id": "coffee",
"orders": [ "espresso", "cappuccino", "espresso", ... ],
}
How do I use a single update statement that ensures there are exactly say 2 espressos in this document, without knowing how many there are to begin with?
I know that using 2 consecutive statements I can do
db.test.update(
{ _id: "coffee" },
{ "$pull": { "orders": "espresso" } }
);
followed by
db.test.update(
{ "_id": "coffee" },
{ "$push": { "orders": { "$each": ["espresso", "espresso"] } } }
);
But when combining both into a single statement, MongoDB balks with an error 40, claiming Updating the path 'orders' would create a conflict at 'orders' (understandable enough - how does MongoDB what to do first?).
So, how can I do the above in a single statement? Please note that since I'll be using the above in the context of a larger unordered bulk operation, combining the above in an ordered bulk operation won't work.
Thanks for your help!

meteor query for all documents with unique field

I want to do exactly what this SO question gets at but with Meteor on the server side:
How do I retrieve all of the documents which HAVE a unique value of a
field?
> db.foo.insert([{age: 21, name: 'bob'}, {age: 21, name: 'sally'}, {age: 30, name: 'Jim'}])
> db.foo.count()
3
> db.foo.aggregate({ $group: { _id: '$age', name: { $max: '$name' } } }).result
[
{
"_id" : 30,
"name" : "Jim"
},
{
"_id" : 21,
"name" : "sally"
}
]
My understanding is that aggregate is not available for Meteor. If that is correct, how can I achieve the above? Performing post-filtering on a query after-the-fact is not an ideal solution, as I want to use limit. I'm also happy to get documents with a unique field some other way as long as I can use limit.
There is a general setup you can use to access the underlying driver collection object and therefore .aggregate() without installing any other plugins.
The basic process goes like this:
FooAges = new Meteor.Collection("fooAges");
Meteor.publish("fooAgeQuery", function(args) {
var sub = this;
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var pipeline = [
{ "$group": {
"_id": "$age",
"name": { "$max": "$name" }
}}
];
db.collection("foo").aggregate(
pipeline,
// Need to wrap the callback so it gets called in a Fiber.
Meteor.bindEnvironment(
function(err, result) {
// Add each of the results to the subscription.
_.each(result, function(e) {
// Generate a random disposable id for aggregated documents
sub.added("fooAges", Random.id(), {
"age": e._id,
"name": e.name
});
});
sub.ready();
},
function(error) {
Meteor._debug( "Error doing aggregation: " + error);
}
)
);
});
So you define a collection for the output of the aggregation and within a routine like this you then publish the service that you are also going to subscribe to in your client.
Inside this, the aggregation is run and populated into the the other collection ( logically as it doesn't actually write anything ). So you then use that collection on the client with the same definition and all the aggregated results are just returned.
I actually have a full working example application of a similar processs within this question, as well as usage of the meteor hacks aggregate package on this question here as well, if you need further reference.

MongoDB update document object

user = users.findOne({
"$or": [{
'local.email': 'some#email.com'
}, {
'google.email': 'some#email.com'
}, {
'facebook.email': 'some#email.com'
}]
// do stuff with user object
So I have the user object. This is fine, after I'm finished with what I need from it property wise I wish to update some of the fields in this object now, I've tried the following without it working:
user.local.email = 'other#email.com';
users.update(user);
Is this not a viable way of updating a document?
Use the $set operator to update your document as follows:
db.users.update(
{
"$or": [
{'local.email': 'some#email.com'},
{'google.email': 'some#email.com'},
{'facebook.email': 'some#email.com'}
]
},
{
$set: {
'local.email': 'other#email.com'
}
}
)
With the update method, you do not need to do another query which finds the document you want to update because the update() method takes in a query parameter which is the selection criteria for the update, the same query selectors as in the find() method are available. Read more on the update method in the Mongo docs here.
This was the better suited solution.
user.local.email = 'other#email.com';
users.update({
"$or": [{
'local.email': 'some#email.com'
}, {
'google.email': 'some#email.com'
}, {
'facebook.email': 'some#email.com'
}]
}, user);

Replace a word from a string

I have mongodb documents with a field like this:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg
How can I replace the zoom part in the string value with some other text in order to get:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg
You could use mongo's forEach() cursor method to do an atomic update with the $set operator :
db.collection.find({}).snapshot().forEach(function(doc) {
var updated_url = doc.Image.replace('zoom', 'product2');
db.collection.update(
{"_id": doc._id},
{ "$set": { "Image": updated_url } }
);
});
Given a very large collection to update, you could speed up things a little bit with bulkWrite and restructure your update operations to be sent in bulk as:
var ops = [];
db.collection.find({}).snapshot().forEach(function(doc) {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { "$set": { "Image": doc.Image.replace('zoom', 'product2') } }
}
});
if ( ops.length === 500 ) {
db.collection.bulkWrite(ops);
ops = [];
}
})
if ( ops.length > 0 )
db.collection.bulkWrite(ops);
db.myCollection.update({image: 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg'}, {$set: {image : 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg'}})
If you need to do this multiple times to multiple documents, you need to iterate them with a function. See here: MongoDB: Updating documents using data from the same document
Nowadays,
starting Mongo 4.2, db.collection.updateMany (alias of db.collection.update) can accept an aggregation pipeline, finally allowing the update of a field based on its own value.
starting Mongo 4.4, the new aggregation operator $replaceOne makes it very easy to replace part of a string.
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
db.collection.updateMany(
{ "Image": { $regex: /zoom/ } },
[{
$set: { "Image": {
$replaceOne: { input: "$Image", find: "zoom", replacement: "product2" }
}}
}]
)
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
The first part ({ "Image": { $regex: /zoom/ } }) is just there to make the query faster by filtering which documents to update (the ones containing "zoom")
The second part ($set: { "Image": {...) is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline):
$set is a new aggregation operator (Mongo 4.2) which in this case replaces the value of a field.
The new value is computed with the new $replaceOne operator. Note how Image is modified directly based on the its own value ($Image).