How to update nested object in mongodb - mongodb

I am rather new to mongodb and self learning. Since recently I am a bit stuck on this one. I would like to update a nested document. I worked around it by retrieving the full document and updating the entire thing, but I need a solution where I can update in a single shot. I made the following simplified example:
db.test.insert({
'name': 'someSchool',
'classes': [
{
'name': 'someClass',
'students': [
{
'name': 'Jack',
'coolness' : 7
}
]
}
]
});
Here the arrays only have one entry for simplicity, you understand we normally have more...
db.test.find({
'name' : 'someSchool',
'classes' : {
$elemMatch: {
'name': 'someClass',
'students.name' : 'Jack'
}
}
});
This find works! Thats great, so now I want to use it in an update, which is where Im stuck. Something like this??
db.test.update({
'name' : 'someSchool',
'classes' : {
$elemMatch: {
'name': 'someClass',
'students.name' : 'Jack'
}
}
}, {
'$set' : { 'classes.students.coolness' : 9}
});
So this doesnt work and now I am stuck at it :) Hopefully someone here can help.
Side note: I am using mongoose. If you guys tell me its better to change the schema to make it easier somehow, I can.
Update since marked as duplicate: In the referenced question, there is only 1 level of nesting and the problem gets solved by using dot notation. Here there are 2 levels. I accepted teh answer which explains it is not supported and which gives a correct workaround.

As mentioned here it's not possible right now to update two level nested arrays.
I would suggest you to change your schema in order to replace one level array nesting with object. So your document would look like this:
{
"name" : "someSchool",
"classes" : {
"someClass" : {
"students" : [
{
"name" : "Jack",
"coolness" : 7
}
]
}
}
}
Then you can use positional operator to update specific array element. But be aware that positional operator updates only the first element in array that matches the query.
Update query would look like this:
db.test.update({
'classes.someClass.students' : {
$elemMatch: {
'name' : 'Jack'
}
}
}, {
'$set' : {
'classes.someClass.students.$.coolness' : 9
}
});

Related

get data from nested documents in mongodb / find is not working [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 4 years ago.
i have data in my collection called users like this
{
"_id" : ObjectId("5aeffdb80e006205640052ff"),
"name" : "KOTA AMBON",
"email" : "ambon#gmail.com",
"password" : "$2y$10$VVaGAVniUbgBnDw6x5yZ0uAvJDGa5ekmVPJk/1Lubz5yKzZ75opo2",
"updated_at" : ISODate("2018-05-07T07:18:16Z"),
"created_at" : ISODate("2018-05-07T07:18:16Z"),
"dinas" : [
{
"id" : "5aeffdb80e006205640052ff_5af0101d0e00620564005307",
"nama_dinas" : "dinas perikanan",
"deskripsi_dinas" : "dinas untuk ikan",
"keyword" : "ikan"
},
{
"id" : "5aeffdb80e006205640052ff_5af010390e00620564005308",
"nama_dinas" : "dinas perhubungan",
"deskripsi_dinas" : "dinas untuk hubungan",
"keyword" : "jalan"
}
]
}
I want to get dinas attribute where id is `"5aeffdb80e006205640052ff_5af0101d0e00620564005307"
I had using function db.users.find('dinas.id', '5aeffdb80e006205640052ff_5af0101d0e00620564005307') but the output still called all elements in dinas like i wrote above.
Did i do something wrong?
Filtering in MongoDB (using find) will always return entire document, you have to add projection as a second argument of a find method like below:
db.users.find({"dinas.id": "5aeffdb80e006205640052ff_5af0101d0e00620564005307"},
{ "dinas": { $elemMatch: { id: "5aeffdb80e006205640052ff_5af0101d0e00620564005307" } } });
$elemMatch in projection will filter your nested array and return first matching element (that's fine assuming your ids are unique)
You need to take a look at array querying. The problem is that your dinas is a array of objects, and you're trying to query it as a object directly.
Probably $elemMatch will help you. Something like:
db.users.find({
dinas: {
$elemMatch: {
id: 'youridhere'
}
}
});

How to re-map the fields of a mongodb collection?

Picture it -- I have a collection, and it has a particular mapping.
Pretend:
{
"field": "lala",
"subdoc": {
"otherfield": "la",
"etcfield": "la"
}
}
And I need to convert that to a different data structure:
{
"field-gets-renamed": "lala",
"subdoc-has-different-stuff-in-it": {
"something": "la",
"something-else": "la"
}
}
What is the typical way of doing that? (Is there one?)
Do I read collection1 doc by doc and write it to a different collection?
Or is there a way to just remap the fields in collection1?
I would greatly appreciate some example, as I am a novice to mongodb.
You should make use of the $rename operator
In your case it would be:
db.collectionName.update({}, { $rename : { 'field' : 'field123', 'subdoc' : 'subdoc123', 'subdoc.otherfield' : 'subdoc.otherfield123', 'subdoc.etcfield' : 'subdoc.etcfield123'} }, false, true);
P.S: I havent tested the above command.

mongodb update and/or change an array key without using the value

I'm having trouble removing/renaming an array object from my mongodb.
{
"_id" : ObjectId("556a7e1b7f0a6a8f27e01b8a"),
"accountid" : "AC654164545",
"sites" :[
{ "site_id" : "example1.com" },
{ "002" : "example2.com" },
{ "003" : "example3.com" },
{ "004" : "example4.com" },
{ "005" : "example5.com" },
{ "006" : "example6.com" }
]}
}
Please take notice of the array key "site_id", I want to change it to "001" by either removing and appending it, which I know how to do, or rename it.
I've tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites.site_id}})
But that says "unexpected token".
So I tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites:site_id}})
That says "site_id is not defined"
Then I tried:
db.accounts.update({'id':ObjectId("556a7e1b7f0a6a8f27e01b8a")}, {$unset: {sites:'site_id'}})
That says WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
I also tried a $rename command:
db.accounts.update( { _id:ObjectId("556a7e1b7f0a6a8f27e01b8a") }, { $rename: { "sites.site_id": "sites.001" } } )
But that gave me a "Cannot use part (sites of sites.site_id) to traverse the element"
One option would be to use .find(), iterate through and delete it. Save the undeleted ones into an object, and run an .insert() command, but I want to stay away from that if I have too.
This site talks about dynamic renaming: http://docs.mongodb.org/manual/reference/operator/update/positional/
Aka first you make a matching query and then you use the $ to match that to the index in the array.
Here's the query that'll accomplish what you want for the test data you provided:
db.accounts.update({'accountid':"AC654164545", "sites.site_id": "example1.com"}, {$set: {"sites.$": {'001': 'example1.com'}}})
It is not recommended to use dynamic values such as numbers as a key in document structure. This will be more difficult to query using such values.
You can use $set and $elemMatch to get result as following:
db.collection.update({
'_id': ObjectId("556a7e1b7f0a6a8f27e01b8a"),
"sites": {
$elemMatch: {
"site_id": "example1.com"
}
}
}, {
$set: {
"sites.$":{"001": "example1.com"}
}
})

Use two push-es in update command

Well the answer is probably no but I am curious to ask.
I have a Document which has two level of arrays in it:
{ '_id : '...' , events : [ urls : [], social_id : [] ], 'other_data' : '...' }
The code below works. What is does is update on a specific event the url array and adds to that set the event['event_url'] value (python).
db.col.update(
{ 'f_id' : venue['id'],
"events.title" : find_dict["events.title"] },
{ '$addToSet': { 'events.$.urls': event['event_url']} }
)
However in the same event I want to add a social id if not exists.
db.col.update(
{ 'f_id' : venue['id'],
"events.title" : find_dict["events.title"] },
{ '$addToSet': { 'events.$.social_id': event['social_id']} }
)
I was wandering if it's possible to merge the above commands into one and not run the update twice. I have not found anything in the documentation but I guess it's worth asking.
You can combine the two updates into a single operation by including both fields in the $addToSet object:
db.col.update(
{ 'f_id': venue['id'], "events.title": find_dict["events.title"] },
{ '$addToSet': {
'events.$.urls.': event['event_url'],
'events.$.social_id.': event['social_id']
}}
)

How do I perform this query in both Mongo console and Mongoid?

I'm trying to learn how to query Mongo in more advanced ways. Let's say my data structure is this:
{ "_id" : "-bcktick-ajman-ae-292932", "asciiname" : "`Ajman",
"alternatenames" : [
{
"isolanguage" : "no",
"alternateNameId" : 2698358,
"alternateName" : "Ajman"
},
{
"isolanguage" : "en",
"alternateNameId" : 2698357,
"alternateName" : "Ajman"
}
]
}
So to find Ajman is easy:
db.cities.find({ "asciiname":"`Ajman" })
However, I want to find cities that only have an isolanguage of en. You'll notice the isolanguage is in the alternatenames array.
But I can't seem to find the correct syntax in either the client or mongoid
Either one (or both) would be greatly appreciated.
Thanks
I think you are looking for the $elemMatch keyword:
db.cities.find(
{ 'alternatenames' : {
$elemMatch: { isolanguage: 'en'}
}
})
Currently, Mongoid does not have a helper for $elemMatch so you have to pass in the raw query:
City.where({ :alternatenames => { '$elemMatch' => { :isolanguage => 'en' } } })
More info here on $elemMatch here:
http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24elemMatch
http://www.mongodb.org/display/DOCS/Dot+Notation+%28Reaching+into+Objects%29
More info on Mongoid support for $elemMatch here:
http://groups.google.com/group/mongoid/browse_thread/thread/8648b451e3957e12
https://github.com/mongoid/mongoid/issues/750