Multiple Groupings for MongoDB Aggregate - javascript

Given the following data set of Event objects:
[
{
"_id": ObjectId("4fda05cb322b1c95b531ac26",
"title": "BUTTON CLICKED",
"createdAt": ISODate("2017-01-12T01:00:00+01:00")
},
{
"_id": ObjectId("1235h1k235h1kl325h1v31gv",
"title": "BUTTON CLICKED",
"createdAt": ISODate("2017-01-14T01:00:00+01:00")
},
{
"_id": ObjectId("c2n890904cn897qnxp23hjk1",
"title": "PAGE VIEWED",
"createdAt": ISODate("2017-01-12T02:00:00+01:00")
}
]
How would I group them by date then by name?
The desired result would look like this:
[
{
_id: { year: 2017, month: 1, day: 11 },
events: [ {
title: "BUTTON PRESSED",
count: 3
}, {
title: "PAGE VIEWED",
count: 2
}
]
},
{
_id: { year: 2017, month: 1, day: 24 },
events: [ {
title: "BUTTON PRESSED",
count: 1
}
]
}
]
Any help on this issue would be greatly appreciated, so thank you!

you can try this query
db.collectionName.aggregate([
$group: {
_id : {
year : { $year : "$createdAt" },
month : { $month : "$createdAt" },
day : { $dayOfMonth : "$createdAt" },
title: "$title"
},
count:{$sum:1}
}
},
{
$group:{
_id:{
year: "$_id.year",
month: "$_id.month",
day: "$_id.day"
},
data:{
$push: {
name:"$_id.title",
count:"$count"
}
}
}
}
])

Related

MongoDB aggregate - Set and condition $gt,$lte not working

I have 2 collections
1st users and 2nd shifts
When I am writing a query like below with lookup and unwind expressions.
results.users = await model.aggregate([
{
$match: filter,
},
{
$lookup: {
from: "shifts",
localField: "_id",
foreignField: "employeeId",
as: "shifts",
},
},
{
$set: {
shifts: {
$filter: {
input: "$shifts",
cond: [
{
$and: [
{
$gte: [
"$$this.date",
new Date("2022-10-01"),
],
},
{
$lte: [
"$$this.date",
new Date("2022-10-05"),
],
},
],
},
],
},
},
},
},
]);
[
{
_id: "60dd781c4524e6c116e2336d",
workerFirstName: "MADASWAMY",
workerSurname: "KARUPPASWAMY",
workerId: "1002",
shifts:[]
},
{
_id: "60dd781d4524e6c116e234d4",
workerFirstName: "AMIT",
workerSurname: "SHAH",
workerId: "1001",
shifts:[]
},
{
_id: "60dd781d4524e6c116e23642",
workerFirstName: "DEVELOPER",
workerSurname: "DEVELOPER",
workerId: "7738",
shifts: [
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-01T00:00:00.000Z",
},
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-02T00:00:00.000Z",
},
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-03T00:00:00.000Z",
},
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-04T00:00:00.000Z",
},
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-05T00:00:00.000Z",
},
{
_id: "634d8d3ce596dd34c9532d5d",
month: "October",
workerId: "7738",
date: "2022-10-06T00:00:00.000Z",
},
....
],
},
]
As you can see I am getting, the list of Data.
but I want only specific data between the date range given
Ex : - The date range is from 1 to 5th Oct so I want only that much data but I am getting all data.
i tried doing this and it worked for me
{
$set: {
attendances: {
$filter: {
input: "$attendances",
cond: {
$and: [
{
$gte: ["$$this.Date", new Date(fromDate)],
},
{
$lte: ["$$this.Date", new Date(toDate)],
},
{
$eq: ["$$this.createdAs", dataType],
},
{
$eq: ["$$this.status", true],
},
{
$eq: ["$$this.workerType", workerType],
},
],
},
},
},
},
},
Just removed the array brackets from the condition

Mongodb sum of views by day name

I have this simple collection of views:
Views:
[
{
title: "cartoons",
views: 1,
created_at: 2022-10-03 12:00:00.000Z
},
{
title: "songs",
views: 4,
created_at: 2022-10-04 12:00:00.000Z
},
{
title: "lectures",
views: 3,
created_at: 2022-10-10 12:00:00.000Z
},
{
title: "news",
views: 2,
created_at: 2022-10-05 12:00:00.000Z
},
{
title: "movies",
views: 6,
created_at: 2022-10-07 12:00:00.000Z
},
{
title: "tv series",
views: 6,
created_at: 2022-10-12 12:00:00.000Z
}
]
Here I need to see how many views I got on each day of week in e.g 2 years
Expected Result:
{
"monday": 4,
"tuesday": 4,
"wednesday": 8,
"thursday": 0,
"friday": 6,
"saturday": 0,
"sunday": 0,
}
Since I am very new to mongodb, Is this possible to perform such operation using query? If yes then can I get some help regarding this?
What about this?
// select some random mongo database for testing
use("stack")
// at first clean collection
db.data.drop()
// populate with initial data
db.data.insertMany([
{
title: "cartoons",
views: 1,
created_at: ISODate("2022-10-03 12:00:00.000Z"),
},
{
title: "songs",
views: 4,
created_at: ISODate("2022-10-04 12:00:00.000Z"),
},
{
title: "lectures",
views: 3,
created_at: ISODate("2022-10-10 12:00:00.000Z"),
},
{
title: "news",
views: 2,
created_at: ISODate("2022-10-05 12:00:00.000Z"),
},
{
title: "movies",
views: 6,
created_at: ISODate("2022-10-07 12:00:00.000Z"),
},
{
title: "tv series",
views: 6,
created_at: ISODate("2022-10-12 12:00:00.000Z"),
}
])
// get results
p = [
// get day of week for each record based on created_at date
{
$project: {
weekDay: {
$arrayElemAt: [
// mongo returns day numbers from 1 to 7, Sunday being 1
["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
{ $add: [ {$dayOfWeek: "$created_at"}, -1 ] }
]
},
views: 1,
_id: 0,
}
},
// count sum of views numbers for each weekday
{
$group: { _id: "$weekDay", total_views: {$sum: "$views"} }
},
// reshape current results to make them easily convertable to one final object
{
$replaceRoot: {
newRoot: { k: "$_id", v: "$total_views" }
}
},
// step required to get just 1 document at the end
{
$group: {
_id: 0,
merged: { $push: "$$ROOT" }
}
},
// fill in missing week days with 0 values and follow sorting order that we want
{
$project: {
merged: {
$mergeObjects: [
{
"monday": 0,
"tuesday": 0,
"wednesday": 0,
"thursday": 0,
"friday": 0,
"saturday": 0,
"sunday": 0,
},
{$arrayToObject: "$merged"},
]
}
}
},
// return field value that we want directly
{
$replaceRoot: { newRoot: "$merged"}
}
]
// Run
db.data.aggregate(p)
And the result is
[
{
"monday": 4,
"tuesday": 4,
"wednesday": 8,
"thursday": 0,
"friday": 6,
"saturday": 0,
"sunday": 0
}
]
https://mongoplayground.net/p/QutCGjKiy6z
await db.collectionName.aggregate([{
$addFields: {
days: {
$dayOfWeek: {
$toDate: '$created_at'
}
}
}
}, {
$group: {
_id: {
days: '$days'
},
totalReview: {
$sum: '$views'
},
daysCount: {
$sum: 1
}
}
}, {
$project: {
_id: 0,
totalReview: 1,
day: {
$switch: {
branches: [
{
'case': {
$eq: [
'$_id.days',
1
]
},
then: 'sunday'
},
{
'case': {
$eq: [
'$_id.days',
2
]
},
then: 'monday'
},
{
'case': {
$eq: [
'$_id.days',
3
]
},
then: 'tuesday'
},
{
'case': {
$eq: [
'$_id.days',
4
]
},
then: 'wednesday'
},
{
'case': {
$eq: [
'$_id.days',
5
]
},
then: 'thursday'
},
{
'case': {
$eq: [
'$_id.days',
6
]
},
then: 'friday'
},
{
'case': {
$eq: [
'$_id.days',
7
]
},
then: 'saturday'
}
],
'default': 'day unknown'
}
}
}
}]);
You can do it like this:
$set and $isoDayOfWeek - to calculate the day of week based on created_at property
$group and $sum - to sum all views for each day of the week
db.collection.aggregate([
{
"$set": {
"dayOfWeek": {
"$isoDayOfWeek": "$created_at"
}
}
},
{
"$group": {
"_id": "$dayOfWeek",
"count": {
"$sum": "$views"
}
}
}
])
Note: In the response, 1 is Sunday and 7 is Saturday.
Working example

Mongoose group by an lookup with nested arrays

I have two schema's , first one is Quiz and the other one is quizResults, I want to get aggregated data from the quizResult while doing the lookup in quiz Schema. Below is my Quiz schema:
vidId: {type: Number, required: true},
status: {type: Number, enum: [statusType.quizStatusEnums.LIVE, statusType.quizStatusEnums.DELETED], default: statusType.quizStatusEnums.LIVE},
questions: [
{
questionNum: { type: Number },
questionName: { type: String },
answers: [
{
answerId: { type: String, default: uuid.v4() },
answerName: { type: String },
isCorrect: { type: Boolean },
answerType: { type: Number, enum: [statusType.quizTypeEnums.QUIZ, statusType.quizTypeEnums.SURVEY], default: statusType.quizTypeEnums.QUIZ},
hotspotId: { type: Number },
overlayId: {type: Number},
panelId: {type: String}
}
]
}
],
The Second one is QuizResults: The aggregation query needs to be performed on this collection.
created: {
type: Date,
},
vidId: {
type: Number,
required: true,
},
viewerId: {
type: String,
required: true,
},
quizId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
questionId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
answerId: {
type: String,
required: true
},
isCorrect: {
type: Boolean,
default: false,
},
whenAnswered: {
type: Date
},
I want the final aggregated result like that:
[
{
"questionNum": 2,
"questionName": "Which is the best selling record in history ?",
"correct": 10,
"incorrect": 20,
"totalAnswers": 30,
"answers": [
{
"answerId": "123abc",
"answerName": "Thriller Michel Jackson",
"numResponses": 10
},
{
"answerId": "234d",
"answerName": "A kind of Magic Queen",
"numResponses": 10
},
{
"answerId": "432e",
"answerName": "help The Beatles",
"numResponses": 10
}
]
},
{
"questionNum": 1,
"questionName": "What value has the number PI?",
"correct": 5,
"incorrect": 3,
"totalAnswers": 8,
"answers": [
{
"answerId": "111",
"answerName": "3.12",
"numResponses": 0
},
{
"answerId": "222",
"answerName": "3.14",
"numResponses": 5
},
{
"answerId": "333",
"answerName": "3.16",
"numResponses": 3
}
]
}
]
What I tried is :
aggregate([
{ "$match": { "vidId": 8225342, } },
{
"$group": {
"_id": "$questionId",
"Correct": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", true] },
1,
0
]
},
},
"Incorrect": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", false] },
1,
0
]
}
},
}
},
{
"$lookup": {
"from": "quiz",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$id", "$questions._id"] } } },
{ "$unwind": "$questions" },
{ "$match": { "$expr": { "$eq": ["$questions._id", "$$id"] } } },
],
"as": "quizData"
}
},
{ $unwind: '$quizData' },
{ "$project": {
"questionName": "$quizData.questions.questionName",
"questionNum": "$quizData.questions.questionNum",
"Correct": "$Correct",
"Incorrect": "$Incorrect",
"answers": "$quizData.questions.answers" } },
])
I got the results something like that:
{
"_id": "611632305bd3910929b95552",
"questionName": "Which is the best selling record in history?",
"questionNum": 5,
"Correct": 3,
"Incorrect": 0,
"answers": [
{
"answerId": "078f441b-373f-40e9-89e1-04fca0a9fc5d",
"answerType": 0,
"_id": "611632305bd3910929b95553",
"answerName": "Thriller Michel Jackson",
"isCorrect": true,
"hotspotId": 470114,
"overlayId": 3,
"panelId": "12abc"
},
{
"answerId": "644b80fe-5778-46fa-b3a6-1eff5989cdee",
"answerType": 0,
"_id": "611632305bd3910929b95554",
"answerName": "A kind of Magic Queen",
"isCorrect": false,
"hotspotId": 470113,
"overlayId": 4,
"panelId": "12345abc"
},
{
"answerId": "5bde2682-66fe-4c79-a728-aea67f6842a8",
"answerType": 0,
"_id": "611632305bd3910929b95555",
"answerName": "help The Beatles",
"isCorrect": false,
"hotspotId": 470112,
"overlayId": 3,
"panelId": "12abc"
}
]
},
how I can get the Answers array like that:
answers: [
{
answerId: "123abc",
answerName: "Thriller Michel Jackson",
numResponses: 10
},
{
answerId: "234d",
answerName: "A kind of Magic Queen",
numResponses: 10
},
{
answerId: "432e",
answerName: "help The Beatles",
numResponses: 10
}
]
         
You can try to approach it the other way around. Use $lookup to get quizResults filtered and aggregated and then run $map along with $filter to get matching statistics for each answer:
db.quiz.aggregate([
{
$match: { "vidId": 8225342 }
},
{
$lookup: {
from: "quizResults",
pipeline: [
{ $match: { "vidId": 8225342 } },
{
$group: {
_id: "$answerId",
count: { $sum: 1 }
}
}
],
as: "quizResults"
}
},
{
$project: {
_id: 1,
questions: {
$map: {
input: "$questions",
as: "q",
in: {
_id: "$$q._id",
questionName: "$$q.questionName",
questionNum: "$$q.questionNum",
answers: {
$map: {
input: "$$q.answers",
as: "a",
in: {
$mergeObjects: [
"$$a",
{
$let: {
vars: {
fst: {
$first: {
$filter: { input: "$quizResults", cond: { $eq: [ "$$this._id", "$$a._id" ] } }
}
}
},
in: { numResponses: "$$fst.count" }
}
}
]
}
}
}
}
}
}
}
}
])
Mongo Playground

Using Reduce to rearrange an Object of Arrays

I am trying to get the below output from the array using reduce, however, I can't wrap my head about some parts on how reduce behave , appreciate some explanation so I can fully grasp it.
final goal is after reducing the result into a single array, removing duplicate objects based on the vch_number
Reduce function
const result = car.reduce((acc,vch)=>{
const temp = {...acc,[vch.name]:vch.Vehciles}
for (const [key, value] of Object.entries(temp)){
const fillterd = value.map(item => {
item.status = key
return item
})
}
return temp
}
,{})
console.log(result)
// final desired output vs current output
current = { available:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' } ],
parked:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ],
service:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' } ] }
desired = [
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ,
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' }
]
// Original API array
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
]
First problem is your using a {} as the second argument (the so called accumulator) in the .reduce() function. You'll want to pass an empty array [].
Second off all you have the Vehciles array inside those objects so you have to perform one more transformation inside.
more about reducers:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
I think the key is to wrap your head around the initialValue and the accumulator and how it relates to the currentValue when it comes to Array.reduce()
I've whipped up this code:
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
];
const result = car.reduce((acc,vch)=>{
const cars = vch.Vehciles.map(vehicle => {
const temp = {
status: vch.name,
...vehicle
};
return temp;
}).reduce((carAcc, car) => {
carAcc.push(car);
return carAcc;
}, acc);
return acc;
}, []);
console.log(result)
You could map the objects and check with a Set.
const
data = [{ name: "available", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51511 }, { make: "bwm", model: "i8", year: 2020, vch_number: 51541 }] }, { name: "parked", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }, { name: "service", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }],
result = data.flatMap(
(seen => ({ name: status, Vehciles }) => Vehciles.flatMap(o => seen.has(o.vch_number)
? []
: (seen.add(o.vch_number), { ...o, status })
))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Mongoose: How to sort aggregate response with two fields

I have this mongoose query:
MinutesSpentStudying.aggregate([
{ $match: { connected_user_id: ObjectId(user_id) } },
{
$project: {
minutes_spent_studying: 1,
year: { $year: "$date" },
day: { $dayOfMonth: "$date" },
},
},
{
$group: {
_id: {
day: "$day",
year: "$year",
},
total_minutes: { $sum: "$minutes_spent_studying" },
},
},
{ $sort: { _id: 1 } },
]);
It returns this response:
[
{
"_id": {
"day": 2,
"year": 2021
},
"total_minutes": 11
},
{
"_id": {
"day": 3,
"year": 2021
},
"total_minutes": 1
},
{
"_id": {
"day": 26,
"year": 2020
},
"total_minutes": 1
},
{
"_id": {
"day": 27,
"year": 2020
},
"total_minutes": 3
},
]
I'd like it to sort out by year, and then by day so that it returns the results of 2020 and then the result of 2021.
Any idea how to configure so as to achieve this result?
You can sort by multiple fields and use the dot notation for the nested ones:
{
$sort: {
"_id.year": 1,
"_id.day": 1
}
}
Mongo Playground

Categories