MongoDB aggregate to get stats - mongodb

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

Related

How to find date wise sum and total sum of all date at a time in mongodb?

I have a collection billow and i need to find date wise total cost and sum of all cost available in this collection. I can find total cost of a day but failed to get sum of all cost from the collection
[{
"date":"12-2-2015",
"cost":100
},
{
"date":"13-2-2015",
"cost":10
},
{
"date":"12-2-2015",
"cost":40
},
{
"date":"13-2-2015",
"cost":30
},
{
"date":"13-2-2015",
"cost":80
}]
I can find output like
[{
"day": "12-2-2015",
"cost": 140
},{
"day": "13-2-2015",
"cost": 120
}]
But I want output like this.
{
"day": "12-2-2015",
"cost": 140,
"total": 260
}
use this aggregate I dont add $match stage you could add to match date
db.collection.aggregate([
{
$group: {
_id: null,
orig: {
$push: "$$ROOT"
},
"total": {
$sum: "$cost"
},
}
},
{
$unwind: "$orig"
},
{
$project: {
date: "$orig.date",
cost: "$orig.cost",
total: "$total"
}
},
{
$group: {
_id: "$date",
cost: {
$sum: "$cost"
},
orig: {
$push: "$$ROOT.total"
}
},
},
{
"$unwind": "$orig"
},
{
$group: {
_id: {
_id: "$_id",
cost: "$cost",
total: "$orig"
},
},
},
{
$project: {
date: "$_id._id",
"cost": "$_id.cost",
total: "$_id.total",
_id: 0
}
}
])
https://mongoplayground.net/p/eN-pDg2Zz7u
It is like 2 queries.
There are 3 solutions that i can think of
2 queries (works no matter the collection size)
1 query and facet (the bellow solution)
group and pack each group in an array
(limitation = ngroups(distinct day dates) small enough to fit in 1 array 16MB distinct dates,
(which is true for like 200.000? distinct days see this)
1 query no facet
for example group and pack all collection into 1 array
(limitation = all collection must fit in 100MB memory
because of $push see this)
*for the limits i think they are like that, based on what i have understanded.
Query
Test code here
db.collection.aggregate([
{
"$facet": {
"total": [
{
"$group": {
"_id": null,
"total": {
"$sum": "$cost"
}
}
}
],
"coll": [
{
"$group": {
"_id": "$date",
"cost": {
"$sum": "$cost"
}
}
}
]
}
},
{
"$unwind": {
"path": "$coll"
}
},
{
"$project": {
"total": {
"$let": {
"vars": {
"t": {
"$arrayElemAt": [
"$total",
0
]
}
},
"in": "$$t.total"
}
},
"date": "$coll._id",
"cost": "$coll.cost"
}
}
])
I would do one query to get a cursor, then iterate the cursor and at the same time sum the total cost and push the relevant doc, then add the total to each group. In this way you perform only one query to mongodb and let your server do the rest while keeping the code simple.
// 1. Fetch the groups
const grouped = db.data.aggregate([
{ $group: {
_id: "$date",
cost: { $sum: "$cost" }
}}
]);
// 2. Iterate the cursor, push the results into an array while summing the total cost
let total = 0;
const result = [];
grouped.forEach(group => {
total += group.cost;
result.push(group); // push as much as your limit
});
// 3. Add total to each group
result.forEach(group => group.total = total);

MongoDB how to $project aggregate with multiple $sum?

For MongoDB, need to group on event and supply multiple attribute fields.
My purpose is to show for a group the associated field attributes together with a list of sum totals from numeric fields.
For the MongoDB the aggregate has $group and $project. $project can show fields listed within the $group.
My $group is working fine by itself, e.g, without the $project. When I supply $project before or after the $group, I receive the following error:
query failed: (Location40323) A pipeline stage specification object must contain exactly one field.
My code is as follows:
db.collection.aggregate([
{
$project: {
eventName: "$EVENT_TYPE",
registeredDate: "$BEGIN_DATE_TIME",
stateName: "$STATE",
damageCosts: "$DAMAGE_PROPERTY",
peopleCosts: "$DEATHS_DIRECT",
injuriyCosts: "$INJURIES_DIRECT",
cropCosts: "$DAMAGE_CROPS"
},
$group: {
_id: "$EVENT_TYPE",
totalPropCost: {
$sum: "$DAMAGE_PROPERTY"
},
totalDeaths: {
$sum: "$DEATHS_DIRECTY"
},
totalInjury: {
$sum: "$INJURIES_DIRECT"
},
totalCropCost: {
$sum: "$DAMAGE_CROPS"
}
}
}
])
Alternately: attempted to use the $push command, and it looks like a valid run with $push:
db.collection.aggregate([
{
$group: {
_id: "$EVENT_TYPE",
totalPropCost: {
$sum: "$DAMAGE_PROPERTY"
},
totalDeaths: {
$sum: "$DEATHS_DIRECTY"
},
totalInjury: {
$sum: "$INJURIES_DIRECT"
},
totalCropCost: {
$sum: "$DAMAGE_CROPS"
},
events: {
$push: {
name: "$EVENT_TYPE",
date: "$BEGIN_DATE_TIME",
state: "$STATE"
}
}
}
}
])
I use the following sample of data for this mongoDb query, which without the $project, works correct on $sum.
[
{
"BEGIN_YEARMONTH": 201007,
"BEGIN_DAY": 7,
"END_YEARMONTH": 201007,
"END_DAY": 7,
"END_TIME": 1630,
"STATE": "NEW HAMPSHIRE",
"YEAR": 2010,
"EVENT_TYPE": "Heat",
"BEGIN_DATE_TIME": "07-JUL-10 12:51:00",
"END_DATE_TIME": "07-JUL-10 16:30:00",
"INJURIES_DIRECT": 0,
"DEATHS_DIRECT": 0,
"DAMAGE_PROPERTY": "0.00K",
"DAMAGE_CROPS": "0.00K"
},
{
"BEGIN_YEARMONTH": 201001,
"BEGIN_DAY": 17,
"END_YEARMONTH": 201001,
"END_DAY": 18,
"END_TIME": 1500,
"STATE": "NEW HAMPSHIRE",
"YEAR": 2010,
"MONTH_NAME": "January",
"EVENT_TYPE": "Heavy Snow",
"BEGIN_DATE_TIME": "17-JAN-10 23:00:00",
"END_DATE_TIME": "18-JAN-10 15:00:00",
"INJURIES_DIRECT": 0,
"DEATHS_DIRECT": 0,
"DAMAGE_PROPERTY": "0.00K",
"DAMAGE_CROPS": "0.00K"
}
]
For this investigation, after many tries, and other ideas welcome, especially using alternative solutions with $project. By and by, this much is giving me adequate results. The $push command appear to work best. If anyone knows how to get a $project working, please advise and provide explanation. For now, $push work on one solutions for group and list of attribute fields.
db.collection.aggregate([
{
$group: {
_id: "$STATE",
totalPropCost: {
$sum: "$DAMAGE_PROPERTY"
},
totalDeaths: {
$sum: "$DEATHS_DIRECTY"
},
totalInjury: {
$sum: "$INJURIES_DIRECT"
},
totalCropCost: {
$sum: "$DAMAGE_CROPS"
},
events: {
$push: {
eventName: "$EVENT_TYPE",
CzName: "$CZ_NAME",
cZTimeZone: "$CZ_TIMEZONE",
eventDate: "$BEGIN_DATE_TIME",
eventMonth: "$MONTH_NAME",
state: "$STATE",
observer: "$SOURCE"
}
}
}
}
])

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

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

How do you get the middle result from a mongodb query

I have a MongoDB database, which has a collection that contains all of the addresses from a country. Sometimes when I execute a query on that I have a chance that I receive about 200 results (house numbers within that street). I want to get the middle item of that result.
When I do that in my coding like this for example:
const result = Address.find({ street: "fooStreet" })
// results in an array with a length of let's say 200 (could also be 20, 49, 103, etc) items
I could split it in my coding like below:
const middleIndex = Math.round(result.length / 2);
const house = result[middleIndex];
But this means that the other records go to waste and use unnecessary bandwidth + computing power which should be handled by the database. Since the database OS is optimized for working with collections etc, I was wondering if I could achieve the same result in a mongodb query? See pseudo below:
db.getCollection("addresses")
.find({ street: "fooStreet" })
.helpMeHere()
// ^ do something to get the middle result from the N items
You can do as below
db.collection.aggregate([
{ //Any match condition
$match: {}
},
{
$group: {//get the total matching result
"_id": null,
data: {
$push: "$$ROOT"
},
count: {
$sum: 0.5
}
}
},
{
$project: {//get the second half
"result": {
"$slice": [
"$data",
{
"$toInt": {
"$multiply": [//Negating results records from the last
{
"$toInt": "$count"
},
-1
]
}
}
]
}
}
}
])
playground
To get one element:
playground
db.collection.aggregate([
{
$match: {}
},
{
$group: {
"_id": null,
data: {
$push: "$$ROOT"
},
count: {
$sum: 0.5
}
}
},
{
$project: {
"result": {
"$arrayElemAt": [//array access
"$data",
{
"$toInt": "$count"
}
]
}
}
}
])

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"
}
}
}])