How to aggregate data which an array field sum is between two values? - mongodb

I have two values which are minCount and maxCount.
In my model I have field which is called counts.Something like this.
{
createdAt: date
counts: [ 0,200,100] ==> Sum 300
},
{
createdAt: date
counts: [ 200,500,0] ==> Sum 700
},
{
createdAt: date
counts: [ 0,1100,100] ==> Sum 1200
},
I need to return sum of counts which sum of counts array elements are between minCount and MaxCount.
Exm:
minCount= 400
maxCount= 1300
Return
{
createdAt: date
total: 700
},
{
createdAt: date
total: 1200
},
I
I have createdAt dates between two dates like this in first step of pipe.
Record.aggregate ([
{
$match: {
createdAt: {
$gte: new Date (req.body.startDate),
$lte: new Date (req.body.endDate),
},
},
},
{}, ==> I have to get total counts with condition which I could not here.
])
I am almost new to aggreagate pipeline so please help.

Working example - https://mongoplayground.net/p/I6LOLhTA-yA
db.collection.aggregate([
{
"$project": {
"counts": 1,
"createdAt": 1,
"totalCounts": {
"$sum": "$counts"
}
}
},
{
"$match": {
"totalCounts": {
"$gte": 400,
"$lte": 1300
}
}
}
])

Related

How to get depended max value from another max value?

Assume there are documents with following structure in the collection:
{
_id: ObjectId("63af57637d4f4258c1ba460b"),
metadata: {
billYear: 2022,
billNumber: 1
}
},
{
_id: ObjectId("63af57637d4f4258c1ba460c"),
metadata: {
billYear: 2022,
billNumber: 2
}
},
{
_id: ObjectId("63af57637d4f4258c1ba460d"),
metadata: {
billYear: 2023,
billNumber: 1
}
}
I need to get the max value of billYear and within this year the max value of billNumber. So in this example the max year is 2023 and the max value in this year is 1.
I tried this attempt:
Data.aggregate( [ { $max : { billNumber : "$billYear" } } ] )
Update
Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ])
gives me the max year value:
[ { _id: null, maxBillYear: 2023 } ]
So I would think of running a second query with this year to get the max value for the number. But is it possible to do this directly in a single query?
as for your first attempt to get the max year you were not accessing value correctly if you try like this it will work
Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ])
Now the second part of your question to get the max value for the number in single query you can try like this
To get the max bill year from the data
maxBillYear = Data.aggregate([{ $group: { _id: null, maxBillYear: { $max: "$metadata.billYear" }}} ]).first().maxBillYear
To get the max value for the number of bills in a year
Data.aggregate([{ $group: { _id: "$metadata.billYear", maxBillNumber: { $max: "$metadata.billNumber" }}} ])
Yes you can get only max number result on single query
db.collection.aggregate([
{
$group: {
_id: "$metadata.billYear",
maxBillNumber: {
$max: "$metadata.billNumber"
}
}
},
{
$sort: {
_id: -1
}
},
{
$limit: 1
}
])

Creating objects from array of objects in a grouped mongo aggregation

I have been writing an aggregation pipeline to show a summarized version of data from a collection.
Sample Structure of Document:
{
_id: 'abcxyz',
eventCode: 'EVENTCODE01',
eventName: 'SOMEEVENT',
units: 1,
rate: 2,
cost: 2,
distribution: [
{
startDate: 2021-05-31T04:00:00.000+00:00
units: 1
}
]
}
I have grouped it and merged the distribution into a single list with $unwind step before $group:
[
$unwind: {
path: '$distribution',
preserveNullAndEmptyArrays: false
},
$group: {
_id: {
eventName: '$eventName',
eventCode: '$eventCode'
},
totalUnits: {
$sum: '$units'
},
distributionList: {
$push: '$distribution'
},
perUnitRate: {
$avg: '$rate'
},
perUnitCost: {
$avg: '$cost'
}
}
]
Sample Output:
{
_id: {
eventName: 'EVENTNAME101'
eventCode: 'QQQ'
},
totalUnits: 7,
perUnitRate: 2,
perUnitCost: 2,
distributionList: [
{
startDate: 2021-05-31T04:00:00.000+00:00,
units: 1
},
{
startDate: 2021-05-31T04:00:00.000+00:00,
units: 1
},
{
startDate: 2021-06-07T04:00:00.000+00:00,
units: 1
}
]
}
I'm getting stuck at the next step; I want to consolidate the distributionList into a new List with no repeating startDate.
Example: Since first 2 objects of distributionList have the same startDate, it should be a single object in output with sum of units:
Expected:
{
_id: {
eventName: 'EVENTNAME101'
eventCode: 'QQQ'
},
totalUnits: 7,
perUnitRate: 2,
perUnitCost: 2,
newDistributionList: [
{
startDate: 2021-05-31T04:00:00.000+00:00,
units: 2 //units summed for first 2 objects
},
{
startDate: 2021-06-07T04:00:00.000+00:00,
units: 1
}
]
}
I couldn't use $unwind or $bucket as I intend to keep the grouping I did in previous steps ($group).
Can I get suggestions or a different approach if this doesn't seem accurate?
You may want to do the first $group at eventName, eventCode, distribution.startDate level. Then, you can $group again at eventName, eventCode level and using $first to keep your original $group fields.
Here is the Mongo Playground to show the idea for your reference.

MongoDB query - aggregates and embedded documents

Need some help writing a MongoDB query.
Background: I'm building an app that keeps track of donations.
I creating an API in ExpressJS, and I am using Mongoose to hook up to MongoDB.
I have a MongoDB collection called Donations that looks like this:
[
{
donor: 123,
currency: 'CAD',
donationAmount: 50
},
{
donor: 123,
currency: 'USD',
donationAmount: 50
},
{
donor: 789,
currency: 'CAD',
donationAmount: 50
},
{
donor: 123,
currency: 'CAD',
donationAmount: 50
}
]
For each donor, I need to sum up the total amount of donations per currency.
Ideally I want a single MongoDB query that would produce the following dataset. (I'm flexible on the structure ... my only requirement are that in the results, 1) each donor has one and only one document, and 2) this document contains the summed total of each currency type)
[
{
donor: 123,
donations: [
{
CAD : 100,
},
{
USD : 50
}
]
},
{
donor: 789,
donations: [
{
CAD: 50
}
]
},
]
Any ideas on the best way to do this?
My solution right now is pretty ugly - I haven't been able to achieve it without doing multiple queries.
You can run $group twice and use $arrayToObject to build your keys dynamically:
Model.aggregate([
{ $group: { _id: { donor: "$donor", currency: "$currency" }, sum: { $sum: "$donationAmount" } } },
{ $group: { _id: "$_id.donor", donations: { $push: { $arrayToObject: [[{ k: "$_id.currency", v: "$sum" }]] } } } },
{ $project: { _id: 0, donor: "$_id", donations: 1 } }
])
Mongo Playground

Mongodb calculation query--cummulative multiplication

I recently started working in Mongodb for POC. I have one json collection below
db.ccpsample.insertMany([
{
"ccp_id":1,
"period":601,
"sales":100.00
},
{
"ccp_id":1,
"period":602,
"growth":2.0,
"sales":"NULL" ##sales=100.00*(1+(2.0/100)) -- 100.00 comes from(ccp_id:1 and period=601)
},
{
"ccp_id":1,
"period":603,
"growth":3.0,
"sales":"NULL" ##sales=100.00*(1+(2.0/100))**(1+(3.0/100))-- 100.00 comes from(ccp_id:1 and period=601) 2.0 comes from (ccp_id:2 and period=602)
},
{
"ccp_id":2,
"period":601,
"sales":200.00
},
{
"ccp_id":2,
"period":602,
"growth":2.0,
"sales":"NULL" ##sales=200.00*(1+(2.0/100))
},
{
"ccp_id":2,
"period":603,
"growth":3.0,
"sales":"NULL" ##same like above
}
])
And i need to calculate sales field which has NULL by using above documents with matching conditions of ccp_id should same and period field should be equal to 601. I have added a line to demonstrate calculation of sales field in collection itself above. I tried with $graphlookup but no luck. Can you people kindly help or suggest some way?
You can use below aggregation:
db.ccpsample.aggregate([
{ $sort: { ccp_id: 1, period: 1 } },
{
$group: {
_id: "$ccp_id",
items: { $push: "$$ROOT" },
baseSale: { $first: "$sales" },
growths: { $push: "$growth" }
}
},
{
$unwind: {
path: "$items",
includeArrayIndex: "index"
}
},
{
$project: {
cpp_id: "$items.cpp_id",
period: "$items.period",
growth: "$items.growth",
sales: {
$cond: {
if: { $ne: [ "$items.sales", "NULL" ] },
then: "$items.sales",
else: {
$reduce: {
input: { $slice: [ "$growths", "$index" ] },
initialValue: "$baseSale",
in: { $multiply: [ "$$value", { $add: [1, { $divide: [ "$$this", 100 ] }] } ] }
}
}
}
}
}
}
])
Basically to calculate the value for n-th element you have to know following things:
sales value of first element ($first in $group)
the array of all growths ($push in $group)
the n which indicates how many multiplications you have to perform
To calculate the index you should $push all elements into one array and then use $unwind with includeArrayIndex option which will insert the index of unwinded array to field index.
Last step calculates the cumulative multiplication. It uses $slice with index field to evaluate how many growths should be processed. So there will be one element for 601, two elements for 602 and so on.
Then it's time for $reduce to process that array and perform the multiplications based on your formula: (1 + (growth/100))

Aggregation with multiple conditions javascript/mongoose

Im trying to use aggregation on my mongoose .
The ideal script, would sum up all crime.amount and heavy_crime amount the last 100 days, and only get the results relevant with the userid.
(today: todate )
( 100 days ago: fromdate)
model:
time: Number,
userid : String,
crime: {
cash: {type: Number, default: 0},
amount: { type: Number, default: 0}
},
heavy_crime: {
cash: {type: Number, default: 0},
amount: { type: Number, default: 0}
},
Attempting code:
function checkCrimeRating(userid) {
var todate = new Date();
var fromdate = new Date();
var lastfive = new Date();
var MainDate = date.getDate();
fromdate.setHours(0,0,0,0);
todate.setHours(0,0,0,0);
lastfive.setHours(0,0,0,0);
lastfive.setDate(MainDate - 5);
fromdate.setDate(MainDate - 100);
return dailyStats.aggregate(
{ $group: {
_id: "$time" : $gte {fromdate.getTime()}, $lte: {todate.getTime()},
total: { $sum: { $add: ["$crime.amount", "$heavycrime.amount"] } },
}}
).then(function (numberO))
}
So how can i make the aggregation only work sum up the last 100 days, with the right userid?
numberO Would ideally return number of crime and heavycrime the last 100 days.
You are missing the $match stage.
Move your date comparison to match stage followed by $group to sum crimes.
dailyStats.aggregate([
{
"$match": {
"user": userid,
"time": {
"$gte": fromdate.getTime(),
"$lte": todate.getTime()
}
}
},
{
"$group": {
"_id": null,
"total": {
"$sum": {
"$add": [
"$crime.amount",
"$heavycrime.amount"
]
}
}
}
}
]);