Mongodb - Update at multiple places in the same document - mongodb

I have a document something like this :
myDoc : {
_id : a101
name : John,
batch : [{
_id : batch101,
value : physics
},{
_id : batch102,
value : chemistry
},{
_id : batch103,
value : maths
}]
}
I want to update the "value" to "computers" where the batch._id is either "batch101" or "batch102" (not batch103).
Please help! Thanks in advance.
-Manish :)

As noted in comments on the orignal question, the best solution here might be to work with the document on the client side. I will break this down into a few steps:
1) Query for the document. You mentioned you are working in node, so this will be represented in JSON. Lets call this variable x
2) Go through your document finding the elements you want to update, this might look like:
for(i = 0; i < x.batch.length; i++) {
if(x.batch[i]._id == 'batch101') {
//do something
}
}
(this code is not complete, obviously, but gives you an idea of what you want)
3) Now, using this changed document, update the old document in mongoDB.
This process should allow you to achieve your goal of updating certain elements in the batch list.

Related

Updating documents with nested arrays based on conditions

I'm new to MongoDB and have to work on a legacy project that I didn't create... and I'm struggling!
I need to reset some documents in one of my collections, based on a particular field value. I have had some success with this so far, but some of the data I need to update is within nested arrays in that document, and I can't work that part out.
Below is an example of one document in the collection:
{
"_id" : ObjectId("1234567890"),
"currentStatus" : "approved",
"itemsInstalled" : [
{
"installDate" : ISODate("2017-04-18T00:00:00.000Z"),
"_id" : ObjectId("1234567890"),
"status" : "approved"
},
{
"installDate" : ISODate("2017-04-18T00:00:00.000Z"),
"_id" : ObjectId("0987654321"),
"status" : "approved"
}
],
"__v" : 5005,
"approvalDate" : ISODate("2017-12-04T10:40:01.580Z"),
"approvedBy" : "automatic"
}
I need to update every document in my collection where the approvedBy field is set to automatic, and leave the others untouched.
For the documents I am updating, I need to remove the approvalDate and approvedBy fields completely, change the currentStatus field to action and change every item in the itemsInstalled array to pending. Everything else can stay as it is.
This is something I would persist with solving myself if I had more time.
Unfortunately, to my knowledge, you can't update multiple array elements. Your best bet is probably to use forEach. You can accomplish what you want with something like this:
db.your_collection.find({
approvedBy: "automatic"
}).forEach(function(doc) {
for(var i = 0; i < doc.itemsInstalled.length; i++) {
doc.itemsInstalled[i].status = "pending";
}
doc.currentStatus = "action";
delete doc.approvedBy;
delete doc.approvalDate;
db.your_collection.update({_id: doc._id}, doc);
});
Using forEach, you can update all of the array elements at once. The downside is that you will be performing multiple update queries, so you should be careful about doing this on particularly large collections or as part of your application logic. Ideally this should be a one-time use scenario.

MongoDB: Updating a sub properties inside a nested array document

I have a structure which looks like below:
course: { // course
id,
coursename,
sections: { // has an array of sections
section: {
id,
sectionname
cards: { // each section has an array of cards
card: {
id
cardname
}
}
}
}
}
Now, I am given the card ID. Suppose cardID is 301. I don't have details about the course or the section in which the card is stored. I only have the cardID. I want to update the cardname from "Test 1" to "Test 2".
Please provide some information about how to update the name of card given only the cardId. Also please provide information about how I can access a single card using only cardId.
If i run the following query:
db.course.find( {"sections.cards.id" : ObjectId("5859517447c4286d0f0639ee")},
{"sections.cards.$":1,_id:0})
The whole section object is returned instead of returning a single card. Please provide some infromation about how to access a single card using only cardId.
An example of document that i have:
{
"_id" : ObjectId("58595041cfdc46100f92a985"),
"modelType" : "Course",
"name" : "c1",
"sections" : [
{
"id" : ObjectId("5859504dcfdc46100f92a986"),
"name" : "s1",
"cards" : [
{
"id" : ObjectId("58595057cfdc46100f92a987"),
"name" : "card1",
},
{
"id" : ObjectId("5859506ccfdc46100f92a988"),
"name" : "card2"
}
]
}
]
}
Please provide information about how to update a card using only card Id.
Suppose I want to change the card's name. How do I achieve it?
I hope this answer is enough for your needs:
db.collection.aggregate([
{$unwind: "$sections"}, // unwind the array
{$unwind: "$sections.cards"}, // unwind the array
{$match: {"sections.cards.id": ObjectId("5859506ccfdc46100f92a988")}}, // filter
{$project: {_id: 0, id: "$sections.cards.id", name: "$sections.cards.name"}} // cut and clear any property that you don't need
])
which result in(I tested it using your sample):
{
"name" : "card2",
"id" : ObjectId("5859506ccfdc46100f92a988")
}
Explanation:
First off, you need aggregate(find any library like mongojs or mongoose and how to do this within their respective manual),
then unwind sections and cards inside it.
After both steps, you need to filter documents resulted from unwind operation according to your requirement. In this case: which card match your ID
Clear any properties that you don't need
Return the result
To Update properties inside a document:
It's impossible to update an element inside of an array without knowledge of its index, so first, you must find out its index/position inside. The simplest way I could think of is by using for (since yours is double tier-ed array, I think mapReduce method will be much more complicated):
// first, find the document, where xxx is the target card ID
db.collection.find({'sections.cards.id': ObjectId("xxx")}, function(err, reply){
// iterates through the documents
for(var z = 0; z < reply.length; z++){
// iterates through the `sections`
for(var y = 0; y < reply[z].sections.length; y++){
// iterates through the `cards`
for(var x = 0; x < reply[z].sections[y].cards.length; x++){
if(reply[z].sections[y].cards[x].id.toString() === "xxx")
{
// do what you want to do the card here
reply[z].sections[y].cards[x].name = "updated name";
}
}
}
// save the updated doc one-by-one,
db.collection.update({_id: reply._id}, reply);
}
});
The best solution
The best solution is: Don't have this kind of document in the first place. schema-free database approach DOESN'T mean that you can put everything inside a document. You had to consider the functionality and accessibility of data inside next time you design the document schema.
You can rationalize the documents according to your needs. In this case, you should split the course document, since it clear that card property should be an independent collection from course. If you worry that you'll need to join both collection, worry not. Since 3.2 mongodb introduced $lookup for this kind of application.
Not only it'll make your life easier, it'll make mongo queries run faster too.

How to check if a portion of an _id from one collection appears in another

I have a collection where the _id is of the form [message_code]-[language_code] and another where the _id is just [message_code]. What I'd like to do is find all documents from the first collection where the message_code portion of the _id does not appear in the second collection.
Example:
> db.colA.find({})
{ "_id" : "TRM1-EN" }
{ "_id" : "TRM1-ES" }
{ "_id" : "TRM2-EN" }
{ "_id" : "TRM2-ES" }
> db.colB.find({})
{ "_id" : "TRM1" }
I want a query that will return TRM2-EN and TRM-ES from colA. Of course in my live data, there are thousands of records in each collection.
According to this question which is trying to do something similar, we have to save the results from a query against colB and use it in an $in condition in a query against colA. In my case, I need to strip the -[language_code] portion before doing this comparison, but I can't find a way to do so.
If all else fails, I'll just create a new field in colA that contains only the message code, but is there a better way do it?
Edit:
Based on Michael's answer, I was able to come up with this solution:
var arr = db.colB.distinct("_id")
var regexs = arr.map(function(elm){
return new RegExp(elm);
})
var result = db.colA.find({_id : {$nin : regexs}}, {_id : true})
Edit:
Upon closer inspection, the above method doesn't work after all. In the end, I just had to add the new field.
Disclaimer: This is a little hack it may not end well.
Get distinct _id using collection.distinct method.
Build a regular expression array using Array.prototype.map()
var arr = db.colB.distinct('_id');
arr.map(function(elm, inx, tab) {
tab[inx] = new RegExp(elm);
});
db.colA.find({ '_id': { '$nin': arr }})
I'd add a new field to colA since you can index it and if you have hundreds of thousands of documents in each collection splitting the strings will be painfully slow.
But if you don't want to do that you could make use of the aggregation framework's $substr operator to extract the [message-code] then do a $match on the result.

Search full document in mongodb for a match

Is there a way to match a value with every array and sub document inside the document in mongodb collection and return the document
{
"_id" : "2000001956",
"trimline1" : "abc",
"trimline2" : "xyz",
"subtitle" : "www",
"image" : {
"large" : 0,
"small" : 0,
"tiled" : 0,
"cropped" : false
},
"Kytrr" : {
"count" : 0,
"assigned" : 0
}
}
for eg if in the above document I am searching for xyz or "ab" or "xy" or "z" or "0" this document should be returned.
I actually have to achieve this at the back end using C# driver but a mongo query would also help greatly.
Please advice.
Thanks
You could probably do this using '$where'
db.mycollection({$where:"JSON.stringify(this).indexOf('xyz')!=-1"})
I'm converting the whole record to a big string and then searching to see if your element is in the resulting string. Probably won't work if your xyz is in the fieldnames!
You can make it iterate through the fields to make a big string and then search it though.
This isn't the most elegant way and will involve a full tablescan. It will be faster if you look through the individual fields!
While Malcolm's answer above would work, when your collection gets large or you have high traffic, you'll see this fall over pretty quickly. This is because of 2 things. First, dropping down to javascript is a big deal and second, this will always be a full table scan because $where can't use an index.
MongoDB 2.6 introduced text indexing which is on by default (it was in beta in 2.4). With it, you can have a full text index on all the fields in the document. The documentation gives the following example where a text index is created for every field and names the index "TextIndex".
db.collection.ensureIndex(
{ "$**": "text" },
{ name: "TextIndex" }
)

MongoDB: Doing $inc on multiple keys

I need help incrementing value of all keys in participants without having to know name of the keys inside of it.
> db.conversations.findOne()
{
"_id" : ObjectId("4faf74b238ba278704000000"),
"participants" : {
"4f81eab338ba27c011000001" : NumberLong(2),
"4f78497938ba27bf11000002" : NumberLong(2)
}
}
I've tried with something like
$mongodb->conversations->update(array('_id' => new \MongoId($objectId)), array('$inc' => array('participants' => 1)));
to no avail...
You need to redesign your schema. It is never a good idea to have "random key names". Even though MongoDB is schemaless, it still means you need to have defined key names. You should change your schema to:
{
"_id" : ObjectId("4faf74b238ba278704000000"),
"participants" : [
{ _id: "4f81eab338ba27c011000001", count: NumberLong(2) },
{ _id: "4f78497938ba27bf11000002", count: NumberLong(2) }
]
}
Sadly, even with that, you can't update all embedded counts in one command. There is currently an open feature request for that: https://jira.mongodb.org/browse/SERVER-1243
In order to still update everything, you should:
query the document
update all the counts on the client side
store the document again
In order to prevent race conditions with that, have a look at "Compare and Swap" and following paragraphs.
It is not possible to update all nested elements in one single move in current version of MongoDB. So I can advice to use "foreach {}".
Read realted topic: How to Update Multiple Array Elements in mongodb
I hope this feature will be implemented in next version.