Mongoose Mongodb sorting and limiting query of subdocuments - mongodb

I've got the following design Schema:
{
participants: [String],
conversations: [{
date: Date
messages: [String]
}]
}
Now i want to get the 6 newest converations. I have tried a lot but i can't seem to find the solution. I can sort by subdocuments, but at the end if 1 document has the 6 newest conversations the query will end up giving me this one document plus 5 others. I woud like to get an array like this at the end of the query or be able to get this particular information:
[{date:'Adate', messages:[]},{date:'Adate2',messages:[]}]
Thanks for your help!

Actually this is not possible with the single query if you are a using a mongoDB version LESS than 3.1.6.
$Slice is supported in aggregation pipeline in the mongoDB version 3.1.6 and above
If your mongoDB version is below 3.1.6, then you can try the below piece of code :
db.collection.aggregate([
{ $unwind : "conversations"},
{ $sort : {_id : 1, conversations.date : -1}},
{ $group: { _id : "$_id"} , conversations : { $push : "$conversations"}, participants : {$first : "$participants"} },
{ $project : { _id : 1, conversations : 1, participants : 1 } }
]).forEach( function(doc)
{
if( doc.conversations.length > 6)
{
var count = doc.conversations.length - 6;
doc.conversations.splice(6, count );
}
}
)
There is a similar question on the StackOverflow for the version below 3.1.6, Please check the link.
For the mongoDb Version 3.1.6 and above, you can use $Slice in aggregation pipeline to limit the contents of array.
Try the below code :
db.collection.aggregate([
{ $unwind : "conversations"},
{ $sort : {_id : 1, conversations.date : -1}},
{ $group: { _id : "$_id"} , conversations : { $push : "$conversations"}, participants : {$first : "$participants"} },
{ $project :
{
_id : 1,
participants : 1,
newconversations :
{
conversations : { $slice : 6 }
}
}
}
])

Related

Display field document in mongoDB after execute Query aggregate

This is an example of a data document
{
"_id" : ObjectId("5f437e7846103b2ad0fc5d7d"),
"order_no" : "O-200824-AGFJDQW",
"shipment_no" : "S-200824-AGWCRRM",
"member_id" : 2200140,
"ponta_id" : "9990010100280214",
"plu" : 14723,
"product_name" : "AQUA Air Mineral Botol Air Pet 600ml",
"qty" : 2,
"store_id" : "TD46",
"stock_on_hand" : 0,
"transaction_date" : ISODate("2020-08-24T08:28:29.931Z"),
"created_date" : ISODate("2020-08-24T08:46:48.441Z")
}
this is the data query that I run
var bulan = 12 //month is written with number. example: August = 8
db.log_stock_oos.aggregate([
{
$project: {
month: {
$month: '$transaction_date'
}
}
},
{
$match: {month: bulan}
}
]);
but the result is like this after I run the query
{
"_id" : ObjectId("5f44689607fe453fbfba433e"),
"month" : 12
}
how to make the output exactly like the document display that I attached above??
this is my reference
When you use the projection, its kind of if your value 1 then include the field, if your value 0 then exclude the field from the whole documents. Projection
You can do two things
Use the projection
db.collection.aggregate([
{
$project: {
month: {
$month: "$transaction_date"
},
order_no: 1,
shipment_no: 1,
member_id: 1,
//other fields like above with the value 1
}
},
// match stages
])
Use $addFields
use $addFields incited of $project in your code. If will create a filed if not exists in your document, else it will overwrite the field

MongoDB $divide on aggregate output

Is there a possibility to calculate mathematical operation on already aggregated computed fields?
I have something like this:
([
{
"$unwind" : {
"path" : "$users"
}
},
{
"$match" : {
"users.r" : {
"$exists" : true
}
}
},
{
"$group" : {
"_id" : "$users.r",
"count" : {
"$sum" : 1
}
}
},
])
Which gives an output as:
{ "_id" : "A", "count" : 7 }
{ "_id" : "B", "count" : 49 }
Now I want to divide 7 by 49 or vice versa.
Is there a possibility to do that? I tried $project and $divide but had no luck.
Any help would be really appreciated.
Thank you,
From your question, it looks like you are assuming result count to be 2 only. In that case I can assume users.r can have only 2 values(apart from null).
The simplest thing I suggest is to do this arithmetic via javascript(if you're using it in mongo console) or in case of using it in progam, use the language you're using to access mongo) e.g.
var results = db.collection.aggregate([theAggregatePipelineQuery]).toArray();
print(results[0].count/results[1].count);
EDIT: I am sharing an alternative to above approach because OP commented about the constraint of not using javascript code and the need to be done only via query. Here it is
([
{ /**your existing aggregation stages that results in two rows as described in the question with a count field **/ },
{ $group: {"_id": 1, firstCount: {$first: "$count"}, lastCount: {$last: "$count"}
},
{ $project: { finalResult: { $divide: ['$firstCount','$lastCount']} } }
])
//The returned document has your answer under `finalResult` field

MongoDB Group by field and show array of grouped items?

I have a collection of Projects in where projects are like this:
{
"_id" : ObjectId("57e3e55c638cb8b971"),
"allocInvestor" : "Example Investor",
"fieldFoo" : "foo bar",
"name" : "GTP 3 (Roof Lease)"
}
I want to receive a list of projects grouped by allocInvestor field and only show fields: name and id
If I use aggregate and $group like this:
db.getCollection('projects').aggregate([
{"$group" : {
_id:"$allocInvestor", count:{$sum:1}
}
}
])
I receive a count of project per allocInvestor but I need is to receive the list of allocInvestor with subarray of projects per allocInvestor.
I'm using meteor by the way in case that helps. But I first want to get the query right on mongodb then try for meteor.
You can use $push or $addToSet to create a list of name and _id per every group.
$push allows duplicates and $addToSet does not add an element to the list again, if it is already there.
db.getCollection('projects').aggregate([
{ "$group" : { _id : "$allocInvestor",
count : {$sum : 1},
"idList" : {"$addToSet" : "$_id"},
"nameList" : {"$addToSet":"$name"}
}
}
]);
To get the name and _id data in a single list:
db.getCollection('projects').aggregate([
{ "$group" : { _id : "$allocInvestor", "projects" : {"$addToSet" : {id : "$_id", name: "$name"}}}},
{"$project" : {"_id" : 0, allocInvestor : "$_id", "projects" : 1 }}
]);
Use the $$ROOT operator to reference the entire document and then use project to eliminate the fields that you do not require.
db.projects.aggregate([
{"$group" : {
"_id":"$allocInvestor",
"projects" : {"$addToSet" : "$$ROOT"}
}
},
{"$project" : {
"_id":0,
"allocInvestor":"$_id",
"projects._id":1
"projects.name":1
}
}
])

Return latest record from subdocument in Mongodb

Let's say i want to return the latest inserted document from the subdocument. I want to be able to return the second record within the tags array w/ the _id of 54a1845def7572cd0e3fe288
So I far I have this query but it returns all values in the tags array.
db.modules.findOne({_id:"ui","svn_branches.branch":"Rocky"},{"svn_branches.$":1})
Mongodb array:
{
"_id" : "ui",
"svn_branches" : [
{
"updated_at" : ISODate("2013-06-12T20:48:17.297Z"),
"branch" : "Rocky",
"revision" : 0,
"tags" : [
{
"_id" : ObjectId("54a178b8ef7572d30e3fe288"),
"commit_message" : "r277 | ssmith | 2015-02-11 17:43:23 -0400 (Wed, 11 Feb 2015)",
"latest_tag" : "20150218r1_6.32_abc",
"revision" : 1,
"tag_revision_number" : "280",
"updated_at" : ISODate("2015-02-18T19:54:54.062Z")
},
{
"_id" : ObjectId("54a1845def7572cd0e3fe288"),
"commit_message" : "r271 | sam | 2dskjh\n",
"latest_tag" : "20150218r2_6.32_abc",
"revision" : 2,
"tag_revision_number" : "281",
"updated_at" : ISODate("2015-02-19T19:54:54.062Z")
}
]
}
]
}
Simple Solution
Let say we have a category as a document and items as a subdocument.
// find document from collection
const category = await Category.findOne({ _id:'$hec453d235xhHe4Y' });
// fetch last index of sub-document
const lastItemIndex = category.items.length - 1;
// here is the last item of sub-document
console.log(category.items[lastItemIndex]);
as mongodb inserted the latest sub-document at last index, so we need to find the last index for the latest sub-doc.
Queries in MongoDB do not return subdocuments (or, as in your case, subdocuments of subdocuments). They match and return the the documents in the collection. The documents' shape can be changed a bit by projection, but it's limited. If you want to find the latest tag commonly, you probably want to make your documents represent tags. Having an array in an array is generally a bad idea in MongoDB, too.
If this is an uncommon operation, and one that doesn't need to be particularly fast, you can use an aggregation:
db.modules.aggregate([
{ "$unwind" : "$svn_branches" },
{ "$unwind" : "$svn_branches.tags" },
{ "$sort" : { "svn_branches.tags.updated_at" : -1 } },
{ "$group" : { "_id" : "$_id", "latest_tag" : { "$first" : "$svn_branches.tags" } } }
])
I needed to find the last entry of subdocuments and I managed to make it to work with the $slice projection operator: mondodb.com > $slice (projection)
db.modules.find({_id:'ui', 'svn_branches.branch':'Rocky'},
{ 'svn_branches.tags': {$slice:-1} } )
I had only one level, if this doesn't work, please let me know.

MongoDB fetch documents with sort by count

I have a document with sub-document which looks something like:
{
"name" : "some name1"
"like" : [
{ "date" : ISODate("2012-11-30T19:00:00Z") },
{ "date" : ISODate("2012-12-02T19:00:00Z") },
{ "date" : ISODate("2012-12-01T19:00:00Z") },
{ "date" : ISODate("2012-12-03T19:00:00Z") }
]
}
Is it possible to fetch documents "most liked" (average value for the last 7 days) and sort by the count?
There are a few different ways to solve this problem. The solution I will focus on uses mongodb's aggregation framework. First, here is an aggregation pipeline that will solve your problem, following it will be an explanation/breakdown of what is happening in the command.
db.testagg.aggregate(
{ $unwind : '$likes' },
{ $group : { _id : '$_id', numlikes : { $sum : 1 }}},
{ $sort : { 'numlikes' : 1}})
This pipeline has 3 main commands:
1) Unwind: this splits up the 'likes' field so that there is 1 'like' element per document
2) Group: this regroups the document using the _id field, incrementing the numLikes field for every document it finds. This will cause numLikes to be filled with a number equal to the number of elements that were in "likes" before
3) Sort: Finally, we sort the return values in ascending order based on numLikes. In a test I ran the output of this command is:
{"result" : [
{
"_id" : 1,
"numlikes" : 1
},
{
"_id" : 2,
"numlikes" : 2
},
{
"_id" : 3,
"numlikes" : 3
},
{
"_id" : 4,
"numlikes" : 4
}....
This is for data inserted via:
for (var i=0; i < 100; i++) {
db.testagg.insert({_id : i})
for (var j=0; j < i; j++) {
db.testagg.update({_id : i}, {'$push' : {'likes' : j}})
}
}
Note that this does not completely answer your question as it avoids the issue of picking the date range, but it should hopefully get you started and moving in the right direction.
Of course, there are other ways to solve this problem. One solution might be to just do all of the sorting and manipulations client-side. This is just one method for getting the information you desire.
EDIT: If you find this somewhat tedious, there is a ticket to add a $size operator to the aggregation framework, I invite you to watch and potentially upvote it to try and speed to addition of this new operator if you are interested.
https://jira.mongodb.org/browse/SERVER-4899
A better solution would be to keep a count field that will record how many likes for this document. While you can use aggregation to do this, the performance will likely be not very good. Having a index on the count field will make read operation fast, and you can use atomic operation to increment the counter when inserting new likes.
You can use this simplify the above aggregation query by the following from mongodb v3.4 onwards:
> db.test.aggregate([
{ $unwind: "$like" },
{ $sortByCount: "$_id" }
]).pretty()
{ "_id" : ObjectId("5864edbfa4d3847e80147698"), "count" : 4 }
Also as #ACE said you can now use $size within a projection instead:
db.test.aggregate([
{ $project: { count: { $size : "$like" } } }
]);
{ "_id" : ObjectId("5864edbfa4d3847e80147698"), "count" : 4 }