Mongoose find all documents that have a string in an array - arrays

I've a question:
How i can find all documents that have a string in an array using mongoose?
For example, my document:
<Model>.findMany(/* code that i need */).exec() // return all documents that have an array called "tags" that includes tag "test"
{
"_id": {
"$oid": "61b129b7dd0906ad4a2efb74"
},
"id": "843104500713127946",
"description": "Server di prova",
"tags": [
"developers",
"programming",
"chatting",
"ita"
],
"shortDescription": "Siamo un server nato per chattare e aiutare programmatori su Discord!",
"invite": "https://discord.gg/NdqUqHBxz9",
"__v": 0
}
For example, if I need to get all documents with ita tag, I need to get this document.
If the document doesn't have ita tag in array tag, I don't need it and the code will not return it.
Thanks in advance and sorry for bad english.

Actually you can just request tags to be test since mongoose looks for every tags entry
so this:
await Test.insertMany([{ tags: ["test"] }, { tags: ["Notest"] }])
let res = await Test.find({ "tags": "test" })
console.log(res)
}
returns that:
[
{
_id: new ObjectId("61b8af02c3effad21a5d7187"),
tags: [ 'test' ],
__v: 0
}
]
2 more neat things to know:
This works no matter how many entries tags has, as long test is one of ethem
This also enables you to change the entrie containing the "test" by using positional $ operator, so something like {$set: "tags.$": "smthNew"} will change all the test entries
Example for 2nd:
let res = await Test.updateMany({ "tags": "test" }, { $set: { "tags.$": "new" } }, { new: true })

Related

How to remove a field having a specified value from a MongoDB document?

Here is a given document in mongo db as below
{
"emailid": "xxx.gmail",
"user_devices": [],
"devices": [{
"created_time": 1607153351,
"token": 123
},
{
"created_time": 1807153371,
"token": 1345
}]
}
Here i want to remove the field inside devices ie
"created_time": '', "token": '1345'
here token value is known, so need to delete "created_time" along with "token" where output is like
{
"emailid": "xxx.gmail",
"user_devices": [],
"devices": [{
"created_time": 1607153351,
"token": 123
}]
}
I tried unset code like below
var myquery = { 'emailid': emailid , 'devices.token':1345};
var newvalues = { $unset:{
'devices.created_time':'',
'devices.token':1345
} };
and used
updateOne(myquery, newvalues, function(err, res))
but it does not work. Can anyone tell how to remove this specified field. Thanks in advance.
Since the object you want to remove is in an array, you have to use $pull in this way:
db.collection.update({
"emailid": "xxx.gmail"
},
{
"$pull": {
"devices": {
"token": 123
}
}
})
In this query you are telling mongo: "Remove -using $pull- one field from the array devices where token is 123".
Example here
Edit:
Also, if you want to remove only one field into objec within array and not the object itself, the you can use $[<identifier>] to filter and then $unset like this:
db.collection.update({
"emailid": "xxx.gmail"
},
{
"$unset": {
"devices.$[elem].created_time": ""
}
},
{
"arrayFilters": [
{
"elem.token": 123
}
]
})
In this query, using arrayFilters you can $unset only in the object where token is 123. You can use created_time to filter if you want too.
Example here

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

MongoDB - Update multiple subdocuments in array in multiple documents

I'm making a node.js website. I have a posts collection in which comments for posts are stored in an array with the comment author's details as nested object.
This is new post's schema:
{
"text": text,
"image": image,
"video": video,
"type": type,
"createdAt": createdAt,
"reactions": [],
"comments": [],
"shares": [],
"user": {
"_id": user._id,
"username": user.username
}
}
This is new comment being pushed to its post:
$push: {
"comments": {
"_id": commentId,
"user": {
"_id": user._id,
"type": type,
"name": user.name,
"profileImage": user.photo,
},
"comment": comment,
"createdAt": createdAt,
"replies": []
}
}
To avoid storing comments in another collection and doing complex multiple lookups(I'm doing 1 lookup to get post author details but couldn't add another to make it work for comments) to consolidate the newsfeed I decided to save comments and their author's details embedded in the posts.
Now when user profile picture is updated all the comments have to be updated to show the new picture.
I included this updateMany query along with the photo updation route in server.js file:
database.collection("posts").updateMany({
"comments.user._id": user._id,
"comments.user.type": "friend"
}, {
$set: {
"comments.$.user.profileImage": photo
}
});
The problem here is that this updates only the first matching comment in all posts.
I need to update all matching comments in all posts.
I'm actually just learning by doing this following youtube videos, so please help me.
You need to use arrayFilters I think.
If I've understand well your question this example should be similar to your DB.
The query is this:
db.collection.update({
"comments.user._id": 1,
"comments.user.type": "friend"
},
{
"$set": {
"comments.$[element].user.profileImage": "new"
}
},
{
"arrayFilters": [
{
"$and": [
{
"element.user._id": 1
},
{
"element.user.type": "friend"
}
]
}
],
"multi": true
})
First part is the same, and almost the second. You have to add element position into the array that is defined in the next step.
Using arrayFilters, you look for those whose match the comaprsion into $and. And only those ones will be updated.
Note that using updateMany() method, is not neccesary using {multi: true}

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

Update array of subdocuments in MongoDB

I have a collection of students that have a name and an array of email addresses. A student document looks something like this:
{
"_id": {"$oid": "56d06bb6d9f75035956fa7ba"},
"name": "John Doe",
"emails": [
{
"label": "private",
"value": "private#johndoe.com"
},
{
"label": "work",
"value": "work#johndoe.com"
}
]
}
The label in the email subdocument is set to be unique per document, so there can't be two entries with the same label.
My problems is, that when updating a student document, I want to achieve the following:
adding an email with a new label should simply add a new subdocument with the given label and value to the array
if adding an email with a label that already exists, the value of the existing should be set to the data of the update
For example when updating with the following data:
{
"_id": {"$oid": "56d06bb6d9f75035956fa7ba"},
"emails": [
{
"label": "private",
"value": "me#johndoe.com"
},
{
"label": "school",
"value": "school#johndoe.com"
}
]
}
I would like the result of the emails array to be:
"emails": [
{
"label": "private",
"value": "me#johndoe.com"
},
{
"label": "work",
"value": "work#johndoe.com"
},
{
"label": "school",
"value": "school#johndoe.com"
}
]
How can I achieve this in MongoDB (optionally using mongoose)? Is this at all possible or do I have to check the array myself in the application code?
You could try this update but only efficient for small datasets:
mongo shell:
var data = {
"_id": ObjectId("56d06bb6d9f75035956fa7ba"),
"emails": [
{
"label": "private",
"value": "me#johndoe.com"
},
{
"label": "school",
"value": "school#johndoe.com"
}
]
};
data.emails.forEach(function(email) {
var emails = db.students.findOne({_id: data._id}).emails,
query = { "_id": data._id },
update = {};
emails.forEach(function(e) {
if (e.label === email.label) {
query["emails.label"] = email.label;
update["$set"] = { "emails.$.value": email.value };
} else {
update["$addToSet"] = { "emails": email };
}
db.students.update(query, update)
});
});
Suggestion: refactor your data to use the "label" as an actual field name.
There is one straightforward way in which MongoDB can guarantee unique values for a given email label - by making the label a single separate field in itself, in an email sub-document. Your data needs to exist in this structure:
{
"_id": ObjectId("56d06bb6d9f75035956fa7ba"),
"name": "John Doe",
"emails": {
"private": "private#johndoe.com",
"work" : "work#johndoe.com"
}
}
Now, when you want to update a student's emails you can do an update like this:
db.students.update(
{"_id": ObjectId("56d06bb6d9f75035956fa7ba")},
{$set: {
"emails.private" : "me#johndoe.com",
"emails.school" : "school#johndoe.com"
}}
);
And that will change the data to this:
{
"_id": ObjectId("56d06bb6d9f75035956fa7ba"),
"name": "John Doe",
"emails": {
"private": "me#johndoe.com",
"work" : "work#johndoe.com",
"school" : "school#johndoe.com"
}
}
Admittedly there is a disadvantage to this approach: you will need to change the structure of the input data, from the emails being in an array of sub-documents to the emails being a single sub-document of single fields. But the advantage is that your data requirements are automatically met by the way that JSON objects work.
After investigating the different options posted, I decided to go with my own approach of doing the update manually in the code using lodash's unionBy() function. Using express and mongoose's findById() that basically looks like this:
Student.findById(req.params.id, function(err, student) {
if(req.body.name) student.name = req.body.name;
if(req.body.emails && req.body.emails.length > 0) {
student.emails = _.unionBy(req.body.emails, student.emails, 'label');
}
student.save(function(err, result) {
if(err) return next(err);
res.status(200).json(result);
});
});
This way I get the full flexibility of partial updates for all fields. Of course you could also use findByIdAndUpdate() or other options.
Alternate approach:
However the way of changing the schema like Vince Bowdren suggested, making label a single separate field in a email subdocument, is also a viable option. In the end it just depends on your personal preferences and if you need strict validation on your data or not.
If you are using mongoose like I do, you would have to define a separate schema like so:
var EmailSchema = new mongoose.Schema({
work: { type: String, validate: validateEmail },
private: { type: String, validate: validateEmail }
}, {
strict: false,
_id: false
});
In the schema you can define properties for the labels you already want to support and add validation. By setting the strict: false option, you would allow the user to also post emails with custom labels. Note however, that these would not be validated. You would have to apply the validation manually in your application similar to the way I did it in my approach above for the merging.

Resources