Reputation: 1088
I'd like to make a simple "chat" where there is a post and answers for them (only 1 deep), I decided to go this way, so a single document would look like this
{
_id: ObjectId(...),
posted: date,
author: "name",
content: "content",
comments: [
{ posted: date,
author: "name2"},
content: '...'
}, ... ]
}
My question is how should I search in the content this way? I'd first need to look for a match in the "parent" content, then the contents in the comments list. How should I do that?
Upvotes: 1
Views: 91
Reputation: 1636
If you can search for a regex within each content, you could use:
{$or : [
{'content':{$regex:'your search regex'}},
{'comments' : { $elemMatch: { 'content':{$regex:'your search regex'}}}]}
Please note that when fetching for results, upon a match to either a parent or a child you will receive the entire mongo document, containing both the parent and the children.
If you want to avoid this (to be sure what you've found), you can possibly run first a regex query on the parent only, and then on the children only, instead of the single $or
query.
For more details on $elemMatch
take a look at: docs.mongodb.org/manual/reference/operator/query/elemMatch
Upvotes: 2
Reputation: 151092
As was stated in the comments earlier, the basic query to "find" is just a simple matter of using $or
here, which also does short circuit to match on the first condition where that returns true
. There is only one array element here so no need for $elemMatch
, but just use "dot notation" since multiple field matches are not required:
db.messages.find({
"$or": [
{ "content": { "$regex": ".*Makefile.*" } },
{ "comments.content": { "$regex": ".*Makefile.*" } }
]
})
This does actually match the documents that would meet those conditions, and this is what .find()
does. However what you seem to be looking for is something a little "funkier" where you want to "discern" between a "parent" result and a "child" result.
That is a little out of the scope for .find()
and such manipulation is actually the domain of other operations with MongoDB. Unfortunately as you are looking for "part of a string" to match as your condition, doing a "logical" equivalent of a $regex
operation does not exist in something such as the aggregation framework. It would be the best option if it did, but there is no such comparison operator for this, and a logical comparison is what you want. The same would apply to "text" based searches, as there is still a need to discern the parent from the child.
Not the most ideal approach since it does involve JavaScript processing, but the next best option here is mapReduce()
.
db.messages.mapReduce(
function() {
// Check parent
if ( this.content.match(re) != null )
emit(
{ "_id": this._id, "type": "P", "index": 0 },
{
"posted": this.posted,
"author": this.author,
"content": this.content
}
);
var parent = this._id;
// Check children
this.comments.forEach(function(comment,index) {
if ( comment.content.match(re) != null )
emit(
{ "_id": parent, "type": "C", "index": index },
{
"posted": comment.posted,
"author": comment.author,
"content": comment.content
}
);
});
},
function() {}, // no reduce as all are unique
{
"query": {
"$or": [
{ "content": { "$regex": ".*Makefile.*" } },
{ "comments.content": { "$regex": ".*Makefile.*" } }
]
},
"scope": { "re": /.*Makefile.*/ },
"out": { "inline": 1 }
}
)
Basically the same query to input as this does select the "documents" you want and really just using "scope" here is it makes it a little easier to pass in the regular expression as an argument without re-writing the JavaScript code to include that value each time.
The logic there is simple enough, just to each "de-normalized" element you are testing to see if the regular expression condition was a match for that particular element. The results are returned "de-normalized" and discern between whether the matched element was a parent or a child.
You could take that further and not bother to check the children if the parent was a match just by moving that to else
. In the same way you could even just return the "first" child match by some means or another if that was your desire.
Anyhow this should set you on the path to whatever your final code looks like. But this is the basic approach to the only way to are going to get this distinction to be processed on the server, and client side post processing would follow much the same pattern.
Upvotes: 1