Group items by timeframe - node.js

I have a collection db.activities, each item of which has a dueDate. I need to present data in a following format, which basically a list of activities which are due today and this week:
{
"today": [
{ _id: 1, name: "activity #1" ... },
{ _id: 2, name: "activity #2" ... }
],
"thisWeek": [
{ _id: 3, name: "activity #3" ... }
]
}
I managed to accomplish this by simply querying for the last week's activities as a flat list and then grouping them with javascript on the client, but I suspect this is a very dirty solution and would like to do this on server.

look up mongo aggregation pipeline.
your aggregation has a match by date, group by date and a maybe a sort/order stage also by date.
lacking the data scheme it will be along the lines of
db.collection.aggregate([{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $group: {_id: "$duedate", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
if you want 'all' records remove the $match or use { $match: {} } as one does with find.
in my opinion, you cannot aggregate both by day and week within one command. the weekly one may be achieved by projecting duedate using mongos $dayOfWeek. along the lines of
db.collection.aggregate([
{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $project : { dayOfWeek: { $dayOfWeek: "$duedate" } },
{ $group: {_id: "$dayOfWeek", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
check out http://docs.mongodb.org/manual/reference/operator/aggregation/dayOfWeek/

Related

MongoDB - Mongoose find command to get only values from an embedded object

My Schema looks something like this.
{
_id: '1',
items: {
'id1': 'item1',
'id2': 'item2',
'id3': 'item3'
}
}
Following is the query
ItemModel.find({}, {
items: 1,
_id: 0
});
And the result of the find query is:
{ "items" : { "21" : "item21", "22" : "item22", "23" : "item23" } }
{ "items" : { "31" : "item31", "32" : "item32", "33" : "item33" } }
{ "items" : { "11" : "item11", "12" : "item32", "13" : "item13" } }
What I want is:
["item21", "item22", "item23",
"item31", "item32", "item33",
"item11", "item12", "item13"]
Currently, I am doing the processing on the node.js end for getting the above. I want to reduce the output payload size coming from MongoDB. The "items" key is redundant and the IDs mentioned are not required as well when I fetch it. Here, the IDs are quite small like 21, 22, 13, etc. but those are acutally 50 characters in length.
If not the above, any other efficient alternatives are also welcome.
One example of how to achieve that is the following aggregation:
[
{
$project: {
items: {
$objectToArray: '$items',
},
},
},
{ $unwind: '$items' },
{
$project: {
items: '$items.v',
},
},
{
$group: {
_id: null,
items: {
$push: '$items',
},
},
}
];
What this does is first we convert with $project & $objectToArray field to an array so that we could use $unwind. This way we'll have documents with different items. Now we convert with another $project to make it a string instead of an object (which would be { v: <value>, k: <value> }. And, finally, we $group them together.
Final result:
To get exactly that list, you'll need in your code to access items field, like result[0].items ([0] because aggregation will return an array).

mongodb aggregation with list of ids

I got the following aggregation:
It scans all the messages and groups them by a docId and returns only the last updated message in each group.
db.getCollection('Messages').aggregate([ { '$match': { docType: 'order' }}, { '$sort': { updatedAt: -1 } }, { '$group': { _id: '$docId', content: { '$first': '$content' }}}])
which returns -
[
{
"_id" : "some id1",
"content" : "some msg1
}
/* 11 */
{
"_id" : "some id2",
"content" : "some msg2"
}
...
]
It is working as intended (not sure about optimization).
But now I need to add another thing on top of that.
In the UI I got a list of documents and I need to show only the latest message for each. But I also got paging so I dont need to bring the last message for XXXXXX documents but only for 1 page.
So basically something like this -
.find({'docId':{$in:['doc1', 'doc2', 'doc3'...]}}) - if the page had 3 items
But I am not sure how to combine all of that together.
Message sample:
{
"_id": "11111"
"docType": "order",
"docId": "12345", - this is not unique there can be many messages for 1 docId
"content": "my message",
"updatedAt" "01/01/2020..."
}
Adding
{ '$match': { _id: { '$in': ["docId1", "docId2"]} }}
at the end did the trick!
edit:
or actually I think It might be better to add it as the first pipeline so:
db.getCollection('Messages').aggregate([ { '$match': { docId: { '$in': ["5d79cba1-925b-416b-9408-6f4429d7c107", "8e31c748-c86d-407e-8d83-9810c8e23e3e"]} }}, { '$match': { docType: 'order' }}, { '$sort': { cAt: -1 } }, { '$group': { _id: '$docId', content: { '$first': '$content' }}}])
Since I am adding those dynamically I ended up with 2 $match properties. I actually not so sure what difference does it make to use $match + $and vs having 2 different $match (optimization wise).

How can i use multiple group stages in mongodb aggregate query

Mongo query fails to return any input in case, I increase the number of group stages in my query. Below is the snippet of the query which I am using,
.group({
_id: "$date",
count: {
$sum: 1
},
})
/*
.group({
_id: "$joinDate",
count: {
$sum: 1
},
})
.group({
_id: "$applyDate",
count: {
$sum: 1
},
})*/
You can do by the following way
yourModel.aggregate([ { $group : { _id : "$date" } } ] )
This might help you to solve your problem.
#CsAlkemy, It is because the fields you are trying to use are not propagated after the $group stage.
For example, consider the following sample document and an aggregate query on the document,
{
"name": "SV",
"age": 21,
"school": "KV",
"city": "Ajmer"
}
Aggregate Query
db.temp.aggregate([
{
$group: {
"_id": "$school",
"count": {
$sum: 1
}
}
}])
Output
{ "_id" : "KV", "count" : 1 }
As you can see the fields which we get are only _id and count and the other fields name, age, school and city are lost and can't be used later.

how to group in mongoDB and return all fields in result

I am using aggregate method in mongoDB to group but when I use $group it returns the only field which I used to group. I have tried $project but it is not working either. I also tried $first and it worked but the result data is now in different format.
The response format I need looks like:
{
"_id" : ObjectId("5b814b2852d47e00514d6a09"),
"tags" : [],
"name" : "name here",
"rating" : "123456789"
}
and after adding $group in my query.response is like this, the value of _id changes. (and the $group is taking only _id, if i try any other keyword it throws an error of accumulator something. please explain this also.)
{
"_id" :"name here" //the value of _id changed to the name field which i used in $group condition
}
I have to remove the duplicates in name field, without changing any structure and fields. also I am using nodeJS with mongoose, so please provide the solution that works with it.
You can use below aggregation query.
$$ROOT to keep the whole document per each name followed by $replaceRoot to promote the document to the top.
db.col.aggregate([
{"$group":{"_id":"$name","doc":{"$first":"$$ROOT"}}},
{"$replaceRoot":{"newRoot":"$doc"}}
])
user2683814's solution worked for me but in my case, I have a counter accumulator when we replace the newRoot object, the count field is missing in the final stage so I've used $mergeObjects operator to get my count field back.
db.collection.aggregate([
{
$group: {
_id: '$product',
detail: { $first: '$$ROOT' },
count: {
$sum: 1,
},
},
},
{
$replaceRoot: {
newRoot: { $mergeObjects: [{ count: '$count' }, '$detail'] },
},
}])
When you group data on any database, it means you want to perform accumulated operation on the required field and the other field which will not be include in accumulated operation will be used in group like
db.collection.aggregate([{
$group: {
_id: { field1: "", field1: "" },
acc: { $sum: 1 }
}}]
here in _id object will contains all other fields which you want to hold.
for your data you can try this
db.collection.aggregate([{
$group: {
_id: "$name",
rating: { $first: "$rating" },
tags: { $first: "$tag" },
docid: { $first: "$_id" }
}
},
{
$project: {
_id: "$docid",
name: "$_id",
rating: 1,
tags: 1
}
}])
You can use this query
db.col.aggregate([
{"$group" : {"_id" : "$name","data" : {"$first" : "$$ROOT"}}},
{"$project" : {
"tags" : "$data.tags",
"name" : "$data.name",
"rating" : "$data.rating",
"_id" : "$data._id"
}
}])
I wanted to group my collection by groupById field and store it as key value pairs having key as groupById and value as all the items of that group.
db.col.aggregate([{$group :{_id :"$groupById",newfieldname:{$push:"$"}}}]).pretty()
This is working fine for me..

MongoDB: Trying to use aggregate $unwind to fetch embedded documents

I have a MongoDB collection [Users] each with an Array of embedded documents [Photos].
I would like to be able to have a page which lists recent photos, for example - the last 10 photos uploaded by any user.
Users
[
{
_id: ObjectID
name: "Matthew"
Photos: [
{
_id: ObjectID
url: "http://www.example.com/photo1.jpg"
created: Date("2013-02-01")
},
{
_id: ObjectID
url: "http://www.example.com/photo3.jpg"
created: Date("2013-02-03")
}
]
},
{
_id: ObjectID
name: "Bob"
Photos: [
{
_id: ObjectID
url: "http://www.example.com/photo2.jpg"
created: Date("2013-02-02")
},
{
_id: ObjectID
url: "http://www.example.com/photo4.jpg"
created: Date("2013-02-04")
}
]
}
]
My first attempt at solving this was to find all Users where Photos is not null, and sort it by the created date:
User.find({images:{$ne:[]}}).sort({"images.created":-1})
Unfortunately, this doesn't work as I need it to. It sorts Users by the most recent images, but still returns all images, and there is no way to limit the number of images returned by the function.
I started looking into aggregate, and it seems like it might be the solution I'm looking for, but I'm having trouble finding out how to get it to work.
Ideally, the type of result I would like returned would be like this:
results: [
{
_id: ObjectID (from Photo)
name: "Bob"
url: "http://www.example.com/photo4.jpg"
created: Date("2013-02-04")
}
{
_id: ObjectID (from Photo)
name: "Matthew"
url: "http://www.example.com/photo3.jpg"
created: Date("2013-02-03")
}
]
Each result should be a Photo, and I should be able to limit the results to a specific amount, and skip results for paged viewing.
Please let me know if you need any more information, and thank you for your responses.
You need aggregation framework:
db.users.aggregate(
{ $project: {"Photos" : 1, _id: 0, name:1 }},
{ $unwind: "$Photos" },
{ $sort: {"Photos.created" : -1} },
{ $skip: 1 },
{ $limit: 2 }
)
And result would be like this:
{
"result" : [
{
"name" : "Matthew",
"Photos" : {
"_id" : 2,
"url" : "http://www.example.com/photo3.jpg",
"created" : "2013-02-03"
}
},
{
"name" : "Bob",
"Photos" : {
"_id" : 3,
"url" : "http://www.example.com/photo2.jpg",
"created" : "2013-02-02"
}
}
],
"ok" : 1
}
I think you want something like this:
db.Users.aggregate( [
{$unwind:"$Photos"},
{$sort: {"Photos.created":-1},
{$limit: 10}
] );

Resources