Mongo - request all embedded comments in Post query - mongodb

I'm adapting the Microscope tutorial to my needs and am having difficulty writing a query which gathers all the comments embedded within a post.
Here is my query so far:
Posts.find({"postId": this._id}, {"comments":{}});
Here is an example post I want to get the comments from:
{
"_id": "Ad9RYqWqbsJKZx3h7",
"title": "Writing decimal number words as a decimal number",
"userId": "9yqTaFeQSqvKmNn8B",
"author": "Sacha Greif",
"submitted": "2017-01-05T03:26:18.908Z",
"commentsCount": 4,
"comments": [
{
"body": "Hello",
"postId": "Ad9RYqWqbsJKZx3h7",
"userId": "73qGvsRuqNtXcaZDx",
"author": "student",
"submitted": "2017-01-05T10:26:45.745Z"
},
{
"body": "How are you?",
"postId": "Ad9RYqWqbsJKZx3h7",
"userId": "73qGvsRuqNtXcaZDx",
"author": "student",
"submitted": "2017-01-05T10:28:17.225Z"
}
]}
It seems to return a cursor, but I am not able to use the toArray() function on it.
I have read this is not possible (Filtering embedded documents in MongoDB), but this was six years ago...
I've also seen posts about $slice and aggregate but can't seem to get me head around them.
Any help much appreciated!

query this->
Posts.find({"comments.postId": this._id});

You can just simply use findOne to get the specific document from the collection and you can use forEach Loop to iterate through the comments array of that document.
var post = Posts.findOne({_id: "<id_of_the_post>"});
var post_comments = post.comments; // post_comments contains the comments array
or if you want to do something with each comment use forEach
post_comments.forEach(function(entry, index, arr){
// entry has the comment object
//do something with the comment
});
I hope this helps.

Related

Querying the most recent posts in a MongoDB collection

Rather new to Mongodb/Mongoose/Node. Trying to make a query to retrieve the most recent posts (example being the 10 most recent posts) across all documents in a collection.
I tried querying this a few different ways.
MessageboardModel.find({"posts": {"time": {"$gte": ISODate("2014-07-02T00:00:00Z")}}} ...
I tried doing the above just to try getting to the proper nested time property, but everything I was trying throws an error. I'm definitely missing something here...
Here is an example document in the collection:
{
"_id": {
"$oid": "5c435d493dcf9281500cd177"
},
"movie": 433249,
"posts": [
{
"replies": [],
"_id": {
"$oid": "5c435d493dcf9281500cd142"
},
"username": "Username1",
"time": {
"$date": "2019-01-19T17:24:25.204Z"
},
"post": "This is a post title",
"content": "Content here."
},
{
"replies": [],
"_id": {
"$oid": "5c435d493dcf9281500cd123"
},
"username": "Username2",
"time": {
"$date": "2019-01-12T17:24:25.204Z"
},
"post": "This is another post made earlier",
"content": "Content here."
}
],
"__v": 0
}
There are many documents in the collection. I want to get, say the most recent 10 posts, across all of the documents in the entire collection.
Any help?
You can try using aggregation query:
Steps:
1> Match Specific doc
2> Stretch docs of its array using $unwind.
3> Sort using the time field from the posts.
4> Select fields , if specific fields needs to be shown.
5> Add limit, how many docs you want.
<YOUR_MODEL>.aggregate([
{$match:{
"movie": 433249 //you may add find conditions here, otherwise you can keep {} or remove $match from here
}},
{$unwind:"$posts"}, //this will make the each array element with different different docs.
{$sort:{"posts. time":1}}, // sort using the date field now, depends on your requirement use -1 /1
{$project:{posts:1}}, //select docs only from posts field. [u can remove if you want every element, or may modify]
{$limit:10} //you want only last 10 posts
]).exec();
let me know if you still having any issue or getting any error.
would love answer.

How to join two collection in mongo without lookup

I have two collection, there name are post and comment.
The model structure is in the following.
I want to use aggregation query post and sort by comments like length sum, currently I can query a post comments like length sum in the following query statement.
My question is how can I query post and join comment collection in Mongo version 2.6. I know after Mongo 3.2 have a lookup function.
I want to query post collection and sort by foreign comments likes length. Is it have a best way to do this in mongo 2.6?
post
{
"_id": ObjectId("5a39e22c27308912334b4567"),
"uid": "0",
"content": "what is hello world mean?",
}
comment
/* 1 */
{
"_id": ObjectId("5a595d8c2703892c3d8b4567"),
"uid": "1",
"post_id": "5a39e22c27308912334b4567",
"comment": "hello world",
"like": [
"2"
]
}
/* 2 */
{
"_id": ObjectId("5a595d8c2703892c3d8b4512"),
"uid": "2",
"post_id": "5a39e22c27308912334b4567",
"comment": "hello stackoverflow",
"like": [
"1",
"2"
]
}
Query a post comments like sum
db.getCollection('comment').aggregate([
{
"$match": {
post_id: "5a39e22c27308912334b4567"
}
},
{
"$project": {
"likeLength": {
"$size": "$like"
},
"post_id": "$post_id"
}
},
{
"$group": {
_id: "$post_id",
"likeLengthSum": {
"$sum": "$likeLength"
}
}
}
])
There is no "best" way to query, as it'll really depend on your specific needs, but... you cannot perform a single query across multiple collections (aside from the $lookup aggregation pipeline function in later versions, as you already are aware).
You'll need to make multiple queries: one to your post collection, and one to your comment collection.
If you must perform a single query, then consider storing both types of documents in a single collection (with some identifier property to let you filter on either posts or comments, within your query).
There is no other way to join collections in the current MongoDB v6 without $lookup,
I can predict two reasons that causing you the issues,
The $lookup is slow and expensive - How to improve performance?
$lookup optimization:
Follow the guideline provided in the documentation
Use indexs:
You can use the index on the reference collection's fields, as per your sample data you can create an index for post_id field, an index for uid field, or a compound index for both the fields on the basis of your use cases
You can read more about How to Improve Performance with Indexes and Document Filters
db.comment.createIndex({ "post_id": -1 });
db.comment.createIndex({ "uid": -1 });
// or
db.comment.createIndex({ "post_id": -1, "uid": -1 });
Document Filters:
Use the $match, $limit, and $skip stages to restrict the documents that enter the pipeline
You can refer to the documentation for more detailed examples
{ $skip: 0 },
{ $limit: 10 } // as per your use case
Limit the $lookup result:
Try to limit the result of lookup by $limit stage,
Try to coordinate or balance with improved query and the UI/Use cases
You want to avoid $lookup - How to improve the collection schema to avoid $lookup?
Store the analytics/metrics:
If you are trying to get the total counts of the comments in a particular post then you must store the total count in the post collection whenever you get post get a new comment
{
"_id": ObjectId("5a39e22c27308912334b4567"),
"uid": "0",
"content": "what is hello world mean?",
// new fields
"total_comments": 10
}
Store minimum reference data:
If you want to show the comments of a particular post, you can limit the result for ex: show 5 comments per post
You can also store a max of 5 latest comments in the post collection to avoid the $lookup, whenever you get the latest comment then add it and just remove the oldest comment from 5 comments
{
"_id": ObjectId("5a39e22c27308912334b4567"),
"uid": "0",
"content": "what is hello world mean?",
// new fields
"total_comments": 10,
"comments": [
{
"_id": ObjectId("5a595d8c2703892c3d8b4567"),
"uid": "1",
"comment": "hello world"
},
{
"_id": ObjectId("5a595d8c2703892c3d8b4512"),
"uid": "2",
"comment": "hello stackoverflow"
}
]
}
Must read about Reduce $lookup Operations
Must read about Improve Your Schema

Not sure how to save in MongoDB

I am trying to push to a sub-collection for a MongoDB and I am not sure how to go about it.
So, my post collection structure:
{
"_id": "58a3189b67476b420e465f8b",
"postID": "123456",
"comments": [
{
"subComments": [],
"comment": "This is a test",
"commentID": 1
}
]
}
So, what I want to do is find the above document using findOneAndUpdate and then select this particular comment (commentID == 1), and then push into the subComments of it.
I can see how you can findOneAndUpdate by it's postID and add comments by pushing in Mongoose but how do I find the post, and then the comment by it's commentID and then push into it's subComment?
Thanks in advance guys! Very stuck on this last piece of the puzzle.
It's similar to other find queries.
You can try this:
Post.findOneAndUpdate({
"postID" : givenPostID,
"comments.commentID" : givenCommentID
},{
$push : {
"comment.$.subComments" : givenSubComment
}
},function(err,result){...});
comments.commentID: givenCommentID will find the comment element with commentID == givenCommentID.
comment.$.subComments will push givenSubComment to the subComments array of the matched element of the comments array (matched based on commentID).
For more information Please read MongoDB $(update) operator documentation.

Pushing an item into an array of an array in MongoDB

I've been struggling with this for the last few hours and can't get it to work properly. Read all over Stackoverflow, Google and still can't implement it correctly on my case.
Basically, my document in MongoDB looks like this:
{
"_id": "H2RLYBniXZ7Fkszpu",
"userId": "FmMsoqXxJ8Nd4DRm5",
"Data": {
"name": "This is just a test.",
"summary": "sdadsdas",
"testId": "EyWcg1vS-",
"questions": [
{
"type": "test",
"key": "E1eol1DS-",
"options": []
}
],
}
}
My goal is to $push and item into the "Data.questions.options" array if that particular object's key in that array is equal to the input I request.
I tried doing this many different ways, including using the $in operator, doing things like this:
Db.fix.update({_id: "H2RLYBniXZ7Fkszpu"}, {$push: {"Data.questions.$.options": "option"}}); // Pushing the string "option" into the options array
I was also looking into the $cond operator for MongoDB to see if I can add conditionals, but it's only for aggregation. I'm not sure how to proceed with this. I'm not sure it's even possible doing this with such a nested array.
You were very close. The key part with the $ positional operator is that the array field must appear as part of the query document.
Try this:
db.Fix.update({_id: "H2RLYBniXZ7Fkszpu", "Data.questions.key":"E1eol1DS-"}, {$push: {"Data.questions.$.options": "option"}});

Should I use selector or views in Cloudant?

I'm having confusion about whether to use selector or views, or both, when try to get a result from the following scenario:
I need to do a wildsearch for a book and return the result of the books plus the price and the details of the store branch name.
So I tried using selector to do wildsearch using regex
"selector": {
"_id": {
"$gt": null
},
"type":"product",
"product_name": {
"$regex":"(?i)"+search
}
},
"fields": [
"_id",
"_rev",
"product_name"
]
I am able to get the result. The idea after getting the result is to use all the _id's from the result set and query to views to get more details like price and store branch name on other documents, which I feel is kind of odd and I'm not certain is that the correct way to do it.
Below is just the idea once I get the result of _id's and insert it as a "productId" variable.
var input = {
method : 'GET',
returnedContentType : 'json',
path : 'test/_design/app/_view/find_price'+"?keys=[\""+productId+"\"]",
};
return WL.Server.invokeHttp(input);
so I'm asking for input from an expert regarding this.
Another question is how to get the store_branch_name? Can it be done in a single view where we can get the product detail, prices and store branch name? Or do I need to have several views to achieve this?
expected result
product_name (from book document) : Book 1
branch_name (from branch array in Store document) : store 1 branch one
price ( from relationship document) : 79.9
References:
Book
"_id": "book1",
"_rev": "1...b",
"product_name": "Book 1",
"type": "book"
"_id": "book2",
"_rev": "1...b",
"product_name": "Book 2 etc",
"type": "book"
relationship
"_id": "c...5",
"_rev": "3...",
"type": "relationship",
"product_id": "book1",
"store_branch_id": "Store1_branch1",
"price": "79.9"
Store
{
"_id": "store1",
"_rev": "1...2",
"store_name": "Store 1 Name",
"type": "stores",
"branch": [
{
"branch_id": "store1_branch1",
"branch_name": "store 1 branch one",
"address": {
"street": "some address",
"postalcode": "33490",
"type": "addresses"
},
"geolocation": {
"coordinates": [
42.34493,
-71.093232
],
"type": "point"
},
"type": "storebranch"
},
{
"branch_id": "store1_branch2",
"branch_name":
**details ommit...**
}
]
}
In Cloudant Query, you can specify two different kinds of indexes, and it's important to know the differences between the two.
For the first part of your question, if you're using Cloudant Query's $regex operator for wildcard searches like that, you might be better off creating a Cloudant Query index of type "text" instead of type "json". It's in the Cloudant docs, but see the intro blog post for details: https://cloudant.com/blog/cloudant-query-grows-up-to-handle-ad-hoc-queries/ There's a more advanced post on this that covers the tradeoffs between the two types of indexes https://cloudant.com/blog/mango-json-vs-text-indexes/
It's harder to address the second part of your question without understanding how your application interacts with your data, but there are a couple pieces of advice.
1) Consider denormalizing some of this information so you're not doing the JOINs to begin with.
2) Inject more logic into your document keys, and use the traditional MapReduce View indexing system to emit a compound key (an array), that you can use to emulate a JOIN by taking advantage of the CouchDB/Cloudant index sorting rules.
That second one's a mouthful, but check out this example on YouTube: https://youtu.be/0al1KnCKjlA?t=23m39s
Here's a preview (example map function) of what I'm talking about:
'map' : function(doc)
{
if (doc.type==="user") {
emit( [doc._id], null );
}
else if (doc.type==="edge:follower") {
emit( [doc.user, doc.follows], {"_id":doc.follows} );
}
}
The resulting secondary index here would take advantage of the rules outlined in http://wiki.apache.org/couchdb/View_collation -- that strings sort before arrays, and arrays sort before objects. You could then issue range queries to emulate the results you'd get with a JOIN.
I think that's as much detail that's appropriate for here. Hope it helps!