How can I get a sum of sums using MongoDB aggregation? - mongodb

I would like to get the total (sum) of the totalHoursForYear value. Here is my current aggregation:
const byYear = await this.workHistoryModel.aggregate([
{
$group: {
_id: '$year',
history: {
$push: '$$ROOT'
},
totalHoursForYear: {
$sum: {
$add: [
{ $multiply: ['$eightHourDays', 8] },
{ $multiply: ['$tenHourDays', 10] },
{ $multiply: ['$twelveHourDays', 12] },
]
}
},
}
},
]);
Now I need to get a sum of the totalHoursForYear to get the total for all years. Is this possible? I can't seem to get the syntax correct. When I attempt to add another sum:
$group: {
...,
totalHours: {
$sum: {
$add: '$totalHoursForYear'
},
$push: '$$ROOT'
}
}
I get an error: "The field 'totalHours' must specify one accumulator"

When you need total sum, you can do another $group with _id:null which means to consider all documents
With your script, add the following to get the total and get the same structure.
{
$group: {
_id: null,
data: {
$push: "$$ROOT"
},
total: {
$sum: "$totalHoursForYear"
}
}
},
{
"$unwind": "$data"
},
{
"$addFields": {
_id: "$data._id",
history: "$data.history",
totalHoursForYear: "$data.totalHoursForYear",
_id: "$$REMOVE",
data: "$$REMOVE"
}
}
Working Mongo playground

Related

MongoDB aggregate to get stats

I've done this sometime last year, but now I really can't recall and can't find any helpful resources.
I want to get the statistics of my collection based on types.
This is my data object
{
"_id": {
"$oid": "63bfc374378c59a5328f229e"
},
"amountEarned": 11500,
"amountPaid": 10350,
"relianceCommission": 1150,
"receiverType": "RESTAURANT",
"__v": 0
}
I just need the sum of amountPaid for each receiverType, it could be STORE, RESTAURANT or SHOPPER. Then I also need the sum of relianceCommission for all. Resulting in a shape like
{
storeEarnings: 500,
restaurantEarnings: 30,
shopperEarnings: 40,
totalRelianceCommission: 45
}
I've tried
aggregate([
{
$group: {_id: "$receiverType", total: {$sum: "amountPaid"}}
}
])
And then joining with another pipeline to calculate totalRelianceCommission, but I feel there should be a neater way to do it. I'm also not sure how to do the projections to result in the desired shape. Please help.
You need conditional sum.
db.collection.aggregate([
{
$group: {
_id: null,
storeEarnings: {
$sum: {
$cond: [{$eq: ["$receiverType","STORE"]},"$amountPaid",0]
}
},
restaurantEarnings: {
$sum: {
$cond: [{$eq: ["$receiverType","RESTAURANT"]},"$amountPaid",0]
}
},
shopperEarnings: {
$sum: {
$cond: [{$eq: ["$receiverType","SHOPPER"]},"$amountPaid",0]
}
},
totalRelianceCommission: {
$sum: "$relianceCommission"
}
}
}
])
Demo
query:
{
$group: {
_id: "$receiverType",
total: {
$sum: "$amountPaid"
},
commissions: {
$sum: "$relianceCommission"
}
}
}
result:[
{
"_id": "STORE",
"commissions": 1150,
"total": 10350
},
{
"_id": "RESTAURANT",
"commissions": 2300,
"total": 20700
}
]
loop through the array to get a sum of commissions

How to group mongodb aggregate array of objects data To sum numbers on the same date

I'm trying to create charts but I can't combine the data to be like "Expected result below"
What can I use to return "Expected result".
I tried using $group in $group and $reduce and it didn't work well.
I hope someone can help me solve this task
Current result is
[
{
"_id":"5fd4c3586e83b334d97c5218",
"consumption":5,
"charts":[
{
"date":"2020-10",
"consumption":1
},
{
"date":"2020-10",
"consumption":1
},
{
"date":"2020-11",
"consumption":1
},
{
"date":"2020-11",
"consumption":1
}
]
}
]
Expected result is
[
{
"_id":"5fd4c3586e83b334d97c5218",
"consumption":5,
"charts":[
{
"date":"2020-10",
"consumption":2
},
{
"date":"2020-11",
"consumption":2
},
}
]
You need to go with aggregations
db.collection.aggregate([
{ $unwind: "$charts" },
{
$group: {
_id: { _id: "$_id", month: "$charts.date.month", year: "$charts.date.year" },
consumption: { $first: "$consumption" },
total: { $sum: "$charts.consumption" },
date: { $first: "$charts.date" }
}
},
{
$group: {
_id: "$_id._id",
consumption: { $first: "$consumption" },
charts: {
$push: {
date: "$date",
consumption: "$total"
}
}
}
}
])
Working Mongo playground

Aggregate by all days of month mongodb

Hey i need to get the sum of all totalPrice group by days
I get this result
but i need to fetch all rest days of month even if it returns 0
i need solution
this is my code
Order.aggregate([
{ $project: { yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: '$created' }}, totalPrice:"$totalPrice" }},
{ $group: { _id: "$yearMonthDay", count: { $sum: 1 }, total: {"$sum": "$totalPrice"} }},
{ $sort: { _id: -1 } },
{ $group: { _id: null, stats: { $push: "$$ROOT" }}},
{
$project: {
results: {
$map: {
input:{ $range:[16,31] },
as: 'day',
in: {
$let: {
vars: {
dateIndex: {
"$indexOfArray": ["$stats._id", {$dateToString:{ date:{$dateFromParts:{'year':2020, 'month':5, 'day':"$$day"}}, format:'%Y-%m-%d'}}]
}
},
in: {
$cond: {
if: { $ne: ["$$dateIndex", -1] },
then: { $arrayElemAt: ["$stats", "$$dateIndex"] },
else: { _id: {$dateToString:{ date:{$dateFromParts:{'year':2020, 'month':5, 'day':"$$day"}}, format:'%Y-%m-%d'}, count: 0, total: 0 } }
}
}
}
}
}
}
}
},
{ $unwind: "$results" },
{ $replaceRoot: { newRoot: "$results"}}
]
This query should work for you.
db.collectionName.aggregate([
{ $project: { yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: '$created' }}, totalPrice:"$totalPrice" }},
{ $group: { _id: "$yearMonthDay", count: { $sum: 1 }, total: {"$sum": "$totalPrice"} }},
{ $sort: { _id: -1 } },
{ $group: { _id: null, stats: { $push: "$$ROOT" }},
{
$project: {
results: {
$map: {
input: ["2020-05-16","2020-05-15","2020-05-14","2020-05-13","2020-05-12"],
as: "date",
in: {
$let: {
vars: {
dateIndex: {
"$indexOfArray": ["$stats._id", "$$date"]
}
},
in: {
$cond: {
if: { $ne: ["$$dateIndex", -1] },
then: { $arrayElemAt: ["$stats", "$$dateIndex"] },
else: { _id: "$$date", count: 0, total: 0 }
}
}
}
}
}
}
}
},
{ $unwind: "$results" },
{ $replaceRoot: { newRoot: "$results"}}
])
The First 3 steps is same as yours.
{ $group: { _id: null, stats: { $push: "$$ROOT" }} will push previous stage results into an arrray stats which we will use for lookup in later stage.
In last stage, we will create possible date range and iterate over that.
for each key in range.
"$indexOfArray": ["$stats._id", "$$date"] will check if date is present in stats array or not
Then we will use that index to fetch value from stats array otherwise push default values.
As these results are still under results, we will unwind that array and move to root.
If you server version is above 3.6,
we can simplify date range creation part as well. let's initialize input arrays as days using $range.
input:{ $range:[16,31] },
as: 'day'
and modifiy dateIndex part like this
dateIndex: {
"$indexOfArray": ["$stats._id", {$dateToString:{ date:{$dateFromParts:{'year':2020, 'month':5, 'day':"$$day"}}, format:'%Y-%m-%d'}]
}
And change default value part as well similarly.
else: { _id: {$dateToString:{ date:{$dateFromParts:{'year':2020, 'month':5, 'day':"$$day"}}, format:'%Y-%m-%d'}}, count: 0, total: 0 }
Or alternatively, we can also use concat for generating keys
dateIndex: {
"$indexOfArray": ["$stats._id", {$concat:["2020-05","-", {$convert:{input:"$$day", to:"string"}}]}]
}
// And default value
else: { _id: {$concat:["2020-05","-", {$convert:{input:"$$day", to:"string"}}]}, count: 0, total: 0 }
Similarly, you can run another loop for months as well.

Mongodb Aggregate - Count fields that equals value in array, but keep both arrays

I need to calculate the percentage of finalized/total items. The problem I have is calculating how many fields in the array equal to 'finished'. With my current solution I get finished items correctly, but total items are the same number as finished.
This is what I'm doing:
Items.aggregate([
{
$match: {
status: {
$ne: ['cancelled','pending']
}
}
},
{
$group: {
_id: '$person',
items: {
$push: {
total: '$status',
finished: {
$cond: [
{
$eq: ['$status', 'finished']
},
'$status',
null
]
}
}
}
}
},
{
$unwind: '$items'
},
{
$match: {
'items.finished': {
$ne: null
},
}
},
{
$group: {
_id: '$_id',
success: {
$push : '$items.finished'
},
total: {
$push: '$items.total'
}
}
},
{
$project: {
successCount: {
$size: '$success'
},
totalCount: {
$size: '$total'
}
}
},
{
$project: {
successScore: {
$divide: [ "$successCount", "$totalCount"]
}
}
}
]);
I also tried simpler solution, but can't figure how to keep total count field in the loop after doing $unwind
Items.aggregate([
{
$group: {
_id: '$_id',
totalCount: {$sum: 1},
finished: { $cond : [ {$eg: ['status', 'finished']}, $status, null] }
}
},
{ $unwind: '$finished'},
...
Then I can't access totalCount later

Project the size of a set type array in mongodb

I am not able to display the size of an array in projection and groupby. please find my below query.
[
{
"$unwind":"$a"
},
{
$group:{
_id:"$a.acid",
"sessions":{
$sum:1
},
"users":{
$addToSet:"$cid"
}
}
},
{
$project:{
"sessions":1,
"users":1
}
}
]
By the above query I can display all the users, But I want to display the size of the users set(array) only. Could anyone help me on this
You can use $size in mongo like following query:
db.collection.aggregate[{
"$unwind": "$a"
}, {
$group: {
_id: "$a.acid",
"sessions": {
$sum: 1
},
"users": {
$addToSet: "$cid"
}
}
}, {
$project: {
"sessions": 1,
"users": 1,
"size": {
$size: "$users"
}
}
}
])
EDIT AFTER OP's comment that it gives invalid operator error-
You can do something like again unwind users array and count number of users. But this involves again unwinding array so not recommended, The query will be something like following (Not tested):
db.collection.aggregate([{
"$unwind": "$a"
}, {
$group: {
_id: "$a.acid",
"sessions": {
$sum: 1
},
"users": {
$addToSet: "$cid"
}
}
}, {
$unwind: "$users"
}, {
$group: {
_id: "$_id",
"count": {
$sum: 1
},
"sessions": {$push:"$sessions"},// u can use "sessions":{$first:"$sessions"} also.
"users": {
$push: "$users"
}
}
}])