How to group orders by data range in MongoDB? - mongodb

Suppose I have a collection like this:
[
{
"_id": ObjectId("5e3dd3d57f8bc30a7513e843"),
"deleted": true,
"date": "01/01/2020"
"total": 3
},
{
"_id": ObjectId("5e3dd3e97f8bc30a7513e99b"),
"date": "02/01/2020",
"deleted": false,
"total": 11
},
{
"_id": ObjectId("5e3dd3e97f8bc30a75137635"),
"date": "15/02/2020",
"deleted": false,
"total": 5
},
{
"_id": ObjectId("5e3dd3e97f8bc30a75131725"),
"date": "18/02/2020",
"deleted": false,
"total": 7
},
{
"_id": ObjectId("5e3dd3e97f8bc30a75131725"),
"date": "03/03/2020",
"deleted": false,
"total": 9
}
]
I need to merge these orders by a range to receive something like this:
{
"january": [order1, order2],
"february": [order3, order4],
"march": [order5]
}
of course I don't need the words "january, february" etc specifically, just something that let me group by data ranges. Something like this:
db.sales.aggregate( [
{ $group: { date: { "$gte": new Date(req.query.minDate), "$lte": new Date(req.query.maxDate) }, mergedOrders: { ?? } } }
])
which is not near a valid group aggregate call.
So, how do I group orders by data range? (I need to get, for each data range, the entire array of orders in that data range, as they are, without excluding fields)

You can try this :
db.sales.aggregate([
{ $match: { date: { "$gte": new Date(req.query.minDate), "$lte": new Date(req.query.maxDate) } } },
{
$group: {
_id: {
$month: {
$dateFromString: {
dateString: '$date',
format: "%d/%m/%Y"
}
}
}, mergedOrders: { $push: '$$ROOT' }
}
}, {
$addFields: {
_id: {
$let: {
vars: {
monthsInString: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']
},
in: {
$arrayElemAt: ['$$monthsInString', '$_id']
}
}
}
}
}])
Test : MongoDB-Playground

You can use $group operator to group data based on a particular value in field. Then you may use push operator to have array of those values.
db.sales.aggregate([
$match: {
date: { "$gte": new Date(req.query.minDate), "$lte": new Date(req.query.maxDate) }
},
$group : {
_id: "$date",
orders: {
$push: {
deleted: "$deleted",
total: "$total"
}
}
},
$project: {
_id: 1,
orders: 1
}
)];

Related

Update table with merge in mongodb

I have a collection with values associated to the sells of (almost a million) different products by day, and I have to create the collection with the aggregation per week. I do it with the following (working) query.
Brief explanation:
I filter the dates I want to use in the query.
I convert the weird date format to a real date.
I group by name of the object, year and week, getting the sum per week.
I group again by name to have all dates in the same document.
I save it to a table.
[
{
$match:
/**
* query: The query in MQL.
*/
{
$and: [
{
"_id.date": {
$gte: "20220103",
},
},
{
"_id.date": {
$lte: "20230122",
},
},
],
},
},
{
$project:
/**
* specifications: The fields to
* include or exclude.
*/
{
_id: 1,
realDate: {
$dateFromString: {
dateString: "$_id.date",
format: "%Y%m%d",
},
},
count: 1,
},
},
{
$group:
/**
* _id: The id of the group.
* fieldN: The first field name.
*/
{
_id: {
name: "$_id.name",
year: {
$isoWeekYear: "$realDate",
},
week: {
$isoWeek: "$realDate",
},
},
total: {
$sum: "$count",
},
},
},
{
$group:
/**
* _id: The id of the group.
* fieldN: The first field name.
*/
{
_id: "$_id.name",
dates: {
$addToSet: {
year: "$_id.year",
week: "$_id.week",
count: "$total",
},
},
},
},
{
$merge:
/**
* into: The target collection.
* on: Fields to identify.
* let: Defined variables.
* whenMatched: Action for matching docs.
* whenNotMatched: Action for non-matching docs.
*/
{
into: "dataPerWeek",
on: "_id",
},
},
]
That works, and generate documents like:
{
"_id": "myProduct",
"dates": [
{
"year": {
"$numberLong": "2022"
},
"week": 52,
"count": 10
},
{
"year": {
"$numberLong": "2022"
},
"week": 50,
"count": 6
},
{
"year": {
"$numberLong": "2022"
},
"week": 49,
"count": 2
},
{
"year": {
"$numberLong": "2022"
},
"week": 51,
"count": 5
},
{
"year": {
"$numberLong": "2023"
},
"week": 1,
"count": 5
},
{
"year": {
"$numberLong": "2023"
},
"week": 2,
"count": 2
},
{
"year": {
"$numberLong": "2023"
},
"week": 3,
"count": 4
}
]
}
Now, I would want now to update this list every week adding only the new elements to the array (or creating a new object if it does not exist. But, if I repeat the merge query above limiting the dates to the last week, it basically removes all other data points. Is is possible to do this "update" with a single query?
You should store date values as Date objects. Storing date values as string is a design flaw.
Your pipeline can be shorter and your $merge stage would be like this:
[
{
$match: {
"_id.date": {
$gte: ISODate("2022-01-03"),
$lte: ISODate("2023-01-22"),
}
}
},
{
$group: {
_id: {
name: "$_id.name",
week: { $dateTrunc: { date: "_id.date", unit: "week", startOfWeek: "monday" } }
},
total: { $sum: "$count" },
}
},
{
$group: {
_id: "$_id.name",
dates: {
$push: { // $push should be faster than $addToSet, result is the same
year: { $isoWeekYear: "$_id.week" },
week: { $isoWeek: "$_id.week" },
count: "$total",
}
}
}
},
{
$merge: {
into: "dataPerWeek",
on: "_id",
whenMatched: [
{ $set: { dates: { $concatArrays: ["$dates", "$$new.dates"] } } }
]
}
}
]
The dates elements are simply concatenated. If you like to update existing elements then you need to iterate over all elements with $map

MongoDB aggregate $group $sum that matches date inside array of objects

I'll explain my problem here and i'll put a tldr at the bottom summarizing the question.
We have a collection called apple_receipt, since we have some apple purchases in our application. That document has some fields that we will be using on this aggregation. Those are: price, currency, startedAt and history. Price, currency and startedAt are self-explanatory. History is a field that is an array of objects containing a price and startedAt. So, what we are trying to accomplish is a query that gets every document between a date of our choice, for example: 06-06-2020 through 10-10-2022 and get the total price combined of all those receipts that have a startedAt between that. We have a document like this:
{
price: 12.9,
currency: 'BRL',
startedAt: 2022-08-10T16:23:42.000+00:00
history: [
{
price: 12.9,
startedAt: 2022-05-10T16:23:42.000+00:00
},
{
price: 12.9,
startedAt: 2022-06-10T16:23:42.000+00:00
},
{
price: 12.9,
startedAt: 2022-07-10T16:23:42.000+00:00
}
]
}
If we query between dates 06-06-2022 to 10-10-2022, we would have a return like this: totalPrice: 38,7.
-total price of the 3 objects that have matched the date inside that value range-
I have tried this so far:
AppleReceipt.aggregate([
{
$project: {
price: 1,
startedAt: 1,
currency: 1,
history: 1,
}
},
{
$unwind: {
path: "$history",
preserveNullAndEmptyArrays: true,
}
},
{
$match: {
$or: [
{ startedAt: {$gte: new Date(filters.begin), $lt: new Date(filters.end)} },
]
}
},
{
$group: {
_id: "$_id",
data: { $push: '$$ROOT' },
totalAmountHelper: { $sum: '$history.price' }
}
},
{
$unwind: "$data"
},
{
$addFields: {
totalAmount: { $add: ['$totalAmountHelper', '$data.price'] }
}
}
])
It does bring me the total value but I couldn't know how to take into consideration the date to make the match stage to only get the sum of the documents that are between that date.
tl;dr: Want to make a query that gets the total sum of the prices of all documents that have startedAt between the dates we choose. Needs to match the ones inside history field - which is an array of objects, and also the startedAt outside of the history field.
https://mongoplayground.net/p/lOvRbX24QI9
db.collection.aggregate([
{
$set: {
"history_total": {
"$reduce": {
"input": "$history",
"initialValue": 0,
"in": {
$sum: [
{
"$cond": {
"if": {
$and: [
{
$gte: [
new Date("2022-06-06"),
{
$dateFromString: {
dateString: "$$this.startedAt"
}
}
]
},
{
$lt: [
{
$dateFromString: {
dateString: "$$this.startedAt"
}
},
new Date("2022-10-10")
]
},
]
},
"then": "$$this.price",
"else": 0
}
},
"$$value",
]
}
}
}
}
},
{
$set: {
"history_total": {
"$sum": [
"$price",
"$history_total"
]
}
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"currency": "BRL",
"history": [
{
"price": 12.9,
"startedAt": "2022-05-10T16:23:42.000+00:00"
},
{
"price": 12.9,
"startedAt": "2022-06-10T16:23:42.000+00:00"
},
{
"price": 12.9,
"startedAt": "2022-07-10T16:23:42.000+00:00"
}
],
"history_total": 325.79999999999995,
"price": 312.9,
"startedAt": "2022-08-10T16:23:42.000+00:00"
}
]
Kudos goes to #user20042973

Aggregation: Grouping based on array element in MongoDB

I am new to MongoDB, trying to write an aggregation function such that my output for the input should be same as below
[
{
"_id": {
"month": 1,
"year": 2022
},
"childServices": [
{"service":"MCT Latency", "sli":99.9},
{"service":"MCT Packet Loss", "sli":99.9}
],
"service": "Network"
},
{
"_id": {
"month": 2,
"year": 2022
},
"childServices": [
{"service":"MCT Latency", "sli":98.9},
{"service":"MCT Packet Loss", "sli":99.9}
]
"service": "Network",
}
]
Tried with below, but it's not grouping each childService by date.
[{
$unwind: {
path: '$childServices'
}
}, {
$group: {
_id: {
month: {
$month: '$date'
},
year: {
$year: '$date'
}
},
service: {
$first: '$service'
},
childServices: {
$first: '$childServices.service'
},
sli: {
$avg: '$childServices.availability'
}
}
}, {
$sort: {
'_id.month': 1,
'_id.year': 1
}
}]
SAMPLE DATA
[{
"_id": {
"$oid": "62fc99c00f5b1cb61d5f1072"
},
"service": "Network",
"date": "01/02/2022 00:32:51",
"childServices": [
{
"service": "MCT Latency",
"availability": 99.9,
},
{
"service": "MCT Packet Loss",
"availability": 99.9,
}
},
{
"_id": {
"$oid": "62fc99df0f5b1cb61d5f1073"
},
"service": "Network",
"date": "02/02/2022 00:32:51",
"childServices": [
{
"service": "MCT Latency",
"availability": 98.3,
},
"service": "MCT Packet Loss",
"availability": 99.9,
}
}
]
Basically, I want to get into the childService > pick each service > group them by month+year and get their monthly avg.
Convert the date from a string to a date type, before grouping, like this:
db.collection.aggregate([
{
$unwind: {
path: "$childServices"
}
},
{
$addFields: {
date: {
"$toDate": "$date"
}
}
},
{
$group: { <---- Here we are grouping the data for each distinct combination of month, year and child service. This needs to be done because we are using $first accumulator
_id: {
month: {
$month: "$date"
},
year: {
$year: "$date"
},
service: "$childServices.service"
},
service: {
$first: "$service"
},
childServices: {
$first: "$childServices.service"
},
sli: {
$avg: "$childServices.availability"
}
}
},
{
"$group": { <-- In this group, we groupBy month and year, and we push the child services record into an array, using $push. This gives us, for every month and year, the average of all distinct childservices
"_id": {
month: "$_id.month",
year: "$_id.year"
},
"childServices": {
"$push": {
service: "$childServices",
sli: "$sli"
}
}
}
},
{
$sort: {
"_id.month": 1,
"_id.year": 1
}
}
])
Playground link.

Mongodb aggregation , group by items for the last 5 days

I'm trying to get the result in some form using mongodb aggregation.
here is my sample document in the collection:
[{
"_id": "34243243243",
"workType": "TESTWORK1",
"assignedDate":ISODate("2021-02-22T00:00:00Z"),
"status":"Completed",
},
{
"_id": "34243243244",
"workType": "TESTWORK2",
"assignedDate":ISODate("2021-02-21T00:00:00Z"),
"status":"Completed",
},
{
"_id": "34243243245",
"workType": "TESTWORK3",
"assignedDate":ISODate("2021-02-20T00:00:00Z"),
"status":"InProgress",
}...]
I need to group last 5 days data in an array by workType count having staus completed.
Expected result:
{_id: "TESTWORK1" , value: [1,0,4,2,3] ,
_id: "TESTWORK2" , value: [3,9,,3,5],
_id : "TESTWORK3", value: [,,,3,5]}
Here is what I'm trying to do, but not sure how to get the expected result.
db.testcollection.aggregate([
{$match:{"status":"Completed"}},
{$project: {_id:0,
assignedSince:{$divide:[{$subtract:[new Date(),$assignedDate]},86400000]},
workType:1
}
},
{$match:{"assignedSince":{"lte":5}}},
{$group : { _id:"workType", test :{$push:{day:"$assignedSince"}}}}
])
result: {_id:"TESTWORK1": test:[{5},{3}]} - here I'm getting the day , but I need the count of the workTypes on that day.
Is there any easy way to do this? Any help would be really appreciated.
Try this:
db.testcollection.aggregate([
{
$match: { "status": "Completed" }
},
{
$project: {
_id: 0,
assignedDate: 1,
assignedSince: {
$toInt: {
$divide: [{ $subtract: [new Date(), "$assignedDate"] }, 86400000]
}
},
workType: 1
}
},
{
$match: { "assignedSince": { "$lte": 5 } }
},
{
$group: {
_id: {
workType: "$workType",
assignedDate: "$assignedDate"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.workType",
values: { $push: "$count" }
}
}
]);

how to convert price from string to int in mongodb and pass it through $project

here I am calculating count and price according to group by I am getting the count but price total is coming as 0. how to send price total .
db.connect(function(err) {
if (err) {
res.sendStatus(510);
} else {
var collection = db.get().collection('Order');
collection.aggregate(
[
{
"$match": {
"$and": [{
"createdDate": {
"$gte": dateFrom
}
},
{
"createdDate": {
"$lte": dateTo
}
}
]
}
},
{
"$project": {
createdDate: {
$dateToString: { format: "%Y-%m-%d", date: "$createdDate" },
},
paymentType: "$paymentType",
price:"$price"
}
},
{
"$group": {
"_id": {
"createdDate": "$createdDate",
"paymentType": "$paymentType"
},
"COUNT(_id)": {"$sum": 1},
"TOTALPRICE": { "$sum" : "$price" }
}
},
{
"$project": {
"COUNT": "$COUNT(_id)",
"TOTALPRICE" : "$TOTALPRICE",
"createdDate": "$_id.createdDate",
"paymentType": "$_id.paymentType",
"_id": 0
}
},
{
"$sort": {
"createdDate": 1
}
}
], {
"allowDiskUse": true
}
)
query response is:
COUNT: 1
TOTALPRICE: 0
createdDate: "2019-09-24"
paymentType: "cod"
TOTALPRICE is coming 0 which is 70.
You need to add price to the $projection step before the $group:
{
"$project": {
createdDate: {
$dateToString: { format: "%Y-%m-%d", date: "$createdDate" },
},
paymentType: "$paymentType",
price: "$price"
}
}
Otherwise you're summing on a field that no longer exists due to the projection phase dropping it, hence the 0.