How to query recent comments in Mongodb - mongodb

The the post document looks like this:
{
...
comments: [{
_id:...
body:...
createDate:...
},
...
]
}
How do I get recent 10 comments from the collection?

If your comments are always in a predictable order (i.e. newest first, or newest last), then you can use the $slice operator to return just a subset of the full comments field when querying:
test> db.foo.save({name: "hello", comments: [1, 2, 3, 4, 5]})
test> db.foo.find({}, {comments: {$slice: 3}})
{ "_id" : ObjectId("4ec7d1c8e72da9b6f31e2528"), "name" : "hello", "comments" : [ 1, 2, 3 ] }
test> db.foo.find({}, {comments: {$slice: -3}})
{ "_id" : ObjectId("4ec7d1c8e72da9b6f31e2528"), "name" : "hello", "comments" : [ 3, 4, 5 ] }
You can read more about controlling the returned fields at http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields

There is no way to partially select the items from embedded document. No matter what it will return the entire array of document. You have to do the filter in your application code. Thats the only way.
But i recommend to have a separate collection for comments. That way you can skip & limit the set.

Related

MongoDB Aggregation - Does $unwind order documents the same way as the nested array order

I am wandering whether using $unwind operator in aggregation pipeline for document with nested array will return the deconstructed documents in the same order as the order of the items in the array.
Example:
Suppose I have the following documents
{ "_id" : 1, "item" : "foo", values: [ "foo", "foo2", "foo3"] }
{ "_id" : 2, "item" : "bar", values: [ "bar", "bar2", "bar3"] }
{ "_id" : 3, "item" : "baz", values: [ "baz", "baz2", "baz3"] }
I would like to use paging for all values in all documents in my application code. So, my idea is to use mongo aggregation framework to:
sort the documents by _id
use $unwind on values attribute to deconstruct the documents
use $skip and $limit to simulate paging
So the question using the example described above is:
Is it guaranteed that the following aggregation pipeline:
[
{$sort: {"_id": 1}},
{$unwind: "$values"}
]
will always result to the following documents with exactly the same order?:
{ "_id" : 1, "item" : "foo", values: "foo" }
{ "_id" : 1, "item" : "foo", values: "foo2" }
{ "_id" : 1, "item" : "foo", values: "foo3" }
{ "_id" : 2, "item" : "bar", values: "bar" }
{ "_id" : 2, "item" : "bar", values: "bar2" }
{ "_id" : 2, "item" : "bar", values: "bar3" }
{ "_id" : 3, "item" : "baz", values: "baz" }
{ "_id" : 3, "item" : "baz", values: "baz2" }
{ "_id" : 3, "item" : "baz", values: "baz3" }
I also asked the same question in the MongoDB community forum . An answer that confirms my assumption was posted from a member of MongoDB stuff.
Briefly:
Yes, the order of the returned documents in the example above will always be the same. It follows the order from the array field.
In the case that you do run into issues with order. You could use includeArrayIndex to guarantee order.
[
{$unwind: {
path: 'values',
includeArrayIndex: 'arrayIndex'
}},
{$sort: {
_id: 1,
arrayIndex: 1
}},
{ $project: {
index: 0
}}
]
From what I see at https://github.com/mongodb/mongo/blob/0cee67ce6909ca653462d4609e47edcc4ac5c1a9/src/mongo/db/pipeline/document_source_unwind.cpp
The cursor iterator uses getNext() method to unwind an array:
DocumentSource::GetNextResult DocumentSourceUnwind::doGetNext() {
auto nextOut = _unwinder->getNext();
while (nextOut.isEOF()) {
.....
// Try to extract an output document from the new input document.
_unwinder->resetDocument(nextInput.releaseDocument());
nextOut = _unwinder->getNext();
}
return nextOut;
}
And the getNext() implemenation relies on array's index:
DocumentSource::GetNextResult DocumentSourceUnwind::Unwinder::getNext() {
....
// Set field to be the next element in the array. If needed, this will automatically
// clone all the documents along the field path so that the end values are not shared
// across documents that have come out of this pipeline operator. This is a partial deep
// clone. Because the value at the end will be replaced, everything along the path
// leading to that will be replaced in order not to share that change with any other
// clones (or the original).
_output.setNestedField(_unwindPathFieldIndexes, _inputArray[_index]);
indexForOutput = _index;
_index++;
_haveNext = _index < length;
.....
return _haveNext ? _output.peek() : _output.freeze();
}
So unless there is anything upstream that messes with document's order the cursor should have unwound docs in the same order as subdocs were stored in the array.
I don't recall how merger works for sharded collections and I imagine there might be a case when documents from other shards are returned from between 2 consecutive unwound documents. What the snippet of the code guarantees is that unwound document with next item from the array will never be returned before unwound document with previous item from the array.
As a side note, having million items in an array is quite an extreme design. Even 20-bytes items in the array will exceed 16Mb doc limit.

MongoDB: Retrieving an entire array from a specific document

I have set up some test data in mongoDB that has the following form:
{
"_id" : ObjectId("579ab44c0f9f0dc3aeec42ab"),
"name" : "Bob",
"references" : [ 1, 2, 3, 4, 5, 6 ]
}
{
"_id" : ObjectId("579ab7a20f9f0dc3aeec42ac"),
"name" : "Jeff",
"references" : [ 11, 12, 13, 14, 15 ]
}
I want to be able to return the references array only for Bob. Currently I am able to return the complete Document for Bob with the following query:
db.test_2.find({"name" : "Bob"}, bob).pretty()
Basically the general question is how to return an array for a single document in a collection in MongoDB? If I could get any help for this that would be much appreciated!
You can add a projection document to limit the fields returned.
For example:
db.products.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 } )
Take a look at the documentation:
https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find
The other option would be to select the field from the given document (if you use it in a loop for example).
In any case mongo will return a json document which you need to take the array from.
Regards
Jony
You can do this...
db.test_2.findOne({ "name": "Bob" }).select({ references: 1, _id: 1 })
P.S this is with MongoDB v4.2
db.test_2.find({ "name": "Bob" }, { "references": 1 });

Multiple update in a document in MongoDB

I am trying to update multiple nested documents in a document in mongoDB.
Say my data is:
{
"_id" : "ObjectId(7df78ad8902c)",
"title" : "Test",
"img_url" : "[{s: 1, v:1}, {s: 2, v: 2}, {s: 3, v: 3}]",
"tags" : "['mongodb', 'database', 'NoSQL']",
"likes" : "100"
}
I want to update v to 200 for s = 1 and s= 2 in img_url list.
It is easy to update v for any single s.
Is there any way to update multiple documents satisfying some criteria.
I tried:
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } });
and
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } }, {mulit: true});
Some sources are suggesting it is not possible to do so.
Multiple update of embedded documents' properties
https://jira.mongodb.org/browse/SERVER-1243
Am I missing something ?
For the specific case/example you have here. You are specifying an _id which means you are to update only one with that specific _id.
to update img_url try without the _id; something like this:
db.test.update({}, {"$set":{"img_url.0":{s:1, v:400}}}, {multi:true})
db.test.update({}, {"$set":{"img_url.1":{s:2, v:400}}}, {multi:true})
0 and 1 in img_url are the array indexes for s:1 and s:2
in order to update based on specific criteria you need to set the attribute you need on the first argument. say for example, to update all documents that have likes greater than 100 increment by 1 you do (assuming likes type is int...):
db.people.update( { likes: {$gt:100} }, {$inc :{likes: 1}}, {multi: true} )
hope that helps

How to query nested document in mongodb?

I have document with nested document reviews:
{
"_id" : ObjectId("53a5753937c2f0ef6dcd9006"),
"product" : "Super Duper-o-phonic",
"price" : 11000000000,
"reviews" : [
{
"user" : "fred",
"comment" : "Great!",
"rating" : 5
},
{
"user" : "Tom",
"comment" : "Great again!",
"rating" : 5
},
{
"user" : "Tom",
"comment" : "I agree with fred somewhat",
"rating" : 4
}
]
}
I want to find only those reviews whose rating is 5.
Final query should select product price and two documents from reviews whose rating is 5.
The last query I tried is :
db.testData.find({'reviews':{$elemMatch:{'rating':{$gte:5}}}}).pretty()
It's strange but it doesn't work.
How to do this in mongodb?
If you only want a single sub-doc from reviews whose rating is 5, you can use the $ positional projection operator to include the first element of reviews that matches your query:
db.test.find({'reviews.rating': 5}, {product: 1, price: 1, 'reviews.$': 1})
If you want all reviews elements with a rating of 5 (instead of just the first) you can use aggregate instead of find:
db.test.aggregate([
// Only include docs with at least one 5 rating review
{$match: {'reviews.rating': 5}},
// Duplicate the docs, one per reviews element
{$unwind: '$reviews'},
// Only include the ones where rating = 5
{$match: {'reviews.rating': 5}},
// Only include the following fields in the output
{$project: {product: 1, price: 1, reviews: 1}}])
Take a look up here: MongoDB - how to query for a nested item inside a collection?
Just in case you thought about this:
If you try to accomplish this with $elemMatchit will jsut return the first matching review.
http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/

Nested Query Mongodb

I have this beautiful Json , and I'm trying with the powerful mongodb query to get all comments with file_id 12....so this is what I would like have back [4,5,7,10,11,15].
I tried with this query but the file_id it's completely ignored by the engine:
db.collection.distinct("changes.comments",
{"my_uuid":"bf48e757-1a65-4546-bf24-2bb001effddd",
"changes":{$elemMatch:{file_id:12}} }
)
Output:
{
"_id" : ObjectId("5342bf796b03d7ffc834afcc"),
"my_uuid" : "bf48e757-1a65-4546-bf24-2bb001effddd",
"changes" : [
{
"file_id" : 12,
"version" : 1,
"comments" : [
4,
5,
7
],
"lastseen" : 1394640549
},
{
"file_id" : 12,
"version" : 2,
"comments" : [
10,
11,
15
],
"lastseen" : 1394640511
},
{
"file_id" : 234,
"version" : 1,
"comments" : [
100,
110,
150
],
"lastseen" : 1394640555
}
]
}
Thanks in advance
You can use the aggregation framework to achieve what you what. Although the query looks complex for what you are trying to do, it's simple once you get a hang of it.
db.collection.aggregate([
// Get only documents where "my_uuid" equals "bf48e757-1a65-4546-bf24-2bb001effddd"
{"$match":{"my_uuid":"bf48e757-1a65-4546-bf24-2bb001effddd"}},
// Unwind the "changes" array
{"$unwind":"$changes"},
// Get only elements of the "changes" array where "file_id" equals 12
{"$match":{"changes.file_id":12}},
// Unwind the "comments" array
{"$unwind":"$changes.comments"},
// Group by _id and add the comments to array only if not already present
{"$group":{_id:"$_id", comments:{$addToSet:"$changes.comments"}}},
// Cleanup the output
{"$project":{_id:0, comments:1}}
])
Output:
{
"result" : [
{
"comments" : [
4,
5,
7,
10,
11,
15
]
}
],
"ok" : 1
}
EDIT: Including my_uuid in the results is fairly straight-forward. We just need to group by my_uuid instead of _id:
db.collection.aggregate([
{"$match":{"my_uuid":"bf48e757-1a65-4546-bf24-2bb001effddd"}},
{"$unwind":"$changes"},
{"$match":{"changes.file_id":12}},
{"$unwind":"$changes.comments"},
{"$group":{_id:"$my_uuid", comments:{$addToSet:"$changes.comments"}}},
{"$project":{_id:0, my_uuid:"$_id", comments:1}}
])
Currently there is no straight forward way of pulling out only the matching document from an array. The $elemMatch operator will only ensure that at least one of the documents within the array satisfies the condition provided by you. The query will however, always return the entire document. One way to achieve what you are looking for is -
db.sample4.aggregate({$unwind:"$changes"},{$match:{"changes.file_id":12}},{$project:{"changes.comments":1,"_id":0}});
These topics are covered here in stackoverflow, where map-reduce approach as well is listed to achieve this. If the requirement was to return the first matching document, the you could have projected using changes.comments.$:1 Eg. - db.sample4.find({"changes":{$elemMatch:{"file_id":12}} },{"changes.comments.$":1} )