Update an element in sub of sub array in mongodb - mongodb

I have this data in Mongo:
{
"_id" : ObjectId("505fd43fdbed3dd93f0ae088"),
"categoryName" : "Cat 1",
"services" : [
{
"serviceName" : "Svc 1",
"input" : [
{ "quantity" : 10, "note" : "quantity = 10" },
{ "quantity" : 20, "note" : "quantity = 20" }
]
},
{
"serviceName" : "Svc 2",
"input" : [
{ "quantity" : 30, "note" : "quantity = 30" },
{ "quantity" : 40, "note" : "quantity = 40" }
]
}
]
}
Now I want to update a quantity for "Svc 1":
{ "quantity" : 10, "note" : "quantity = 10" }
Like:
{"quantity": 100, "note": "changed to 100"}
How can I do with Mongo?`
As I know, operational operator only supports for the first array, someone advised to use index of an element of the sub sub array, but the problem is that how can know that index at run time? (I'm using native C# driver of MongoDB)
Thanks in advance for your helps!
Johnny

Since you have an array within an array, there isn't any easy way to reference the nested subarray unless you know the position in the array you want to update.
So, for example, you could update the first input for 'Svc 1' with the C# equivalent of:
db.services.update(
// Criteria
{
'_id' : ObjectId("505fd43fdbed3dd93f0ae088"),
'services.serviceName' : 'Svc 1'
},
// Updates
{
$set : {
'services.$.input.0.quantity' : 100,
'services.$.input.0.note' : 'Quantity updated to 100'
}
}
)
If you don't know the position for the nested input array, you will have to fetch the matching services, iterate the input array in your application code, then $set the updated array.
Alternatively, you could modify your nested array to use an embedded document instead, eg:
{
"categoryName" : "Cat 1",
"services" : [
{
"serviceName" : "Svc 1",
"input1" : { "quantity" : 10, "note" : "quantity = 10" },
"input2" : { "quantity" : 20, "note" : "quantity = 20" }
},
]
}
Which you could then update by name, eg input1:
db.services.update(
// Criteria
{
'_id' : ObjectId("5063e80a275c457549de2362"),
'services.serviceName' : 'Svc 1'
},
// Updates
{
$set : {
'services.$.input1.quantity' : 100,
'services.$.input1.note' : 'Quantity updated to 100'
}
}
)

Since you don't know the position of the value wanted to update, first insert a new value with updated information and then remove the value wanted to update.
db.services.update(
{
'_id' : ObjectId("505fd43fdbed3dd93f0ae088"),
'services.serviceName' : 'Svc 1'
},
{
{ $addToSet: { 'services.$.input' : "new sub-Doc" }
}
)
And then remove when insert is success
db.services.update(
{
'_id' : ObjectId("505fd43fdbed3dd93f0ae088"),
'services.serviceName' : 'Svc 1'
},
{
{ $pull: { 'services.$.input' : { "quantity" : 10, "note" : "quantity = 10" } }
}
)
This is useful when index is not known and document should have sub-documents having same key like "input" in post Update an element in sub of sub array in mongodb

Related

Mongodb update and delete operations in a single query

I have documents in which I would like to update the hostUser with one of the members of the document,also have to delete the record from the member document and add the chips of the deleted member in the club chips.
Here is the sample document.
{
"_id" : "1002",
"hostUser" : "1111111111",
"clubChips" : 10000,
"requests" : {},
"profile" : {
"clubname" : "AAAAA",
"image" : "0"
},
"tables" : [
"SJCA3S0Wm"
],
"isDeleted" : false,
"members" : {
"1111111111" : {
"chips" : 0,
"id" : "1111111111"
},
"2222222222" : {
"chips" : 0,
"id" : "2222222222"
}
}
}
This is what I have tried.
db.getCollection('test').updateMany({"hostUser":"1111111111"},
{"$set":{"hostUser":"2222222222"},"$unset":{"members.1111111111":""}})
This is how you would handle unset and set in a single call to updateMany. Can you please clarify what you meant by "check if the values exist in the member field"?
db.getCollection('test').updateMany(
{"hostUser":"1111111111"},
{
'$set': {"hostUser":"2222222222"} ,
'$unset': {"members.1111111111":""}
}
)

how to update property in nested mongo document

I want to update a particular property in a nested mongo document
{
"_id" : ObjectId("55af76e60b0e4b318ba822ec"),
"make" : "MERCEDES-BENZ",
"model" : "E-CLASS",
"variant" : "E 250 CDI CLASSIC",
"fuel" : "Diesel",
"cc" : 2143,
"seatingCapacity" : 5,
"variant_+_fuel" : "E 250 CDI CLASSIC (Diesel)",
"make_+_model_+_variant_+_fuel" : "MERCEDES-BENZ E-CLASS E 250 CDI CLASSIC (Diesel)",
"dropdown_display" : "E-CLASS E 250 CDI CLASSIC (Diesel)",
"vehicleSegment" : "HIGH END CARS",
"abc" : {
"variantId" : 1000815,
"makeId" : 1000016,
"modelId" : 1000556,
"fuelId" : 2,
"segmentId" : 1000002,
"price" : 4020000
},
"def" : {
"bodyType" : 1,
"makeId" : 87,
"modelId" : 21584,
"fuel" : "DIESEL",
"vehicleSegmentType" : "E2"
},
"isActive" : false
}
This is my document. If I want to add or update a value for key "nonPreferred" inside "abc", how do I go about it?
I tried it with this query:
db.FourWheelerMaster.update(
{ "abc.modelId": 1000556 },
{
$Set: {
"abc": {
"nonPreferred": ["Mumbai", "Pune"]
}
}
},
{multi:true}
)
but it updates the whole "abc" structure, removed all key:values inside it and kept only newly inserted key values like below
"abc" : {
"nonPreferred" : [
"Mumbai",
"Pune"
]
},
Can anyone tell me how to update only particular property inside it and not all the complete key?
Instead of using the $set operator, you need to push that array using the $push operator together with the $each modifier to append each element of the value separately as follows:
db.FourWheelerMaster.update(
{ "abc.modelId": 1000556 },
{
"$push": {
"abc.nonPreferred": {
"$each": ["Mumbai", "Pune"]
}
}
},
{ "multi": true }
)

mongodb mapreduce exclude nested field

I am mongodb newbie! I am trying to process some tweeter data. my goal is to group users on each time interval (for simplicity, daily interval) and count his unique hashtags on that day. My idea to build new DB which is only contains user, date and hashtags. Here is data format:
> db.sampledDB.findOne()
{
"_id" : NumberLong("2334234"),
"replyid" : NumberLong(-1),
"userid" : NumberLong(21313),
"replyuserid" : NumberLong(-1),
"createdAt" : ISODate("2013-07-02T22:35:06Z"),
"tweettext" : "RT #BBCBreaking: Plane carrying Bolivia President Morales is diverted to Austria on suspicion US fugitive #Snowden is on board - Bolivian m…",
"screenName" : "x83",
"name" : "david x",
"retweetCount" : NumberLong(0),
"retweet_id" : NumberLong("12313223"),
"retweet_userid" : NumberLong(123123123),
"source" : "Twitter for Windows Phone",
"hashtags" : [
{
"start" : 106,
"end" : 114,
"text" : "Snowden"
}
],
"mentions" : [
{
"start" : 3,
"end" : 15,
"id" : NumberLong(876678),
"screenName" : "BBCBreaking",
"name" : "BBC Breaking News"
}
],
"media" : [ ]
}
I use mapReduce like this:
MAP:
map = function(){
//format date to year/month/day
var format = this.createdAt.getFullYear() + '/' + (this.createdAt.getMonth()+1) + '/' + this.createdAt.getDate();
var key = {userid:this.userid, date:format};
emit(key,{hashtags:this.hashtags}); }
REDUCE:
reduce = function(key,values){
var result = {a:[]};
for (var idx=0;idx<values.length;idx++){
result.a.push(values[idx].hashtag);
}
return result};
it results to:
{
"_id" : {
"userid" : NumberLong(7686787),
"date" : "2013/7/5"
},
"value" : {
"hashtag" : [
{
"start" : 24,
"end" : 44,
"text" : "SıkSöylenenYalanlar"
},
{
"start" : 45,
"end" : 60,
"text" : "ZimmermanTrial"
},
{
"start" : 61,
"end" : 84,
"text" : "ZaynMalikYouArePerfect"
},
{
"start" : 85,
"end" : 99,
"text" : "TrayvonMartin"
},
{
"start" : 100,
"end" : 110,
"text" : "Wimbledon"
},
{
"start" : 111,
"end" : 118,
"text" : "Футбол"
},
{
"start" : 119,
"end" : 127,
"text" : "Snowden"
},
{
"start" : 128,
"end" : 138,
"text" : "TFFistifa"
}
]
}
},
{
"_id" : {
"userid" : NumberLong(45666),
"date" : "2013/7/5"
},
"value" : {
"hashtag" : [
{
"start" : 24,
"end" : 44,
"text" : "SıkSöylenenYalanlar"
},
{
"start" : 45,
"end" : 60,
"text" : "ZimmermanTrial"
},
{
"start" : 61,
"end" : 84,
"text" : "ZaynMalikYouArePerfect"
},
{
"start" : 85,
"end" : 99,
"text" : "TrayvonMartin"
},
{
"start" : 100,
"end" : 110,
"text" : "Wimbledon"
},
{
"start" : 111,
"end" : 118,
"text" : "Футбол"
},
{
"start" : 119,
"end" : 127,
"text" : "Snowden"
},
{
"start" : 128,
"end" : 138,
"text" : "TFFistifa"
}
]
}
},
But I just want to keep the text element of hashtags. I tried to change the reducer to values[idx].hashtag.text or values[idx].hashtag["text"] which did not help.
UPDATE:
I suspect my problem is similar to MapReduce problem, but I dont know to fix mine
You might also consider using the aggregation framework which can produce the results shown below. The pipeline would look similar to this:
{$project: {
userid: "$userid",
"hashtags": "$hashtags.text",
date: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt"},
day: {$dayOfMonth: "$createdAt"} }}},
{$unwind: "$hashtags" },
{ $group: { _id : {
date: "$date",
userid: "$userid"},
hashtags: { $addToSet:"$hashtags" }
}} )
Might produce a result like:
[
{
"_id" : {
"date" : {
"year" : 2013,
"month" : 8,
"day" : 4
},
"userid" : NumberLong(362337301)
},
"hashtags" : [
"tagger",
"stackoverflow",
"twitter"
]
}, /* more */
A brief explanation of the aggregation framework pipeline:
Using $project, grab only the fields that will matter through the rest of the pipeline. Before doing this, if there was a specific date or range that would have been desired, using $match would have been a great step to filter some of the results efficiently). Note that the createdAt field has been split into the respective pieces so that the time of day will later be ignored when grouping. After the projection has occurred, the new field will be called date in the example. Here, the hash tags have been simplified to be only the text property, and the name reused as "hashtags".
Next, as "hashtags" is an array at this point (would look like: ['tagger', 'stackoverflow', 'twitter'] for example, the pipeline creates a new document for each element in the "hashtag" array.
Finally, the grouping pipeline operator uses the combination of userid and date as a grouper, and adds all unique hash tags to a field called "hashtags".
As an alternative to splitting the date, you can also just treat the createdAt field as a string, and remove the time by using this in the pipeline:
date: {$substr: ["$createdAt",0, 10] }
It would produce something like:
2013-07-02
Edit
As you've pointed out, there is currently a 16MB limit in the document that is output from an Aggregation. While this is scheduled to be changed in the 2.6 version of MongoDB, you may be able to get a MapReduce as well that work. It's a bit messier given a MapReduce wasn't necessarily intended for this type of work, so the results may not be necessarily what you want.
map = function() {
var format = this.createdAt.getFullYear() + '/'
+ (this.createdAt.getMonth()+1) + '/' + this.createdAt.getDate();
var key = {userid:this.userid, date:format};
var hashtags = this.hashtags || [];
for(var i=0, l=hashtags.length; i < l; i++) {
emit(key, hashtags[i].text);
}
};
reduce = function(key, values){
values = values || [];
var tag;
var tags = {};
for(var i=0, l=values.length; i<l ; i++) {
tag = values[i] || "";
if (tag.length > 0) {
tags[tag] = "";
}
};
values = [];
for(var t in tags) {
values.push(t);
}
return values.join(',');
};
Instead of emitting the array, it emits each hash tag in the map. The reduce eliminates duplicates using a simple associative array and then returns a joined string with all of the hash tags. MongoDB does not support returning an array of results via the reduce function (the idea is that a reduce should be providing one result, not an array of results).
Results:
{
"_id" : {
"userid" : NumberLong(262317302),
"date" : "2013/7/2"
},
"value" : "Wisconsin,Space,Cheese"
}
If you don't need to do this work frequently, you could also just write a shell script in the MongoDB console that extracts the hash tags into a new collection. Then, just run it when you need to.
here is how I managed to produce the same result as the answer above. just for presenting another solution.
map = function(){
var day = this.createdAt.getFullYear() + '/' + (this.createdAt.getMonth()+1) + '/' + this.createdAt.getDate();
var key = {userid:this.userid, date:day};
var values = {hashtags:[]};
for (var idx=0;idx<this.hashtags.length;idx++){
values.hashtags.push(this.hashtags[idx].text);
}
emit(key,values);
};
reduce = function(key,values){
hashtag_list = {hashtags: []} ;
for(var i in values) {
hashtag_list.hashtags= values[i].hashtags.concat(hashtag_list.hashtags);
}
return hashtag_list;
}
Try:
values[idx].text
hashtag is not a property of the object, but text is.

How to update particular array element in MongoDB

I am newbie in MongoDB. I have stored data inside mongoDB in below format
"_id" : ObjectId("51d5725c7be2c20819ac8a22"),
"chrom" : "chr22",
"pos" : 17060409,
"information" : [
{
"name" : "Category",
"value" : "3"
},
{
"name" : "INDEL",
"value" : "INDEL"
},
{
"name" : "DP",
"value" : "31"
},
{
"name" : "FORMAT",
"value" : "GT:PL:GQ"
},
{
"name" : "PV4",
"value" : "1,0.21,0.00096,1"
}
],
"sampleID" : "Job1373964150558382243283"
I want to update the value to 11 which has the name as Category.
I have tried below query:
db.VariantEntries.update({$and:[ { "pos" : 117199533} , { "sampleID" : "Job1373964150558382243283"},{"information.name":"Category"}]},{$set:{'information.value':'11'}})
but Mongo replies
can't append to array using string field name [value]
How one can form a query which will update the particular value?
You can use the $ positional operator to identify the first array element to match the query in the update like this:
db.VariantEntries.update({
"pos": 17060409,
"sampleID": "Job1373964150558382243283",
"information.name":"Category"
},{
$set:{'information.$.value':'11'}
})
In MongoDB you can't adress array values this way. So you should change your schema design to:
"information" : {
'category' : 3,
'INDEL' : INDEL
...
}
Then you can adress the single fields in your query:
db.VariantEntries.update(
{
{"pos" : 117199533} ,
{"sampleID" : "Job1373964150558382243283"},
{"information.category":3}
},
{
$set:{'information.category':'11'}
}
)

Updating an array of objects with a new key in mongoDB

Similar to this question
Barrowing the data set, I have something similar to this:
{
'user_id':'{1231mjnD-32JIjn-3213}',
'name':'John',
'campaigns':
[
{
'campaign_id':3221,
'start_date':'12-01-2012',
},
{
'campaign_id':3222,
'start_date':'13-01-2012',
}
]
}
And I want to add a new key in the campaigns like so:
{
'user_id':'{1231mjnD-32JIjn-3213}',
'name':'John',
'campaigns':
[
{
'campaign_id':3221,
'start_date':'12-01-2012',
'worker_id': '00000'
},
{
'campaign_id':3222,
'start_date':'13-01-2012',
'worker_id': '00000'
}
]
}
How to insert/update a new key into an array of objects?
I want to add a new key into every object inside the array with a default value of 00000.
I have tried:
db.test.update({}, {$set: {'campaigns.worker_id': 00000}}, true, true)
db.test.update({}, {$set: {campaigns: {worker_id': 00000}}}, true, true)
Any suggestions?
I'm supposing that this operation will occur once, so you can use a script to handle it:
var docs = db.test.find();
for(var i in docs) {
var document = docs[i];
for(var j in document.campaigns) {
var campaign = document.campaigns[j];
campaign.worker_id = '00000';
}
db.test.save(document);
}
The script will iterate over all documents in your collection then over all campaigns in each document, setting the *worker_id* property.
At the end, each document is persisted.
db.test.update({}, {$set: {'campaigns.0.worker_id': 00000}}, true, true
this will update 0 element.
if you want to add a new key into every object inside the array you should use:
$unwind
example:
{
title : "this is my title" ,
author : "bob" ,
posted : new Date() ,
pageViews : 5 ,
tags : [ "fun" , "good" , "fun" ] ,
comments : [
{ author :"joe" , text : "this is cool" } ,
{ author :"sam" , text : "this is bad" }
],
other : { foo : 5 }
}
unwinding tags
db.article.aggregate(
{ $project : {
author : 1 ,
title : 1 ,
tags : 1
}},
{ $unwind : "$tags" }
);
result:
{
"result" : [
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "fun"
},
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "good"
},
{
"_id" : ObjectId("4e6e4ef557b77501a49233f6"),
"title" : "this is my title",
"author" : "bob",
"tags" : "fun"
}
],
"OK" : 1
}
After you could write simple updaiting query.