MongoDB How to get an item in a subdocument array [duplicate] - node.js

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 5 years ago.
I'm very new to using Mongo and I have a collection that looks like this
{
"_id" : "5e7a39ed-941a-4e07-af0b-df8764820206",
"title" : "Test Title Task",
"taskBody" : "This is a test task body",
"comments" : [
{
"_id" : "57b51a73-f585-4e80-ad51-107da707efd6",
"poster" : "Jack Black",
"comment" : "This is a comment"
},
{
"_id" : "4ea314f3-3671-4568-b938-d8a1477ac681",
"poster" : "Joe Blow",
"comment" : "Another comment"
},
{
"_id" : "c5f1a0e6-2fb9-429e-9931-8634f42fc143",
"poster" : "Mike Hall",
"comment" : " And yet Another comment"
}
]
}
And I'm trying to get one of the comment elements by passing the ID like this:
getCommentById(id) {
return tasks().then((taskCollection) => {
return taskCollection.find({ "comments._id": id }).toArray().then((commentQuery) => {
if (!commentQuery) throw "Comment not found";
return commentQuery;
});
});
}
However, it seems to be returning everything in the comments collection and not just one comment that I'm passing in the id for. How can I get it to return just the comment that I'm passing the id for?
This is what the debug window shows me
Array(1) [Object]
Object {_id: "5e7a39ed-941a-4e07-af0b-df8764820206", title: "Test Title Task", taskBody: "This is a test task body", …}
Array(3) [Object, Object, Object, …]
0: Object {_id: "57b51a73-f585-4e80-ad51-107da707efd6", poster: "Jack Black", comment: "Comment text 2"}
1: Object {_id: "4ea314f3-3671-4568-b938-d8a1477ac681", poster: "Joe Blow", comment: "Another comment"}
2: Object {_id: "c5f1a0e6-2fb9-429e-9931-8634f42fc143", poster: "Joe Blow", comment: "Another comment"}
What I'm expecting to get returned is just:
{
"_id" : "57b51a73-f585-4e80-ad51-107da707efd6",
"poster" : "Jack Black",
"comment" : "This is a comment"
}
Assuming "57b51a73-f585-4e80-ad51-107da707efd6" is the passed in ID. Any help would be appreciated.

You are querying sub array correctly. But your assumption is wrong. While you are querying with a specific sub document of an array field, a find query will still return a list of actual documents. You execute find on collection not just on an array field of a single document.
What you can do with mongoDB is this: you can exclude or include fields for your select queries. So you does not read unnecessary fields from mongoDB and it improves performance. You can learn more about this here
So for your question; while you just want to read a sub document in an array field, you have to use $ operator too. You can find more information here
Your query should look like this:
db.collection.find({ "_id": "some id", "comments._id": "some id"}, {"comments.$": 1, "_id": 0})
But again remember: This query will still return a list(or one collection if you use findOne) of your actual document, not a list of comment. Returned json will look like this:
{
"comments": [
{
"_id" : "57b51a73-f585-4e80-ad51-107da707efd6",
"poster" : "Jack Black",
"comment" : "This is a comment"
}
]
}
Note: If you really want just a comment with structure in your question. You can use aggregation and play the structure of json before return.

taskCollection.find({ "comments": {$elemMatch: { _id: 'your value here '}}})
This should work for you.

Related

Mongoose getting specific item in Embedded Document

I am new in NoSQL and I'm kinda stuck. Can you help me?
Is there any way to get the specific field in an array if it matches a value?
For example, I would like to get the specific item in the array accountGroup where accountGroupName=="Account 1".
I tried many codes but it just returns the whole array which has the item that matches with the value.
By the way, I am using Mongoose and Nodejs. Thanks.
//here is the database
{
"_id" : ObjectId("60d2db4b90c66c3b0832a616"),
"accountType" : "Account Type 1",
"accountGroup" : [
{
"_id" : ObjectId("60d2db5a90c66c3b0832a619"),
"accountGroupName" : "Account 1",
"rangeFrom" : 25,
"rangeTo" : 35
},
{
"_id" : ObjectId("60d3fbfbc1502c3ed8cadf86"),
"accountGroupName" : "Account2",
"rangeFrom" : 850,
"rangeTo" : 2000
},
{
"_id" : ObjectId("60d2ddb1396dbf384898fbad"),
"accountGroupName" : "account 1 sample 3",
"rangeFrom" : 10,
"rangeTo" : 15
}
],
}
{
"_id" : ObjectId("60d2db4e90c66c3b0832a617"),
"accountType" : "Account Type 2",
"accountGroup" : [
{
"_id" : ObjectId("60d2e9586c4fa82310349c7c"),
"accountGroupName" : "account 2 sample 5",
"rangeFrom" : 50,
"rangeTo" : 60
}
]
}
You can use position operator $ into projection like this:
YourModel.find({
"accountGroup.accountGroupName": "Account 1"
},
{
"accountGroup.$": 1
})
Example here
Note that you are getting the whole document because using
YourModel.find({"accountGroup.accountGroupName": "Account 1"})
You are telling mongo "Give me a document where value accountGroupName into accountGroup array is equal to Account 1. And the document who match that condition contains the whole array.
So using positional operator, is waht you need according to its description:
The positional $ operator limits the contents of an to return the first element that matches the query condition on the array.
This is why with one query you get the whole document and with the other one you get the value you want.
Also note $ return only the first subdocument that match the query.

How to read a particular element from array which is the part of object in handlebar file from mongodb?

Below is my mongo db json file.
I want to read the 'topic' element from array 'Addtasks' for each its object element in .hbs(handle bar) file when the route /addTask is called in express.
{
"_id" : ObjectId("5f2313cb1351d606046660fd"),
"email" : "mike#g.com",
"name" : "Mike Tyson",
"Addtasks" : [
{
"otherdetails" : "haha great!",
"website" : "asad.com",
"keywords" : "article importance, article generation, article quality",
"words" : 1000,
"topic" : "How article is generated?",
"_id" : ObjectId("5f2314011351d606046660ff")
},
{
"otherdetails" : "Not much thanks!",
"website" : "abcdxyz.co.in",
"keywords" : "niggas are great, yo whatsup!",
"words" : 2000,
"topic" : "whats your name nigga?",
"_id" : ObjectId("5f23142d1351d60604666101")
}
],
}
You can use the second arg to .find() to project the values you want.
https://mongoplayground.net/p/LLuM1sd5Raq
db.collection.find({}, {
"Addtasks.topic": 1,
name: 1, // Add whatever other fields you need as well, remove if not
email: 1,
})
Yields:
[
{
"Addtasks": [
{
"topic": "How article is generated?"
},
{
"topic": "whats your name nigga?"
}
],
"_id": ObjectId("5f2313cb1351d606046660fd"),
"email": "mike#g.com",
"name": "Mike Tyson"
}
]
If you pass in your data something like this::
res.render('mytemplate.hbs', { items: jsObject.Addtasks });
then your hbs could look like this:
<ul>
{{#each items}}
<li>{{this.topic}}</li>
{{/each}}
</ul>
(Somewhat unrelated, but that json-file is not valid JSON. JSON and javascript objects are not the same thing. )

Mongoose update object in an embedded array

I want to update a comment in a post. I first retrieve the post document which looks like this.
{
"_id" : ObjectId("5aac169c229f0136296407d4"),
"title" : "First Node.js App",
"body" : "testing 123",
"status" : "public",
"user" : "John Doe",
"date" : ISODate("2017-12-21T18:30:09.779Z"),
"comments" : [
{
"commentBody" : "This is awesome! ",
"commentUser" : ObjectId("5a3bfd5a9e65351f9c18ba18"),
"_id" : ObjectId("5a3c02379e65351f9c18ba1a"),
"commentDate" : ISODate("2017-12-21T18:49:27.620Z")
},
{
"commentBody" : "This is second comment.",
"commentUser" : ObjectId("5a3bfd5a9e65351f9c18gt19"),
"_id" : ObjectId("5a3c02379e65351f9c18ba1b"),
"commentDate" : ISODate("2017-12-21T18:49:27.620Z")
}
],
"allowComments" : true
}
Let say I want to update comment with "_id" ObjectId("5a3c02379e65351f9c18ba1a").
I've tried the following without luck.
const post = await Post.findById(req.body.postID);
await post.update({'comments._id' : req.body.commentID},{$set : {
'comments.$.commentBody': req.body.comment
}
});
This gave me the following error:
MongoError: cannot use the part (comments of comments._id) to traverse the element
Any suggestion would be greatly appreciated. Thanks in advance!
You can try something like this::
Post.findOneAndUpdate(
{ "_id": req.body.postID, "comments._id": req.body.commentID },
{
"$set": {
'comments.$.commentBody': req.body.comment
}
},
function(err,doc) {
}
);
I'm not sure about how to implement this in node.js but here is the Mongo query:
db.sample.aggregate([
{$match:{"comments.commentUser":ObjectId("5a3bfd5a9e65351f9c18ba19")}},
{$redact:{
$cond:{
if:{$or:[{$eq:["$commentUser",ObjectId("5a3bfd5a9e65351f9c18ba19")]},
{$not:"$commentUser"}]},
then:"$$DESCEND",
else:"$$PRUNE"
}
}},
{$addFields:{comments:{$map:{
input:"$comments",
as:"comment",
in:{"commentBody":"test comment", "commentUser" : "$$comment.commentUser", "_id" :"$$comment._id", "commentDate" :"$$comment.commentDate"}
}}
}},
{$out:"sample"}
])
Restricted the document such that only particular user id comments are displayed. After that, added comments with updated comment. Finally replacing the original content within aggregation without update query(note that collection will get replaced if you run the query). I didnt test this extensively, but working for small data set in my local. However, you might need to add some tweaks to this query and then check how u can add same query to node.js

update array in mongoose which matches the condition

my schema looks like
{
qty:{
property1:{
//something
}
property2:[{
size:40,
color:"black",
enabled:"true"
}]
}
}
property 2 is array what i want to do is update those array object whose enabled is true in single query
I tried writing the following query
db.col.update({
"qty.property2.enabled" = "true"
}, {
"qty.property2.color" = "green"
}, callback)
but it is not working
error:
[main] Error: can't have . in field names [qty.pro.size]
db.col.update({"qty.property2.enabled":"true"},{$set: {'qty.property2.$.color': 'green'}}, {multi: true})
this is the way to update element inside array.
equal sign '=' cannot be used inside object
updating array is done using $
Alternative solution for multiple conditions:
db.foo.update({
_id:"i1",
replies: { $elemMatch:{
_id: "s2",
update_password: "abc"
}}
},
{
"$set" : {"replies.$.text" : "blah"}
}
);
Why
So I was looking for similar solution as this question, but in my case I needed array element to match multiple conditions and using currently provided answers resulted in changes to wrong fields.
If you need to match multiple fields, for example let say we have element like this:
{
"_id" : ObjectId("i1"),
"replies": [
{
"_id" : ObjectId("s1"),
"update_password": "abc",
"text": "some stuff"
},
{
"_id" : ObjectId("s2"),
"update_password": "abc",
"text": "some stuff"
}
]
}
Trying to do update by
db.foo.update({
_id:"i1",
"replies._id":"s2",
"replies.update_password": "abc"
},
{
"$set" : {"replies.$.text" : "blah"}
}
);
Would result in updating to field that only matches one condition, for example it would update s1 because it matches update_password condition, which is clearly wrong. I might have did something wrong, but $elemMatch solution solved any problems like that.
Suppose your documet looks like this.
{
"_id" : ObjectId("4f9808648859c65d"),
"array" : [
{"text" : "foo", "value" : 11},
{"text" : "foo", "value" : 22},
{"text" : "foobar", "value" : 33}
]
}
then your query will be
db.foo.update({"array.value" : 22}, {"$set" : {"array.$.text" : "blah"}})
where first curly brackets represents query criteria and second one sets the new value.

Querying a property that is in a deeply nested array

So I have this document within the course collection
{
"_id" : ObjectId("53580ff62e868947708073a9"),
"startDate" : ISODate("2014-04-23T19:08:32.401Z"),
"scoreId" : ObjectId("531f28fd495c533e5eaeb00b"),
"rewardId" : null,
"type" : "certificationCourse",
"description" : "This is a description",
"name" : "testingAutoSteps1",
"authorId" : ObjectId("532a121e518cf5402d5dc276"),
"steps" : [
{
"name" : "This is a step",
"description" : "This is a description",
"action" : "submitCategory",
"value" : "532368bc2ab8b9182716f339",
"statusId" : ObjectId("5357e26be86f746b68482c8a"),
"_id" : ObjectId("53580ff62e868947708073ac"),
"required" : true,
"quantity" : 1,
"userId" : [
ObjectId("53554b56e3a1e1dc17db903f")
]
},...
And I want to do is create a query that returns all courses that have a specific userId in the userId array that is in the steps array for a specific userId. I've tried using $elemMatch like so
Course.find({
"steps": {
"$elemMatch": {
"userId": {
"$elemMatch": "53554b56e3a1e1dc17db903f"
}
}
}
},
But It seems to be returning a empty document.
I think this will work for you, you have the syntax off a bit plus you need to use ObjectId():
db.Course.find({ steps : { $elemMatch: { userId:ObjectId("53554b56e3a1e1dc17db903f")} } })
The $elemMatch usage is not necessary unless you actually have compound sub-documents in that nested array element. And also is not necessary unless the value being referenced could possibly duplicate in another compound document.
Since this is an ObjectId we are talking about, then it's going to be unique, at least within this array. So just use the "dot-notation" form:
Course.find({
"steps.userId": ObjectId("53554b56e3a1e1dc17db903f")
},
Go back and look at the $elemMatch documentation. In this case, the direct "dot-notation" form is all you need

Resources