GroupBy date + mongodb - node.js

I have to aggregate the result based on the month of the given document. Consider the following as my document:
{
"_id" : ObjectId("5b3314a12b05b1b247366f48"),
"email" : "abc#gmail.com",
"qwerty":[{
"id" : "5ba4ebbad1b5eaf038841302",
"status" : "inprogress",
"Date" : "2018-08-20"
},
{
"id" : "5ba4ebbad1b5eaf038841303",
"status" : "inprogress",
"Date" : "2018-08-20"
}]
Following is my query:
var query =[
{ $match: {"email":email} },
{$unwind: "$courses" },
{$group:{_id:{$substrCP: ["$qwerty.Date", 5, 2]},count:{$sum:1}}}
];
Its working properly. But i $substrCP: ["$qwerty.Date", 5, 2] is based on the date format is "2018-08-20", what if "20-08-2018"?? So its possible to change the above query to accomodate of nay type.
Also i tried with new Date("").getMonth() but its showing as "NaN", i get to know that its not possible to use inside group.
Please suggest your ideas.

You can utilize $month in combination with $dateFromString to get what you need:
db.collection.aggregate([
{
$match: {
"email": "abc#gmail.com"
}
},
{
$unwind: "$qwerty"
},
{
$group: {
_id: {
$month: {
$dateFromString: {
dateString: "$qwerty.Date"
}
}
},
count: {
$sum: 1
}
}
}
])
You can see it here with the two different date formats.
To group per the date you can do the same without the $month:
db.collection.aggregate([
{
$match: {
"email": "abc#gmail.com"
}
},
{
$unwind: "$qwerty"
},
{
$group: {
_id: {
$dateFromString: {
dateString: "$qwerty.Date"
}
},
count: {
$sum: 1
}
}
}
])
See this version here

Related

groups by name and provider, then group by month and then get the average of month wise in mongoose

I want to groups by name and provider, then group by month, and then get the average of month-wise in mongoose
the expected result, average group by name and month. Thanks
my collection in MongoDB looks like below:
{
"_id" : ObjectId("614d713c28af063f4cffbb6f"),
"name" : "Chibi Doge",
"id" : "0x7c8e01e8df38f001bcefb3b8901ad5d64374dbdf",
"logo" : "https://img.rarible.com/prod/image/upload/t_avatar_big/prod-collections/0x7c8e01e8df38f001bcefb3b8901ad5d64374dbdf/avatar/Qmdp79f2nP8HRYwwQiEDuRcy6F9ysW4WPdG5QiSvpd57Hu",
"provider" : "rarible",
"totalSalesUsd" : 27436.1841495685,
"totalNFTs" : 59,
"createdAt" : ISODate("2021-09-24T06:33:32.708Z"),
"updatedAt" : ISODate("2021-09-24T06:33:32.537Z"),
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("614d713d28af063f4cffbb73"),
"name" : "",
"id" : "0xa7206d878c5c3871826dfdb42191c49b1d11f466",
"logo" : "https://img.rarible.com/prod/image/upload/t_avatar_big/prod-collections/0xa7206d878c5c3871826dfdb42191c49b1d11f466/avatar/QmYCxLp2puXnnarXHyTcY9onjMqrTAUNQskpirL5VPFF6B",
"provider" : "rarible",
"totalSalesUsd" : 26176.2678881893,
"totalNFTs" : 7,
"createdAt" : ISODate("2021-09-24T06:33:33.228Z"),
"updatedAt" : ISODate("2021-09-24T06:33:33.057Z"),
"__v" : 0
}
I write a query which results in the below result:
[
{
"_id": {
"provider": "superrare",
"name": "batsoupyum2"
},
"data": [
{
"provider": "superrare",
"name": "batsoupyum2",
"totalSalesUsd" : 26176.2678881893,
"createdAt": "2021-09-24T06:23:32.802Z"
},
{
"provider": "superrare",
"name": "batsoupyum2",
"totalSalesUsd" : 26176.2678881893,
"createdAt": "2021-08-24T06:33:32.708Z"
}
]
}
]
and the query is
collection.aggregate([
{
$group: {
_id: {
provider: "$provider",
name: "$name",
// month: { $month: "$createdAt" },
// avgQuantity: { $avg: "$totalSalesUsd" }
},
data: {
$push: {
provider: "$provider",
name: "$name",
totalSalesUsd: "$totalSalesUsd",
createdAt: "$createdAt"
// average: "$_id.average"
},
},
},
},
])
How can I do this using the mongoose.js framework?
You're quite close, what you want to do is add another $group stage, you need to start by $grouping on provider AND time, and only then you group again on just the provider, like so:
db.collection.aggregate([
{
$group: {
_id: {
provider: "$provider",
name: "$name",
month: {
$month: "$createdAt"
},
year: {
$year: "$createdAt"
}
},
avgQuantity: {
$avg: "$totalSalesUsd"
}
}
},
{
$group: {
_id: {
provider: "$_id.provider",
name: "$_id.name"
},
data: {
$push: {
provider: "$_id.provider",
name: "$_id.name",
avgQuantity: "$avgQuantity",
month: "$_id.month",
year: "$_id.year"
}
}
}
}
])
Mongo Playground

Get Total Count using aggregate + facet using Mongo

I want to group my data based on event date with pagination. However what i am getting is whole record totalcount instead of eventDate count. because of this UI part is not working properly. Here is my collection sample:
{
"_id" : ObjectId("5fc4d0009a25e8cfbe306381"),
"eventDate" : ISODate("2021-11-29T01:00:00.000Z"),
"team1" : {
"tName" : "Chicago Bears",
},
"team2" : {
"tName" : "Green Bay Packers",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063ff"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "Nashville SC",
},
"team2" : {
"tName" : "Columbus Crew",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063f4"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "yyyy",
},
"team2" : {
"tName" : "xxxx",
}
}
here is my query:
db.getCollection('game').aggregate([
{ $addFields: { "newEventDate": {$dateToString:{ format: "%Y-%m-%d", date: "$eventDate" }}}},
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$facet": {
"resultData": [
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$group": {
"_id": "$newEventDate",
"games": {$push: {
team1:"$team1",
team2:"$team2"
}}
}
},
{ $sort: {eventDate: 1} },
{
$limit: 1
}
],
"pageInfo": [
{ "$count": "totalRecords" }
]}
}
]);
After executing this query this is my response:
{
"resultData" : [
{
"_id" : "2021-11-29",
"games" : [
{
"awayTeam" : {
"tName" : "Chicago Bears"
},
"homeTeam" : {
"tName" : "Green Bay Packers"
}
},
]
}
],
"pageInfo" : [
{
"totalRecords" : 3 **[here i want 2 ie total event date]**
}
]
}
$match your condition
move your $group stage outside from $facet, convert your date from string inside group, add you date in group stage because we are going to sort in next stage
$sort by eventDate ascending order
$facet, first get single record using $limit, and second part get total count of the record using $count
db.collection.aggregate([
{ $match: { eventDate: { $gte: new Date() } } },
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$eventDate"
}
},
eventDate: { $first: "$eventDate" },
games: {
$push: {
team1: "$team1",
team2: "$team2"
}
}
}
},
{ $sort: { eventDate: 1 } },
{
$facet: {
resultData: [{ $limit: 1 }],
pageInfo: [{ $count: "totalRecords" }]
}
}
])
Playground

Extract month from timestamp mongodb

I have a mongodb like that:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"array" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"timestamp" : "1470420945250",
},
{
"_id" : ObjectId("5ece47aa6510a611b47a8895"),
"timestamp" : "1470420945250"
},
{..},
{..}
]
I am trying to make a query to count how many months from timestamp are January for example. Any suggestion?
mongoplayground
mongoplayground2 => Contains the count, just change the $match around
db.collection.aggregate([
{
$unwind: "$array"
},
{
$addFields: {
timestamp: {
$toDate: "$array.timestamp"
}
}
},
{
$project: {
month: {
$month: {
date: "$timestamp"
}
}
}
}
])
You may add a timezone field inside $month.

Mongodb - Find count of distinct items after applying aggregate and match

Trying to figure out something from Mongo using mongoose in optimal way.
I have following documents
Regions
{
"_id" : ObjectId("5cf21263ff605c49cd6d8016"),
"name" : "Asia"
}
Countries can be part of multiple regions
{
"_id" : ObjectId("5d10a4ad80a93a1d7cd56cc6"),
"regions" : [
ObjectId("5d10a50080a93a1d7cd56cc7"),
ObjectId("5cf2126bff605c49cd6d8017")
],
"name" : "India"
}
Places belongs to one country
{
"_id" : ObjectId("5d11bb8180a93a1d7cd56d26"),
"name" : "Delhi",
"country" : ObjectId("5d136e7a4e480863a51c4056"),
}
Programs each in dayshows array represents one day. On a day show can cover multiple places.
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d31"),
"dayshows" : [
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d41"),
"places" : [
ObjectId("5d11bb8180a93a1d7cd56d26")
],
},
{
"_id" : ObjectId("5d11cc9480a93a1d7cd56d3c"),
"places" : [
ObjectId("5d11bb8180a93a1d7cd56d26"),
ObjectId("5d11bc7c80a93a1d7cd56d2e")
]
}
]
}
What am I trying to figure out?
For a given region, for each country in region which all places are covered and count of programs for each place. Using nodejs and mongoose.
Example
Input - Asia
Output
India
- Delhi (3)
- Mumbai (5)
Thailand
- Pattaya (2)
- Bangkok (5)
New to mongo.
You need to use $lookup to cross different collections.
Pipeline:
Stages 1-6 serves to get all related data.
(Optional) Stages 7-10 serves to transform aggregated data into key:pair object.
ASSUMPTION
Programs to visit 2 places counted as is (Place1: +1, Place2: +1)
You know how to execute MongoDB aggregation in node.js
db.Regions.aggregate([
{
$match: {
name: "Asia"
}
},
{
$lookup: {
from: "Countries",
let: {
region: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$$region",
"$regions"
]
}
}
},
{
$lookup: {
from: "Places",
localField: "_id",
foreignField: "country",
as: "Places"
}
}
],
as: "Countries"
}
},
{
$unwind: "$Countries"
},
{
$unwind: "$Countries.Places"
},
{
$lookup: {
from: "Programs",
localField: "Countries.Places._id",
foreignField: "dayshows.places",
as: "Countries.Places.Programs"
}
},
{
$project: {
"name": 1,
"Countries.name": 1,
"Countries.Places.name": 1,
"Countries.Places.Programs": {
$size: "$Countries.Places.Programs"
}
}
},
{
$group: {
_id: {
name: "$name",
Countries: "$Countries.name"
},
Places: {
$push: {
k: "$Countries.Places.name",
v: "$Countries.Places.Programs"
}
}
}
},
{
$project: {
_id: 1,
Places: {
$arrayToObject: "$Places"
}
}
},
{
$group: {
_id: "$_id.name",
Countries: {
$push: {
k: "$_id.Countries",
v: "$Places"
}
}
}
},
{
$project: {
_id: 0,
name: "$_id",
Countries: {
$arrayToObject: "$Countries"
}
}
}
])
MongoPlayground

Date operation insede and array (aggreagation)

I want to build online test application using mongoDB and nodeJS. And there is a feature which admin can view user test history (with date filter option).
How to do the query, if I want to display only user which the test results array contains date specified by admin.
The date filter will be based on day, month, year from scheduledAt.startTime, and I think I must use aggregate framework to achieve this.
Let's say I have users document like below:
{
"_id" : ObjectId("582a7b315c57b9164cac3295"),
"username" : "lalalala#gmail.com",
"displayName" : "lalala",
"testResults" : [
{
"applyAs" : [
"finance"
],
"scheduledAt" : {
"endTime" : ISODate("2016-11-15T16:00:00.000Z"),
"startTime" : ISODate("2016-11-15T01:00:00.000Z")
},
"results" : [
ObjectId("582a7b3e5c57b9164cac3299"),
ObjectId("582a7cc25c57b9164cac329d")
],
"_id" : ObjectId("582a7b3e5c57b9164cac3296")
},
{
.....
}
],
"password" : "andi",
}
testResults document:
{
"_id" : ObjectId("582a7cc25c57b9164cac329d"),
"testCategory" : "english",
"testVersion" : "EAX",
"testTakenTime" : ISODate("2016-11-15T03:10:58.623Z"),
"score" : 2,
"userAnswer" : [
{
"answer" : 1,
"problemId" : ObjectId("581ff74002bb1218f87f3fab")
},
{
"answer" : 0,
"problemId" : ObjectId("581ff78202bb1218f87f3fac")
},
{
"answer" : 0,
"problemId" : ObjectId("581ff7ca02bb1218f87f3fad")
}
],
"__v" : 0
}
What I have tried until now is like below. If I want to count total document, which part of my aggregation framework should I change. Because in query below, totalData is being summed per group not per whole returned document.
User
.aggregate([
{
$unwind: '$testResults'
},
{
$project: {
'_id': 1,
'displayName': 1,
'testResults': 1,
'dayOfTest': { $dayOfMonth: '$testResults.scheduledAt.startTime' },
'monthOfTest': { $month: '$testResults.scheduledAt.startTime' },
'yearOfTest': { $year: '$testResults.scheduledAt.startTime' }
}
},
{
$match: {
dayOfTest: date.getDate(),
monthOfTest: date.getMonth() + 1,
yearOfTest: date.getFullYear()
}
},
{
$group: {
_id: {id: '$_id', displayName: '$displayName'},
testResults: {
$push: '$testResults'
},
totalData: {
$sum: 1
}
}
},
])
.then(function(result) {
res.send(result);
})
.catch(function(err) {
console.error(err);
next(err);
});
You can try something like this. Added the project stage to keep the test results if any of result element matches on the date passed. Add this as the first step in the pipeline and you can add the grouping stage the way you want.
$map applies an equals comparison between the date passed and start date in each test result element and generates an array with true and false values. $anyElementTrue inspects this array and returns true only if there is atleast one true value in the array. Match stage to include only elements with matched value of true.
aggregate([{
"$project": {
"_id": 1,
"displayName":1,
"testResults": 1,
"matched": {
"$anyElementTrue": {
"$map": {
"input": "$testResults",
"as": "result",
"in": {
"$eq": [{ $dateToString: { format: "%Y-%m-%d", date: '$$result.scheduledAt.startTime' } }, '2016-11-15']
}
}
}
}
}
}, {
"$match": {
"matched": true
}
}])
Alternative Version:
Similar to the above version but this one combines both the project and match stage into one. The $cond with $redact accounts for match and when match is found it keeps the complete tree or else discards it.
aggregate([{
"$redact": {
"$cond": [{
"$anyElementTrue": {
"$map": {
"input": "$testResults",
"as": "result",
"in": {
"$eq": [{
$dateToString: {
format: "%Y-%m-%d",
date: '$$result.scheduledAt.startTime'
}
}, '2016-11-15']
}
}
}
},
"$$KEEP",
"$$PRUNE"
]
}
}])

Resources