Mongodb groupby range of date and id - mongodb

I have a database
{
"_id": "222jMQDEHuHXTuDeF",
"customer_id": "QfdAFubKS9ytdbhbq",
"createdDate": {
"$date": "2020-07-27T08:19:40.791Z"
}
},
{
"_id": "278jKLDEHuHXItDeF",
"customer_id": "HtdAFubJS8ytdnjbe",
"createdDate": {
"$date": "2020-07-26T08:19:40.791Z"
}
},
{
"_id": "128lRLDEHuHXItPhy",
"customer_id": "KodATubJS8yyqkjbe",
"createdDate": {
"$date": "2020-07-25T08:19:40.791Z"
}
}
I need to get data of the previous week where current date is the end date and group by date and customer_id and get the count of customer_id in mongodb.

You can compare like following. For the easiness I have added previouseWeekStart, but you can directly call it inside the $match stage instead of using $addFields. This should definitely work, but I can't show a demo in mongoplayground since I have added subtract symbol (-) for calculating previous date.
db.collection.aggregate([
{
$addFields: {
previouseWeekStart: new Date(new Date()-7*60*60*24*1000)
}
},
{
$match: {
$expr: {
$and: [
{
$gt: [
"$createdDate",
"$previouseWeekStart"
]
},
{
$lt: [
"$createdDate",
new Date()
]
}
]
}
}
},
{
$group: {
_id: {
cusId: "$customer_id",
date: "$createdDate"
},
count: {
$sum: 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

how to check a date is less than today date in mongoose?

my collection in db is
plannedEndDate:{"2020-03-10T11:22:33.677+00:00"}
in controller var tdate= new Date(); [tdate is in the format 2020-03-10T14:28:22.687Z].
now,I need to check plannedEndDate is less than tdate in mongoose.
i tried,
plannedEndCmp: {
$cond: [{ $lt: ["$plannedEndDate", tdate] }, 1, 0]
}
but it is not returning true.
so should i need to trim the timestamp to compare dates in mangodb?or should i need to convert date into common format?
Not clear what you actually like to do but below command find the document or show indication:
db.collection.find(
{ plannedEndDate: { $lt: tdate } }
)
db.collection.aggregate([
{ $set: { plannedEndCmp: { $cond: [{ $lt: ["$plannedEndDate", tdate] }, 1, 0] } } },
{ $set: { plannedEndCmp_bool: { $lt: ["$plannedEndDate", tdate] } } }
])
In Aggregate group:
`
factualEndDate: {
"$dateToString": { "format": "%Y-%m-%d", "date": "$factualEndDate" }
},
In Aggregate project:
DelayedComplete: { $cond: [
{
$and: [
{ $cond: [{ $lte: [{ $max: "$data.factualEndDate" }, today] }, 1, 0] },
]
},
1,
0
]
},
`

I have a record in mongoDB like below. Need a query to get the previous day results using startTime

I have a record in mongoDB like below. Need a query to get the previous day results using startTime.
{
"_id": ObjectId("5c08d195d38e4040788c789a"),
"jobName": "test-ci-build",
"build": "474",
"buildURL": "https://example.com/test-ci-build/474/",
"result": "SUCCESS",
"startTime": "2018-12-06T05:42:22+0000",
"duration": "1 hr 54 min",
"startTimeInMillis": 1544074942061,
"triggeredBy": "Started by timer",
"commit": "b8a04837f1c285e6d9d8852af5801419acd047cb",
"date": ISODate("2018-12-06T07:36:53.045Z")
}
Using the $lt or $lte MongoDB operators on the startTime field, you may get the required data.
You can use:
db.collection.find({startTime:{$lte: "2018-12-06T05:42:22+0000"}})
or
db.collection.find({startTime:{$lte: ISODate("2018-12-06T05:42:22+0000")}})
Consider to find the result for your "startDate < TodaysDate - 1" expression we will compare your "date" field with todays date. (You can modify the Mongo query as per your date fields).
db.collection.aggregate([
{
$addFields: {
yesterdays_date: {
$subtract: [
new Date().getTime(),
86400000
]
}
}
},
{
$addFields: {
convertedDate: {
$toDate: "$yesterdays_date"
}
}
},
{
$addFields: {
"dateComp": {
"$cmp": [
"$date",
"$convertedDate"
]
}
}
},
{
$match: {
"dateComp": -1
}
},
{
$project: {
yesterdays_date: 0,
convertedDate: 0,
dateComp: 0
}
}
])

total of all groups totals using mongodb

i did this Aggregate pipeline , and i want add a field contains the Global Total of all groups total.
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: {
_id:"$_id",
value:"$value",
transaction:"$transaction",
paymentMethod:"$paymentMethod",
createdAt:"$createdAt",
...
}
},
count:{$sum:1},
total:{$sum:"$value"}
}}
{
//i want to get
...project groups , goupsTotal , groupsCount
}
,{
"$skip":cursor.skip
},{
"$limit":cursor.limit
},
])
you need to use $facet (avaialble from MongoDB 3.4) to apply multiple pipelines on the same set of docs
first pipeline: skip and limit docs
second pipeline: calculate total of all groups
{ "$match": query },
{ "$sort": cursor.sort },
{ "$group": {
_id: { key:"$paymentFromId"},
items: {
$push: "$$CURRENT"
},
count:{$sum:1},
total:{$sum:"$value"}
}
},
{
$facet: {
docs: [
{ $skip:cursor.skip },
{ $limit:cursor.limit }
],
overall: [
{$group: {
_id: null,
groupsTotal: {$sum: '$total'},
groupsCount:{ $sum: '$count'}
}
}
]
}
the final output will be
{
docs: [ .... ], // array of {_id, items, count, total}
overall: { } // object with properties groupsTotal, groupsCount
}
PS: I've replaced the items in the third pipe stage with $$CURRENT which adds the whole document for the sake of simplicity, if you need custom properties then specify them.
i did it in this way , project the $group result in new field doc and $sum the sub totals.
{
$project: {
"doc": {
"_id": "$_id",
"total": "$total",
"items":"$items",
"count":"$count"
}
}
},{
$group: {
"_id": null,
"globalTotal": {
$sum: "$doc.total"
},
"result": {
$push: "$doc"
}
}
},
{
$project: {
"result": 1,
//paging "result": {$slice: [ "$result", cursor.skip,cursor.limit ] },
"_id": 0,
"globalTotal": 1
}
}
the output
[
{
globalTotal: 121500,
result: [ [group1], [group2], [group3], ... ]
}
]