How do you update objects in a document's array (nested updating) - mongodb

Assume we have the following collection, which I have few questions about:
{
"_id" : ObjectId("4faaba123412d654fe83hg876"),
"user_id" : 123456,
"total" : 100,
"items" : [
{
"item_name" : "my_item_one",
"price" : 20
},
{
"item_name" : "my_item_two",
"price" : 50
},
{
"item_name" : "my_item_three",
"price" : 30
}
]
}
I want to increase the price for "item_name":"my_item_two" and if it doesn't exists, it should be appended to the "items" array.
How can I update two fields at the same time? For example, increase the price for "my_item_three" and at the same time increase the "total" (with the same value).
I prefer to do this on the MongoDB side, otherwise I have to load the document in client-side (Python) and construct the updated document and replace it with the existing one in MongoDB.
This is what I have tried and works fine if the object exists:
db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})
However, if the key doesn't exist, it does nothing.
Also, it only updates the nested object. There is no way with this command to update the "total" field as well.

For question #1, let's break it into two parts. First, increment any document that has "items.item_name" equal to "my_item_two". For this you'll have to use the positional "$" operator. Something like:
db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } ,
{$inc : {"items.$.price" : 1} } ,
false ,
true);
Note that this will only increment the first matched subdocument in any array (so if you have another document in the array with "item_name" equal to "my_item_two", it won't get incremented). But this might be what you want.
The second part is trickier. We can push a new item to an array without a "my_item_two" as follows:
db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} ,
{$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
false ,
true);
For your question #2, the answer is easier. To increment the total and the price of item_three in any document that contains "my_item_three," you can use the $inc operator on multiple fields at the same time. Something like:
db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
{$inc : {total : 1 , "items.$.price" : 1}} ,
false ,
true);

There is no way to do this in single query. You have to search the document in first query:
If document exists:
db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } ,
{$inc : {"items.$.price" : 1} } ,
false ,
true);
Else
db.bar.update( {user_id : 123456 } ,
{$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
false ,
true);
No need to add condition {$ne : "my_item_two" }.
Also in multithreaded enviourment you have to be careful that only one thread can execute the second (insert case, if document did not found) at a time, otherwise duplicate embed documents will be inserted.

We can use $set operator to update the nested array inside object filed update the value
db.getCollection('geolocations').update(
{
"_id" : ObjectId("5bd3013ac714ea4959f80115"),
"geolocation.country" : "United States of America"
},
{ $set:
{
"geolocation.$.country" : "USA"
}
},
false,
true
);

One way to ensure there are no duplicates of the "item_name" fields would be the do the same actions as given in the answer https://stackoverflow.com/a/10523963 for Question #1 but in reverse order!
Push into the document if "items.item_name":{"$ne":"my_name"} - the update filter MUST contain some uniquely indexed field! And upsert is false.
Increment the document if "items.item_name":"my_name".
The first update should be atomic, thus it doesn't do anything if the array already contains the element with the item_name "my_name".
By the time the second update happens, there must be an array element with the "item_name"="my_name"

Related

Mongo Query to retrieve documents having redundant values within an Array

In my mongo db collection , I have few documents as below :
Each Document has a 'Bricks' array .
In the Bricks Array , some documents have same 'brick_id' ( see - Document 1)
Some documents have unique 'brick_id' ( see - Document 2)
Now, My Question is that I need to write a Mongo Query to retrieve all the Documents which contains redundant 'brick_id' in the "bricks" array .
Means , I need to get Document 1 as output
Please help . I'm new to Mongo Db
**Document 1** :
{
"_id" : 123 ,
"page" : "new_page_1" ,
"bricks" : [
{
"brick_id" : 160,
"name" : "dev-br"
},
{
"brick_id" : 160,
"name" : "dev-br2"
}
]
}
**Document 2**
{
"_id" : 150 ,
"page" : "new_page_1" ,
"bricks" : [
{
"brick_id" : 200,
"name" : "dev-br"
},
{
"brick_id" : 201,
"name" : "dev-br2"
}
]
}
You might use the $setUnion operator to join "$bricks.brick_id" with an empty array, and compare its $size with that of the unmodified "$bricks.brick_id" array. If the sizes are not equal, there was at least one duplicate.

Meteor/Mongo nested arrays update

I'm new to meteor/mongo/js as a stack and I'm getting lost in JSON arrays and referencing them. Based another SO answer ( and the docs) I think I am close...
A document in the Orders collection, the document has nested arrays.
Order -> orderLines -> lineItems:
Sample doc:
{
"_id" : "27tGpRtMWYqPpFkDN",
"orderLines" : [
{
"lineId" : 1,
"name" : "Cheese & Pickle",
"instructions" : "",
"lineItems" : [
{
"name" : "Cheddar Cheese",
"quantity" : 1
},
{
"name" : "Branston Pickle",
"quantity" : 1
},
{
"name" : "Focaccia Roll",
"quantity" : 1
}
]
}
]
}
What I'm trying to do from the meteor/mongo shell:
Add "instructions" of "foo" to orderLines where lineId = 1
Put a new item on lineItems array
This appears to hang...
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN","orderLines.lineId":"1", {$set: {"orderLines.$.instructions":"foo"}})
...
This doesn't like the identifier in the query
meteor:PRIMARY> db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":"1"}, {$push:{"orderLines.$.lineItems":" { "name" : "butter", "quantity" : 1}"}});
2015-10-27T16:09:54.489+0100 SyntaxError: Unexpected identifier
Thanks all for your comments... but I found out some answers, posted for reference
Item 1 - using $set on a value within an array
This was failing due to two typos, one missing closing } at the end of the query, second was quoting the value "1" for itemId in the query.
This works:
db.orders.update({_id:"27tGpRtMWYqPpFkDN", orderLines.lineId":1},
{$set: {"orderLines.$.instructions":"foo"}})
I also realised that when I said "It appears to hang" it is the cli waiting for a valid statement, so hints at a missing } or )!
Item 2 - using $push to add data to an array - 2 levels nested
This was failing due to quoting around the array data
db.orders.update({_id:"27tGpRtMWYqPpFkDN", "orderLines.lineId":1 },
{$push:{"orderLines.$.lineItems": { "name" : "butter", "quantity" : 1} }})
Nested Arrays: can use $ positional operator
What I want to do next is use $set on an item in the second level array, and this would require using the $ positional operator twice:
db.orders.update({"orderLines.lineId":1, lineItems.name:"Cheddar Cheese"},
{$set: {"orderLines.$.lineItems.$.quantity": 2}})
This throws an error:
Too many positional (i.e. '$') elements found in path
There is an open MongoDB enhancement request for this but it's been open since 2010
$push adds a new element to an array. You're merely trying to set the value of a particular key in an array element.
Try:
db.orders.update({ _id: "27tGpRtMWYqPpFkDN", "orderLines.lineId": 1},
{ $set: { "orderlines.$.instructions": "foo" }})
docs

MongoDB update a subdocument attribute

Let me start by saying I'm sorry if this has been answered, but I can't get other questions on this site to fit my needs and, more importantly, work.
I have the below example document, with a subdocument of 'address':
{
"_id" : ObjectId("....")
,"addresses" :
[{
"start" : ISODate("1973-07-10T00:11:51.111Z")
,"value" : "123 long road"
}]
}
What I need to do is to close the existing address record with an end attribute, and add a new line for the new address with a new start and value attribute. Eventually, I'll need to do this again so the code needs to update the subdocument record where end does not exist.
The below code does not work, but it's about as far as I can get:
db.sites.update(
{"_id" : ObjectId("....")
, "addresses.end" : {"$exists" : false}}
,{"$set": {"addresses.$.end" : "fdsa"}});
This gives the error:
Cannot apply the positional operator without a corresponding query field containing an array.
Can anyone point me in the right direction?
Juste replace in your query "addresses.end" : {"$exists" : false} with:
addresses: {$elemMatch: {end: {$exists: false}}}
Your address field is poorly defined. you need make it a subdocument or an array of subdocuments. ie {
"_id" : ObjectId("....")
,"addresses" :
[
{
"start" : ISODate("1973-07-10T00:11:51.111Z")
,"value" : "123 long road"
}
]
}
your query should then work!
I think that the Query should be more specific
**Updated **
db.sites.update ( {"_id" : ObjectId("...."), addresses: { "$elemMatch" : { end:{$exists : false}} } }, {"$set": {"addresses.$.end" : "fdsa"}});
db.sites.find()
results:
{
"_id" : ObjectId("53df93da560b7815e1237934"),
"addresses" : [
{
"start" : ISODate("1973-07-10T00:11:51.111Z"),
"value" : "123 long road",
"end" : "fdsa"
}
]
}
but you can update only one
Take a look http://docs.mongodb.org/manual/reference/operator/projection/positional/#proj.S
You can t update more element in Array https://jira.mongodb.org/browse/SERVER-1243

MongoDB Why this error : can't append to array using string field name: comments

I have a DB structure like below:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"content" : "xxx"
}
]
}
I update a new subdocument in the comments feild. It is OK.
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
after that the DB structure:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"comments" : [
{
"id" : 3,
"content" : "xxx"
}
],
"content" : "xxx"
}
]
}
But when I update a new subdocument in the comment field that _id is 3, There is a error:
db.test.update(
{"_id" : 1, "comments.comments.id" : 3},
{$push : {"comments.comments.$.comments" : {id : 4, content:"xxx"}}}
)
error message:
can't append to array using string field name: comments
Well, it makes total sense if you think about it. MongoDb has the advantage and the disadvantage of solving magically certain things.
When you query the database for a specific regular field like this:
{ field : "value" }
The query {field:"value"} makes total sense, it wouldn't in case value is part of an array but Mongo solves it for you, so in case the structure is:
{ field : ["value", "anothervalue"] }
Mongo iterates through all of them and matches "value" into the field and you don't have to think about it. It works perfectly.. at only one level, because it's impossible to guess what you want to do if you have multiple levels
In your case the first query works because it's the case in this example:
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
Matches _id in the first level, and comments._id at the second level, it gets an array as a result but Mongo is able to solve it.
But in the second case, think what you need, let's isolate the where clause:
{"_id" : 1, "comments.comments.id" : 3},
"Give me from the main collection records with _id:1" (one doc)
"And comments which comments inside have and id=3" (array * array)
The first level is solved easily, comments.id, the second is not possible due comments returns an array, but one more level is an array of arrays and Mongo gets an array of arrays as a result and it's not possible to push a document into all the records of the array.
The solution is to narrow your where clause to obtain an unique document in comments (could be the first one) but it's not a good solution because you never know what is the position of the document you're looking for, using the shell I think the only option to be accurate is to do it in two steps. Check this query that works (not the solution anyway) but "solves" the multiple array part fixing it to the first record:
db.test.update(
{"_id" : 1, "comments.0.comments._id" : 3},
{$push : {"comments.0.comments.$.comments" : {id : 4, content:"xxx"}}}
)

Can't append to array using string field name [$] when performing update on array fields

rowsI am attempting to perform a mongodb update on each field in an array of records.
An example schema is below:
{
"_id" : ObjectId("508710f16dc636ec07000022"),
"summary" : "",
"uid" : "ABCDEF",
"username" : "bigcheese",
"name" : "Name of this document",
"status_id" : 0,
"rows" : [
{
"score" : 12,
"status_id" : 0,
"uid" : 1
},
{
"score" : 51,
"status_id" : 0,
"uid" : 2
}
]
}
So far I have been able to perform single updates like this:
db.mycollection.update({"uid":"ABCDEF","rows.uid":1}, {$set:{"rows.$.status_id":1}},false,false)
However, I am struggling as to how to perform an update that will update all array records to a status_id of 1 (for instance).
Below is how I imagine it should work:
db.mycollection.update({"uid":"ABCDEF"}, {$set:{"rows.$.status_id":1}},false,true)
However I get the error:
can't append to array using string field name [$]
I have tried for quite a while with no luck. Any pointers?
You can't do the sort of 'wildcard' update of array elements that you're looking for. I think the best you can do is simultaneously set each element's status_id value like this:
db.mycollection.update(
{"uid":"ABCDEF"},
{$set:{
"rows.0.status_id":1,
"rows.1.status_id":1
}}, false, true);