Why is my MongoDb query inserting an embedded document on Update? - mongodb

This is my MongoDB query:
db.events.update({date:{$gte: ISODate("2014-09-01T00:00:00Z")}},{$set:{"artists.$.soundcloud_toggle":false}},{multi:true,upsert:false})
Apparently I cannot use "artists.$.soundcloud_toggle" to update all artist documents within the artists array:
"The $ operator can update the first array element that matches
multiple query criteria specified with the $elemMatch() operator.
http://docs.mongodb.org/manual/reference/operator/update/positional/"
I'm happy to run the query a number of times changing the index of the array in order to set the soundcloud_toggle property of every artist in every event that matches the query e.g
artists.0.soundcloud_toggle
artists.1.soundcloud_toggle
artists.2.soundcloud_toggle
artists.3.soundcloud_toggle
The problem is: when there is say, only one artist document in the artists array and I run the query with "artists.1.soundcloud_toggle" It will insert an artist document into the artist array with a single property:
{
"soundcloud_toggle" : true
},
(I have declared "upsert:false", which should be false by default anyways)
How do I stop the query from inserting a document and setting soundcloud_toggle:false when there is no existing document there? I only want it to update the property if an artist exists at the given artists array index.

If, like you said, you don't mind completing the operation with multiple queries, you can add an $exists condition to your filter.
E.g. in the 5th iteration, when updating index=4, add: "artists.4": {$exists: true}, like:
db.events.update(
{ date: {$gte: ISODate("2014-09-01T00:00:00Z")},
"artists.4": {$exists: true} },
{ $set:{ "artists.4.soundcloud_toggle" :false } },
{ multi: true, upsert: false }
)

Related

how can I make the "updated" of mongodb stop when updating a field of a nested array?

I have a database like this:
{
"universe":"comics",
"saga":[
{
"name":"x-men",
"characters":[
{
"character":"wolverine",
"picture":"618035022351.png"
},
{
"character":"wolverine",
"picture":"618035022352.png"
}
]
}
]
},
{
"universe":"dc",
"saga":[
{
"name":"spiderman",
"characters":[
{
"character":"venom",
"picture":"618035022353.png"
}
]
}
]
}
And with this code, I update the field where name: wolverine:
db.getCollection('collection').findOneAndUpdate(
{
"universe": "comics"
},
{
$set: {
"saga.$[outer].characters.$[inner].character": "lobezno",
"saga.$[outer].characters.$[inner].picture": "618035022354.png"
}
},
/*{
"saga.characters": 1
},*/
{
"arrayFilters": [
{
"outer.name": "x-men"
},
{
"inner.character": "wolverine"
}
],
"multi":false
}
)
I want to just update the first object where there is a match, and stop it.
For example, if I have an array of 100,000 elements and the object where the match is, is in the tenth position, he will update that record, but he will continue going through the entire array and this seems ineffective to me even though he already did the update.
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
How can I do it?
Update using arrayFilters conditions
I don't think it will find and update through loop, and It does not matter if collection have 100,000 sub documents, because here is nice explanation in $[<identifier>] and has mentioned:
The $[<identifier>] to define an identifier to update only those array elements that match the corresponding filter document in the arrayFilters
In the update document, use the $[<identifier>] filtered positional operator to define an identifier, which you then reference in the array filter documents. But make sure you cannot have an array filter document for an identifier if the identifier is not included in the update document.
Update using _id
Your point,
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
MongoDB will certainly use the _id index. Here is the nice answer on question MongoDB Update Query Performance, from this you will get an better idea on above point
Update using indexed fields
You can create index according to your query section of update command, Here MongoDB Indexes and Indexing Strategies has explained why index is important,
In your example, lets see with examples:
Example 1: If document have 2 sub documents and when you update and check with explain("executionStats"), assume it will take 1 second to update,
quick use Mongo Playground (this platform will not support update query)
Example 2: If document have 1000 sub documents and when you update and check with explain("executionStats"), might be it will take more then 1 second,
If provide index on fields (universe, saga.characters.character and saga.characters.picture) then definitely it will take less time then usual without index, main benefit of index it will direct point to indexed fields.
quick use Mongo Playground (this platform will not support update query)
Create Index for your fields
db.maxData.createIndex({
"universe": 1,
"saga.characters.character": 1,
"saga.characters.picture": 1
})
For more experiment use above 2 examples data with index and without index and check executionStats you will get more clarity.

Mongoose Updating an array in multiple documents by passing an array of filters to update query

I have multiple documents(3 documents in this example) in one collection that looks like this:
{
_id:123,
bizs:[{_id:'',name:'a'},{_id:'',name:'b'}]
},
{
_id:456,
bizs:[{_id:'',name:'e'},{_id:'',name:'f'}]
}
{
_id:789,
bizs:[{_id:'',name:'x'},{_id:'',name:'y'}]
}
Now, I want to update the bizs subdocument by matching with my array of ids.
That is to say, my array filter for update query is [123,789], which will match against the _id fields of each document.
I have tried using findByIdAndUpdate() but that doesn't allow an array for the update query
How can I update the 2 matching documents (like my example above) without having to put findByIdAndUpdate inside a forloop to match the array element with the _id?
You can not use findByIdAndUpdate when updating multiple documents, findByIdAndUpdate is from mongoose which is a wrapper to native MongoDB's findOneAndUpdate. When you pass a single string as a filter to findByIdAndUpdate like : Collection.findByIdAndUpdate({'5e179dac627ef7823643cd97'}, {}) - then mongoose will internally convert string to ObjectId() & form it as a filter like :_id : ObjectId('5e179dac627ef7823643cd97') to execute findOneAndUpdate. So it means you can only update one document at a time, So if you've multiple documents to be updated use update with option {multi : true} or updateMany.
Assume if you wanted to push a new object to bizs, this is how query looks like :
collection.updateMany({ _id: { $in: [123, 456] } }, {
$push: {
bizs: {
"_id": "",
"name": "new"
}
}
})
Note : Update operations doesn't return the documents in response rather they will return write result which has information about n docs matched & n docs modified.

Mongoose document update- selected objects in array - with $in operator

My schema is as below.
Customer: {
orders: [{
...
status: 'Pending'
...
}]
}
I have a list of order ids whose which are delivered to the customer. So I need to change the Status to closed. This is my simple requirement. I have my below code, which updates the status 'closed' only for the first order id in the OrderIdList.
orderIdList = ["54899c0cbdde6b281e9aaa22","54899c28bdde6b281e9aaa23","54899c2abdde6b281e9aaa24","54899c2cbdde6b281e9aaa25"]
Customer.update({
'orders._id': {
$in: orderIdList
}
}, {
'$set': {
'orders.$.status': 'Closed'
}
}, {
multi: true,
upsert: true
}, function(err, rows) {
console.log("error");
console.log(err);
console.log("rows updated");
console.log(rows);
});
Only one row is updated. for the orderId "54899c0cbdde6b281e9aaa22"
Why is it not updating for all the order Ids. Please suggest me the solution.
Thanks in advance.
which updates the status 'closed' only for the first order id in the
OrderIdList.
Yes, that is how the positional operator($) works. It updates only the first item in an array that has matched the condition in the query.
From the docs,
the positional $ operator acts as a placeholder for the first element
that matches the query document.
multi: true is applicable for parent documents and not for embedded documents. If this parameter is set to true, all the documents that have an order matching the required Id, will be updated. But the positional operator will update only the first sub document that matched the query in all these documents.
To update all the matching orders for all the matching document, currently it is not possible in a single mongo update query. You need to perform the logic in the application code.
One way of doing it in the application code:
Match all the customer documents which contain an order that we are
looking for.
For each customer document identify the orders that we need to
update.
Modify the order.
Perform an update based on the Customer document _id.
Sample Code:
var orderIdList = ['54899c0cbdde6b281e9aaa22'];
Customer.find({"orders._id":{$in:orderIdList }},function(err,resp){
resp.forEach(function(doc){
var orders = doc.orders;
orders.forEach(function(order){
if(orderIdList.indexOf(order._id.toString()) != -1){
order.status = 'closed';
Customer.update({"_id":doc._id},{$set:{"orders":orders}},function(err,aff,raw){
console.log("Updated: "+aff);
});}})})})

Add a new field point type filed to a collection with value of an existing field

I have a huge mongodb collection with 6 million records. I have two fields (latitude, longitude), and I would like to add a third field to the collection with the type of point (spatial). How to do this in command line or PHP?
It you'd like to add a new field (with the same value) to all documents in a collection, that can be done easily with an update() operation. Consider the following shell example:
db.collection.update(
{},
{ $set: { type: "spatial" }},
{ multi: true }
);
This would set the type field to "spatial" for all documents matching empty criteria {} (i.e. everything), and the multi option allows the update to modify multiple documents instead of just the first document matched (default behavior).
If you only wanted to set the type field where it doesn't already exist, you could tweak the criteria like so:
db.collection.update(
{ type: { $exists: false }},
{ $set: { type: "spatial" }},
{ multi: true }
);
Since you're storing geospatial data, you may want to have a look at MongoDB's 2dsphere indexes. This would allow you to store and index well-formed GeoJSON objects in your document. See this previous answer from a related question for more introductory information on the subject.

MongoDB: Capped collection findAndModify

I'm trying to atomically insert an empty document if the capped collection is empty or return the last naturally sorted document if not empty. Can I do this with findAndModify?
db.collection.findAndModify({
query: { _id: { $exists: true }},
sort: { $natural: -1 },
update: {},
upsert: true,
new: true
});
I would have expected this to either return the latest document (if the collection is non empty) or insert a new document if none exist, however, it inserts a blank document (without an _id) every single time it's called. Does findAndModify work with capped collections? I need the added document to have an _id.
Thanks.
-Scott
I'm trying to atomically insert an empty document if the capped
collection is empty or return the last naturally sorted document if
not empty. Can I do this with findAndModify?
There is a flaw in your query logic. A findAndModify() with:
query: { _id: { $exists: true }},
sort: { $natural: -1 },
update: {},
upsert: true,
new: true
... will:
do an update on the last inserted record with an _id set
OR
insert a new (empty) document if no existing document with an _id is found.
The update is going to replace your last inserted record with an empty one .. which presumably is not the intended outcome :).
You are seeing a completely empty document (no _id field) because capped collections have some exceptions to the behaviour for standard collections.
In particular:
there is no requirement for an _id field by default; you can have one generated on the server by including the autoIndexId:true option to createCollection()
there is no index on the _id field (note: you will want a unique index if using replication with a capped collection)
Also note that documents in a capped collection must not grow in size or the update will fail
Refer to the Capped Collection Usage & Restrictions on the wiki for more info.