MongoDB findOne() and updateOne() with embedded document - mongodb

Assume I got the following document with embedded documents in arrays:
{
"name" : "test",
"children" : [
{
"name" : "child1",
"children" : [
{
"name" : "sub1",
"children" : [
{"name" : "c1"},
{"name" : "c2"},
{"name" : "c3"},
{"name" : "c4"},
{"name" : "c5"}
]
}
]
},
{
"name" : "child2",
"children" : [
{
"name" : "sub2",
"children" : [
{"name" : "c1"},
{"name" : "c2"},
{"name" : "c3"},
{"name" : "c4"}
]
},
{
"name" : "sub3",
"children" : [
{"name" : "c1"},
{"name" : "c2"},
{"name" : "c3"},
{"name" : "c4"}
]
},
{
"name" : "sub4",
"children" : [
{"name" : "c1"},
{"name" : "c2"},
{"name" : "c3"},
{"name" : "c4"}
]
}
]
}
]
}
As we can see, test has two children: child1 and child2, while a child has their own children like sub1, sub2, sub3, sub4, etc, and so on.
Currently, I have to update some of the children content, but I ran into the following two issues:
The first issue is about findOne(), when I accidentally made a typo (name child1 should be child2):
db.collection_name.findOne({
'name': 'test',
'children.name': 'child1', // -> should be `child2`
'children.children.name': 'sub2'
})
I can still get document 'test' correctly, but since I made a typo and sub2 is actually under child2, not child1, then how can I still get this correct findOne() result?
The second issue is about updateOne(), when I try to update child1's array children, it works good (the code is also like the sample from the official document):
db.collection_name.updateOne({
'name': 'test',
'children.name': 'child1' // locate child1
}, {
'$set':{'children.$.children':[some other content]}
})
But when I also try to update sub3's array (under child2), the same way failed with error cannot use the part (children of children.children.1.children) to traverse the element, and here is the code with issue:
db.collection_name.updateOne({
'name': 'test',
'children.name': 'child2',
'children.children.name': 'sub3' // locate sub3
}, {
'$set':{'children.children.$.children':[some other content]}
})
I will be really appreaciate if someone can help with these two issues, thanks in advance!

For your first issue, you can use $elemMatch to find the exact element.
db.collection_name.findOne({
'name':'test',
children:{
$elemMatch:{
'name':'child2',
'children.name':'sub2'
}
}
})
I guess that mongo does not support multiple nested array update, but if you know the subarray position you can use this approach.
db.collection_name.updateOne({
'name':'test',
children:{
$elemMatch:{
'name':'child2',
'children.name':'sub3'
}
}
}
, {
$set: {"children.$.children.1.children" :
[
{"name" : "c1"},
{"name" : "c2"},
{"name" : "c3"},
{"name" : "c4"}
]
}
}
)

First Issue: The query return results when querying array as the match is evaluated against all elements of array i.e first match 'children.name': 'child1' matches first element and second 'children.children.name': 'sub2' matches the second element.
Use $elemMatch(query) to match both the conditions to the same array element.
db.collection_name.findOne({
'name': 'test',
'children':{$elemMatch:{'name': 'child2', 'children.name': 'sub2'}}
})
Compare both the above approaches 1 & 2
Second issue: Mongodb 3.6 now supports multiple positional updates through arrayFilters expression.
The below query to replace all children of sub3 children.
db.collection_name.updateOne(
{'name': 'test'},
{'$set':{'children.$[first].children.$[second].children':[some data]}},
{'arrayFilters': [{ 'first.name': 'child2', 'second.name': 'sub3'}]}
)
The below query to push new child in the childrens of sub3 children.
db.collection_name.updateOne(
{'name': 'test'},
{'$push':{'children.$[first].children.$[second].children':new child data}},
{'arrayFilters': [{ 'first.name': 'child2', 'second.name': 'sub3'}]}
)

Related

MongoDB get whole document filtered by subdocuments content [duplicate]

I'm trying to use $elemMatch to find an object in an array. I imported the following data into a collection named trails:
{ "Copper" : [
{"name" : "Spaulding Bowl", "level" : "Extreme Terain", "location" : "East Side"},
{"name" : "Resolution Bowl", "level" : "Double Black", "location" : "East Side"},
{"name" : "Black Bear Glade", "level" : "Double Black", "location" : "East Side"},
{"name" : "Free Fall Glade", "level" : "Double Black", "location" : "East Side"}
]
}
I'm using the syntax from the MongoDB documentation to make the following query:
db.trails.find( { "Copper": { $elemMatch: { "name" : "Spaulding Bowl" } } } )
I have also tried formating it without quotations around the keys:
db.trails.find( { Copper: { $elemMatch: { name : "Spaulding Bowl" } } } )
Instead of returning just one matching element, both return the entire array of objects. Is there a syntax error I'm missing? All tips are appreciated.
$elemmatch(query) returns all rows in a array when there is atleast one row matching the query criteria.
$elemMatch(projection) returns only the first row of all the matching rows when used as projection.
You don't need elemMatch for your case as it is only single criteria.
db.trails.find({"Copper.name": { "Spaulding Bowl" } })
Try as below which uses the elemMatch projection variation.
db.trails.find({}, {"Copper": { $elemMatch: { "name" : "Spaulding Bowl" } } } )

Mongodb: How to create an entry in array if not present

I have a document structure like the following:
{
"_id" : ObjectId("58be819769f922b8427e7f10"),
"id" : "58be7a4cfc13ae51020002cc",
"source" : "facebook",
"source_id" : "b37f5e43-ea3f-46d2-b2b0-b8f55e923933",
"data" : [
{
"date" : "5/8/2016",
"topics" : [
"Vectorworks",
"Statistics",
"Flight Planning",
"HTK",
"Custom Furniture"
]
},
{
"date" : "7/22/2016",
"topics" : [
"FDR",
"NS2",
"Power Plants"
]
},
{
"date" : "12/23/2016",
"topics" : [
"LTV",
"MMS"
]
}
]
}
Now, If I want to append more topics to the date "12/23/2016", I can do that using:
db.revision.findOneAndUpdate({id: '58be7a4cfc13ae51020002cc', data: {$elemMatch: {date: '03/17/2017'}}}, {$push: {'data.$.topics': 'java lambda'}},{upsert: true})
But, how can I add a new object, to data array when the entry for a date is not present?
First insert an empty data doc for the required date field, if its not already present
db.revision.update( {id: '58be7a4cfc13ae51020002cc', "data.date" : {$ne : '03/17/2017' }} ,
{$addToSet : {"data" : {'date' : '03/17/2017'}} } ,
false , //<-- upsert
true ); //<-- multi
Then update the same using the update query as devised by you
db.revision.findOneAndUpdate({id: '58be7a4cfc13ae51020002cc', data: {$elemMatch: {date: '03/17/2017'}}}, {$push: {'data.$.topics': 'java lambda'}},{upsert: true})

How to search nearest place in array object in mongodb

the mongodb document 'contents' is
{
"_id" : ObjectId("57bd1ff410ea3c38386b9194"),
"name" : "4Fingers",
"locations" : [
{
"id" : "locations1",
"address" : "68 Orchard Rd, #B1-07 Plaza Singapura, Plaza Singapura, Singapura 238839",
"phone" : "+65 6338 0631",
"openhours" : "Sunday-Thursday: 11am - 10pm \nFriday/Saturday/Eve of PH*: 11am - 11pm",
"loc" : [
"1.300626",
"103.845061"
]
}
],
"comments" : [ ],
"modified" : 1472271793525,
"created" : 1472012276724,
"createdby" : "Admin",
"modifiedby" : "Admin",
"createdipaddress" : "localhost",
"modifiedipaddress" : null,
"types" : "Restaurant",
"category" : "FoodAndBeverages",
"logo" : "logo4Fingers.png",
"tags" : "western, chicken, restaurant, food, beverages"
}
I want to find the nearest place to my location that i get from HTML5 navigation. How do i query it? Data should be sorted in near to far order.
Thank you.
To query mongodb geospatial data first you need a geo spatial index on your location field.
Your location field is a string, it needs to be a numeric type, you need to update your data accordingly.
Create your index on numerical location data:
db.collection.createIndex( { "locations.loc" : "2d" });
Query:
var projection = {"locations.loc":1};
var query = {"locations.loc": {"$near":[1.300626, 103.845061], "$maxDistance": 0.5}};
db.collection.find(query, projection).pretty();
//result:
{
"_id" : ObjectId("57bd1ff410ea3c38386b9194"),
"locations" : [
{
"loc" : [
1.300626,
103.845061
]
}
]
}
var query2 = {"locations.loc": {"$near":[2.300626, 103.845061], "$maxDistance": 0.5}};
db.collection.find(query2, projection).pretty();
//result:
{}
The query result will be sorted always, with nearest as first document.
Thanks Sergiu Zaharie.
It's working.
Then how can i returned all the field instead of returning this field only.
{
"_id" : ObjectId("57bd1ff410ea3c38386b9194"),
"locations" : [
{
"loc" : [
103.845061,
1.300626
]
}
]
}
//edit
Solved.
i just clear the projection then it work like charm.
Thank you.

Using $addToSet to update an array field using another array field

I should start with: I'm knew to MongoDB, and document-style databases in general.
I have a collection that looks something like this:
{
"_id" : ObjectId("554a5e72b16f31ff0894310e"),
"title" : "ABC",
"admins" : [
"personA",
"personB",
],
"email_address" : "ABC#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff0894310f"),
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff08943110"),
"title" : "Company Three Site",
"admins" : [
"personC"
"personD",
],
"email_address" : "company2plus1#mysite.com"
}
What I need to do, is append the admins list from Company One, to Company Three such that Company Three now has four admins (A, B, C, D).
I tried the following, because it seemed pretty straight forward to me - get the data from the origin and append to destination directly:
db.runCommand({
findAndModify : 'sites',
query : {'title' : 'Company Three Site'},
update : { '$addToSet' :
{'admins' :
db.projects.find({'title' : 'ABC'}, {'_id' : 0, 'admins' : 1}
}
}
})
However, this does not work correctly.
I am still trying to figure out ways I could do this directly, but questions...
1) Is this even possible by using single command, or do I need to split this up?
2) Does my train of logical thought make sense, or should I be doing this some other/easier way that is more conventional for MongoDB style databases?
db.projects.find actually returns a cursor, which you definitely don't want to add to your set. Since you know ahead of time that you will be only finding one value, you can get the properties out of the cursor specifically by using .next().admin -- but remember that this will only work with the first value returned from .find. Otherwise, I think you will have to use a loop.
$addToSet will also add the array as a whole, so instead you have to append multiple values using $each
All together:
db.runCommand({
findAndModify: 'sites',
query: {'title': 'Company Three Site'},
update: {
$addToSet: {
"admins": {
$each: db.projects.find(
{"title": "ABC"},
{"_id": 0, "admins": 1}
).next().admins
}
}
}
})
This is not possible with an atomic update. However, a workaround is to query the source collection using the find() method and use the cursor's forEach() method to iterate over the results, get the array and update the destination collection using the $addToSet operator and the $each modifier.
Let's demonstrate this with the above sample documents inserted to a test collection:
db.test.insert([
{
"title" : "ABC",
"admins" : [
"personA",
"personB"
],
"email_address" : "ABC#mysite.com"
},
{
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
},
{
"title" : "Company Three Site",
"admins" : [
"personC",
"personD"
],
"email_address" : "company2plus1#mysite.com"
}
])
The following operation will add the admins array elements from company "ABC" to the company "Company Three Site" admin array:
db.test.find({"title" : "ABC"}).forEach(function (doc){
var admins = doc.admins;
db.test.update(
{"title" : "Company Three Site"},
{
"$addToSet": {
"admins": { "$each": admins }
}
},
{ "multi": true }
);
});
Querying the collection for the document with company "Company Three Site" db.collection.find({"title" : "Company Three Site"});
will yield:
/* 0 */
{
"_id" : ObjectId("554a7dc35c5e0118072dd885"),
"title" : "Company Three Site",
"admins" : [
"personC",
"personD",
"personA",
"personB"
],
"email_address" : "company2plus1#mysite.com"
}

mongodb: $orderBy on a field in the first element of a list contained in each document

I have a collection in which element has a list of objects. I would like to use $orderBy on a specific field on the first element of a list of objects that each document has.
For example:
each document represents a user, and each user has a list of sessions. I would like to sort the users on the date stored in the first session of the list.
Maybe something like { $orderby: { "sessions[0].timestamp" : 1 } } ?
Is this possible?
The operation you ask for is a simple one with .sort(). Perhaps you are not aware of the usage of "dot notation" with MongoDB
With the following documents as a minimal example:
{
"name" : "Fred",
"sessions" : [ { "timestamp" : ISODate("2014-06-05T10:38:24.371Z") } ]
}
{
"name" : "Barney",
"sessions" : [ { "timestamp" : ISODate("2014-06-05T10:38:34.557Z") } ]
}
Issue the following query:
db.users.find({},{ _id: 0}).sort({ "sessions.0.timestamp": -1 })
And get the ordered result by the first item of the array, timestamp field:
{
"name" : "Barney",
"sessions" : [ { "timestamp" : ISODate("2014-06-05T10:38:34.557Z") } ]
}
{
"name" : "Fred",
"sessions" : [ { "timestamp" : ISODate("2014-06-05T10:38:24.371Z") } ]
}