Which is the efficient way of updating an element in MongoDB? - arrays

I have a collection like below
{
"doc_id": "1234",
"items": [
{
"item_no": 1,
"item": "car",
},
{
"item_no": 2,
"item": "bus",
},
{
"item_no": 3,
"item": "truck",
}
]
},
I need to update an element inside items list based on a search criteria. My search criteria is, if "item_no" is 3, "item" should be updated to "aeroplane".
I have written the following two approaches in Python to solve this.
Approach 1:
cursor = list(collection.find({"doc_id": 1234}))
for doc in cursor:
if "items" in doc:
temp = deepcopy(doc["items"])
for element in doc["items"]:
if ("item_no" and "item") in element:
if element["item_no"] == 3:
temp[temp.index(element)]["item"] = "aeroplane"
collection.update_one({"doc_id": 1234},
{"$set": {"items": temp}})
Approach 2:
cursor = list(collection.find({"doc_id": 1234}))
for doc in cursor:
if "items" in doc:
collection.find_one_and_update({"doc_id": 1234}, {'$set': {'items.$[elem]': {"item_no": 3, "item": "aeroplane"}}}, array_filters=[{'elem.item_no': {"$eq": 3}}])
Among the above two approaches, which one is better in terms of time complexity?

Use only a query and avoid loops:
db.collection.update({
"doc_id": "1234",
"items.item_no": 3
},
{
"$set": {
"items.$.item": "aeroplane"
}
})
Example here
Note how using "items.item_no": 3 into the find stage you can use $ into update stage to refer the object into the array.
So, doing
{
"doc_id": "1234",
"items.item_no": 3
}
When you use $ you are telling mongo: "Ok, do your action in the object where the condition is match" (i.e., the object in the collection with doc_id: "1234" and an array with items.item_no: 3)
Also if you want to update more than one document you can use multi:true like this example.
Edit: It seems you are using pymongo so you can use multi=True (insted of multi: true) or a cleaner way, using update_many.
collection.update_many( /* your query here */ )

Related

MongoDB Array Query - Single out an array element

I am having trouble with querying a MongoDB collection with an array inside.
Here is the structure of my collection that I am querying. This is one record:
{
"_id": "abc123def4567890",
"profile_id": "abc123def4567890",
"image_count": 2,
"images": [
{
"image_id": "ABC123456789",
"image_url": "images/something.jpg",
"geo_loc": "-0.1234,11.234567890",
"title": "A Title",
"shot_time": "01:23:33",
"shot_date": "11/22/2222",
"shot_type": "scenery",
"conditions": "cloudy",
"iso": 16,
"f": 2.4,
"ss": "1/545",
"focal": 6.0,
"equipment": "",
"instructions": "",
"upload_date": 1234567890,
"update_date": 1234567890
},
{
"image_id": "ABC123456789",
"image_url": "images/something.jpg",
"geo_loc": "-0.1234,11.234567890",
"title": "A Title",
"shot_time": "01:23:33",
"shot_date": "11/22/2222",
"shot_type": "portrait",
"conditions": "cloudy",
"iso": "16",
"f": "2.4",
"ss": "1/545",
"focal": "6.0",
"equipment": "",
"instructions": "",
"upload_date": 1234567890,
"update_date": 1234567890
}
]
}
Forgive the formatting, I didn't know how else to show this.
As you can see, it's a profile with a series of images within an array called 'images' and there are 2 images. Each of the 'images' array items contain an object of attributes for the image (url, title, type, etc).
All I want to do is to return the object element whose attributes match certain criteria:
Select object from images which has shot_type = "scenery"
I tried to make it as simple as possible so i started with:
find( { "images.shot_type": "scenery" } )
This returns the entire record and both the images within. So I tried projection but I could not isolate the single object within the array (in this case object at position 0) and return it.
I think the answer lies with projection but I am unsure.
I have gone through the MongoDB documents for hours now and can't find inspiration. I have read about $elemMatch, $, and the other array operators, nothing seems to allow you to single out an array item based on data within. I have been through this page too https://docs.mongodb.com/manual/tutorial/query-arrays/ Still can't work it out.
Can anyone provide help?
Have I made an error by using '$push' to populate my images field (making it an array) instead of using '$set' which would have made it into an embedded document? Would this have made a difference?
Using aggregation:
db.collection.aggregate({
$project: {
_id: 0,
"result": {
$filter: {
input: "$images",
as: "img",
cond: {
$eq: [
"$$img.shot_type",
"scenery"
]
}
}
}
}
})
Playground
You can use $elemMatch in this way (simplified query):
db.collection.find({
"profile_id": "1",
},
{
"images": {
"$elemMatch": {
"shot_type": 1
}
}
})
You can use two objects into find query. The first will filter all document and will only get those whose profile_id is 1. You can omit this stage and use only { } if you wnat to search into the entire collection.
Then, the other object uses $elemMatch to get only the element whose shot_type is 1.
Check an example here

Get Total numbers of each element in Array of MongoDB Object

I have a set of objects in a MongoDB. The object includes an array of types. Now I am connecting to the DB with Mongoose and would like to now the number of objects for each Type.
For example my objects look like
{
"name": "abc",
"tags": ["a","b","c"]
}
Now I would like to get the total number of Objects which for example have the Tag "a"
I am connecting to the MongoDB with a NodeJS Backend using Mongoose
Thanks for your Ideas on how to query this efficiently.
Addition:
In the case where I don't know what kind of different tags are existing in the different objects and I would like to get an overview of all tags, how do I need to query on this one? For better understanding I give an example.
Two objects in the database:
{
"name": "abc",
"tags": ["a","b","c"]
},
{
"name": "abc",
"tags": ["a","b"]
}
the function/query should give a response of something like this:
{
"a": 2,
"b": 2,
"c": 1
}
collection.aggregate([
{$unwind: "$tags" },
{$group: {
_id: "$tags",
count: {$sum : 1}
}},
]);
this will give output like this:
/* 1 */
{
"_id" : "c",
"count" : 2
}
/* 2 */
{
"_id" : "b",
"count" : 3
}
/* 3 */
{
"_id" : "a",
"count" : 2
}
Use count which is a collection method. It returns the count only instead of all documents. If You need the documents , replace count with find
collection.count({tags:"a"})

Filter for items in nested array using lodash

I have an array of > 1000 objects, each with a nested array that looks something like that:
data = [{
"id": 0,
"location": "A",
"basket": [
"milk",
"bread",
"sugar",
"water"
],
}, {
"id": 1,
"location": "B",
"basket": [
"chocolate",
"cereal",
"sugar",
"sauce"
],
}, {
"id": 2,
"location": "C",
"basket": [
"milk",
"cereal",
"soda",
"flour"
],
}]
I have a multi-select dropdown menu that has the list of all items in the "basket" nested array. When I select "sugar", it should be able to return the objects with id=0 and id=1 or if I select both "water" and "milk" should return objects with id=0 and id=2. I have tried using a combination of _.map _.find _.filter, but it doesn't work. Also tried looking for similar questions here, but didn't find one. prefer to use lodash if possible.
You can use this:
var result = _.filter(data, { basket: ['sugar', 'milk'] });
Replace the array of products with whatever you are looking for. They must all occur in the same item for it to be retained in the result.
Although you clearly indicate you prefer a lodash-based solution, I want to add the vanilla JS way as well:
var filtered = data.filter(function(item){
return ['sugar', 'milk'].every(product => item.basket.includes(product));
});
When you want the logic to be that only some of the selected products need to occur in the basket for it to get selected, then also in the lodash version you'll need a callback:
var result = _.filter(data, function(item) {
return _.intersection(item.basket, ['sugar', 'milk']).length;
});
In the vanilla JS version, replace every by some.

mongo update : upsert an element in array

I would like to upsert an element in an array, based on doc _id and element _id. Currently it works only if the element is allready in the array (update works, insert not).
So, these collection:
[{
"_id": "5a65fcf363e2a32531ed9f9b",
"ressources": [
{
"_id": "5a65fd0363e2a32531ed9f9c"
}
]
}]
Receiving this request:
query = { _id: '5a65fcf363e2a32531ed9f9b', 'ressources._id': '5a65fd0363e2a32531ed9f9c' };
update = { '$set': { 'ressources.$': { '_id': '5a65fd0363e2a32531ed9f9c', qt: '153', unit: 'kg' } } };
options = {upsert:true};
collection.update(query,update,options);
Will give this ok result:
[{
"_id": "5a65fcf363e2a32531ed9f9b",
"ressources": [
{
"_id": "5a65fd0363e2a32531ed9f9c",
"qt": 153,
"unit": "kg"
}
]
}]
How to make the same request work with these initial collections:
[{
"_id": "5a65fcf363e2a32531ed9f9b"
}]
OR
[{
"_id": "5a65fcf363e2a32531ed9f9b",
"ressources": []
}]
How to make the upsert work?
Does upsert works with entire document only?
Currently, I face this error:
The positional operator did not find the match needed from the query.
Thanks
I also tried to figure out how to do it. I found only one way:
fetch model by id
update array manually (via javascript)
save the model
Sad to know that in 2018 you still have to do the stuff like it.
UPDATE:
This will update particular element in viewData array
db.collection.update({
"_id": args._id,
"viewData._id": widgetId
},
{
$set: {
"viewData.$.widgetData": widgetDoc.widgetData
}
})
$push command will add new items

Multiple search filtering is not working in cloudant, why?

Here i quoted my code for multiple search filtering. I could not find the mistakes in that. please give a right code to make it work well.
Employee document:
{
"_id": "527c8d9327c6f27f17df0d2e17000530",
"_rev": "24-276a8dc913559901897fd601d2f9654f",
"proj_role": "TeamMember",
"work_total_experience": "3",
"personal": {
"languages_known": [
"English","Telugu"
]},
"skills": [
{
"skill_set": "Webservices Framework",
"skill_exp": 1,
"skill_certified": "yes",
"skill_rating": 3,
},
{
"skill_set": "Microsoft",
"skill_exp": 1,
"skill_certified": "yes",
"skill_rating": 3,
}
]
"framework_competency": "Nasscom",
"type": "employee-docs"
}
Design Document:
{
"_id": "_design/sample",
"_rev": "86-1250f792e6e84f6f33447a00cf64d61d",
"views": {},
"language": "javascript",
"indexes": {
"search": {
"index": "function(doc){\n index(\"default\", doc._id);if(doc.type=='employee-docs'){\nif (doc.proj_role){index(\"project_role\", doc.proj_role);}if(doc.work_total_experience){\nindex(\"work_experience\", doc.work_total_experience);}\nif(doc.personal.languages_known){for(c in doc.personal.languages_known){ \n index(\"languages_known\",doc.personal.languages_known[c]);}} if(doc.skills){for (var i=0;i<doc.skills.length;i++){\nindex('skill_set',doc.skills[i].skill_set);}}}}"
}
}
}
Run using below URL : https://ideyeah4.cloudant.com/opteamize_new/_design/sample/_search/search?q=project_role:TeamMember%20AND%20work_experience:%223%22%20AND%20languages_known:Telugu%20AND%20skill_set:Microsoft&include_docs=true
A simple way to debug this is to query the top 100 results in your index:
https://ideyeah4.cloudant.com/opteamize_new/_design/sample/_search/search?q=*:*&limit=100
This will at least tell you whether there are any documents in your index at all.
Your current query (without URL encoding) looks like:
project_role:TeamMember AND work_experience:"3" AND languages_known:Telugu AND skill_set:Microsoft
I'd suggest that some of these search values require quotes - always true when you are searching string values. Next, you could try:
project_role:"TeamMember"
see if you get any results and refine from there.
Debugging this might also be easier if you store the values as well as index them (so you can see exactly what is indexed). To do this, add an object to each index call { "store": true }. For example,
index("languages_known", doc.personal.languages_known[c], { "store": true });
Now, when you query the index it will return a list of fields which were stored with each match.

Resources