MongoDB aggregation of daily records hourly basis - database

I have a collection or orders with the following format:
"createTime" : ISODate("2021-04-16T08:01:39.000Z"),
"statusDetails" : [
{
"createTime" : ISODate("2021-04-16T08:01:39.000Z"),
"createUser" : "FOP-SYSTEM",
"stateOccurTimeStr" : "2021-04-16 15:01:39",
"status" : 27,
"statusDesc" : "Shipped"
}
]
where createTime is showing that when the order has been created
statusDetails.status = 27 showing that order has been shipped
statusDetails.createTime showing that when the order has been shipped
The result which I need is something like this:
Order Date
0-4 Hours
4-8 Hours
8-12 Hours
12-24 Hours
> 24 Hours
Total Orders
01-Jan-21
15
10
4
1
1
31
This shows that on "1-Jan-2021" after order creation,
15 orders shipped between 0-4 hours,
10 orders shipped between 4-8 hours,
4 orders shipped between 8-12 hours
and so on.
What I have done so far is:
db.orders.aggregate([
{ $unwind : "$statusDetails"},
{$match: {"statusDetails.status": { "$exists": true, "$eq": 24 }}},
{$project : { createTime: 1,statusDetails:1,
dateDiff: {"$divide" : [{ $subtract: ["$statusDetails.createTime","$createTime" ] },3600000]}}},
{$sort:{"createTime":-1}}
])
But this is showing time difference of each individual record, but I need group by
Edit
I have updated my query and now it is showing records using $group but still I need to add an another pipeline to group the current data.
db.orders.aggregate([
{ $unwind : "$statusDetails"},
{$match: {"statusDetails.status": { "$exists": true, "$eq": 27 }}},
{$project : { createTime: 1,statusDetails:1, dateDiff:{"$floor": {"$divide" : [{ $subtract: ["$statusDetails.createTime","$createTime" ] },3600000]}}}},
{
$group:
{ _id: { year : { $year : "$createTime" }, month : { $month : "$createTime" }, day : { $dayOfMonth : "$createTime" }},
shippedTime: { $push: "$dateDiff" },
count: { $sum: 1 }
}
},
{$sort:{"createTime":-1}}
])

$unwind to deconstruct statusDetails array
$match your conditions
$addFields to add slot field on the base of your date calculation hour slot from equation and get total
$group by date as per your format using $dateToString and slot
$sort by slot in ascending order
$group by only date and construct array of slots and get total orders
db.collection.aggregate([
{ $unwind: "$statusDetails" },
{ $match: { "statusDetails.status": 27 } },
{
$addFields: {
slot: {
$multiply: [
{
$floor: {
$divide: [
{
$abs: {
"$divide": [
{ $subtract: ["$statusDetails.createTime", "$createTime"] },
3600000
]
}
},
4
]
}
},
4
]
}
}
},
{
$group: {
_id: {
date: {
$dateToString: {
date: "$createTime",
format: "%d-%m-%Y"
}
},
slot: "$slot"
},
total: {
$sum: 1
}
}
},
{ $sort: { "_id.slot": 1 } },
{
$group: {
_id: "$_id.date",
hours: {
$push: {
slot: "$_id.slot",
total: "$total"
}
},
totalOrders: { $sum: "$total" }
}
}
])
Playground
Result would be:
[
{
"_id": "16-04-2021",
"hours": [
{ "slot": 0, "total": 1 }, // from 0 to 4
{ "slot": 4, "total": 1 }, // from 4 to 8
{ "slot": 8, "total": 2 } // from 8 to 12
],
"totalOrders": 4
}
]

Related

MongoDB Aggregate subfields with individual date ranges

I have a schema which is:
{
"_id" : "12345678",
"action1" : [
{
"date" : "2021-01-15",
"value" : 20
},
{
"date" : "2021-01-14",
"value" : 16
}
],
"action2" : [
{
"date" : "2021-01-15",
"value" : 30
},
{
"date" : "2021-01-14",
"value" : 10
}
],
"action3" : [
{
"date" : "2021-01-15",
"value" : 40
},
{
"date" : "2021-01-14",
"value" : 20
}
],
"action4" : [
{
"date" : "2021-01-15",
"value" : 60
},
{
"date" : "2021-01-14",
"value" : 40
}
]
}
Now I want to write an aggregate query to filter out counts within a date range (for last 7 days, or 30 days or 90 days)
so the final sum should look something like the following:
{
_id: "12345678"
action1 : {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action2: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action3: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
action4: {
alltime: number,
last7Days : number,
last30Days: number,
last90Days: number
},
}
I am trying to get the total number of actions using $project and $match for a particular _id
But how can I filter the past7days/30days/90days data
My query looks like the following
db.collection.aggregate([
{
$project: {
alltimeAction1: {$sum: "$action1.value"},
alltimeAction2: {$sum: "$action2.value"},
alltimeAction3: {$sum: "$action3.value"},
alltimeAction4: {$sum: "$action4.value"}
}
},
{
$match: {
_id: "12345678"
}
}
])
is mapReduce the only option available?
db.test1.aggregate([
{
"$project": {//Reshape actions, you need this as you have dynamic keys
data: {
"$objectToArray": "$$ROOT"
}
}
},
{//Denormalize
"$unwind": "$data"
},
{//Denormalize
"$unwind": "$data.v"
},
{
"$project": {//Formatting date
"_id": 1,
"key": "$data.k",
"date": {
"$dateFromString": {
"dateString": "$data.v.date",
"format":"%Y-%m-%d"
}
},
"value": "$data.v.value"
}
},
{
"$addFields": {//Getting the conditions ready
"7Days": {
$gte:["$date", new Date(new Date().getTime() - (7*24*3600*1000))]
},
"30Days": {
$gte:["$date", new Date(new Date().getTime() - (30*24*3600*1000))]
}
}
},
{
$group:{//Grouping them, you can add few more cases
"_id": "$key",
"7days":{
$sum:"$value"
},
"30days":{
$sum:"$value"
}
}
}
])
You don't need to do this much complex query if you have flattened schema where actionName can be identified by a constant field name.

Aggregate group by array and divide quantity to array length

Now I want to aggregate schema to group by users in array and divide items field to array length to create average..
This is simple json data ->
[{"users": ["5ea40086fc4b145b489da93d","5e8cb9a4462e45178c4d3405"],"isBuilt": true, "_id": "5eadd43b30f97f342cf663fc", "items": 3, ...},
{"users": ["5e8cb9a4462e45178c4d3405"], "isBuilt": true, "_id": "5ead419081eec52258b67f70", "items": 5, ...}]
And after aggregating with ->
Building.aggregate([
{
$match: {
updatedAt: {
$gte: startDate,
$lte: endDate
},
isBuilt: true
}
},
{
$unwind: "$users"
},
{
$group: {
_id: "$users",
items: {
$sum: '$items'
}
}
},
{
$project: {
user: '$_id',
items: 1,
_id: 0
}
}
])
I got this json ->
[{"items": 3, "user": "5ea40086fc4b145b489da93d"}, {"items": 8, "user": "5e8cb9a4462e45178c4d3405"}]
As you see here I got sum of items. In initial data Users "5ea40086fc4b145b489da93d" and "5e8cb9a4462e45178c4d3405" have 3 items, and user "5e8cb9a4462e45178c4d3405" has 5 items. And after aggregating they count by sum of items, that user "5e8cb9a4462e45178c4d3405" -> 8 items, and user "5ea40086fc4b145b489da93d" -> 3 items... Now I want make average items to users, like if length of array users is 2 or more it will divide items and give sum.. and final json will look like ->
[{"items": 1.5, "user": "5ea40086fc4b145b489da93d"}, {"items": 6.5, "user": "5e8cb9a4462e45178c4d3405"}]
PS if result of item is not integer, result should be rounded to ten
I've solved my problem with aggregation ->
Building.aggregate([
{
$match: {
updatedAt: {
$gte: startDate,
$lte: endDate
},
isBuilt: true
}
},
{
$addFields: {
itemsAvg: {
$divide: ["$items", {$size: "$users"}]
}
}
},
{
$addFields: {
roundedItemsAvg: {
$round: ["$itemsAvg", 1]
}
}
},
{
$unwind: "$users"
},
{
$group: {
_id: "$users",
items: {
$sum: '$roundedItemsAvg'
}
}
},
{
$project: {
user: '$_id',
items: 1,
_id: 0
}
}
])

Query Mongodb Sum of firsts element of an array of objects

I would like to write a query for summing each field payment of the first object inside an array, for each element of my database.
The schema is the following:
var schema = new Schema({
plate : String,
category : String,
brand : String,
model : String,
sign : String,
tax : [{
date : { type: Date, default: Date.now },
payment : { type: Number, default: 0 },
}],
});
I wrote the following function for my query:
function(callback){
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: "$tax.payment"}
}}
]
,callback);
}
But in this way I retrieve the sum of all the payments inside the array tax. My goal is to take only the first, so I tried with $tax.0.payment and using arrayElemAt : [$tax,0] but all my trials gave a tot = 0.
The idea here is pick out the first element of each of payment array field via $arrayElemAt with projection and then group-sum the field $group $sum.
Query:
db.collection.aggregate([
{
$project: {
firstPayment: {
$arrayElemAt: [
"$tax",
0
]
}
}
},
{
$group: {
_id: null,
PaymentSum: {
$sum: "$firstPayment.payment"
}
}
}
]);
Demo O/P:
[
{
"PaymentSum": 11,
"_id": null
}
]
Machine.aggregate({$unwind:
{path: "$tax"}
},
{$group:{
_id: "$_id",
payment: {$first: "$tax.payment"}
}},
{$group: {
_id: null,
total: {$sum: "$payment"}
}}
)
Explanation:
First I used $unwind on tax, then in the first $group stage I grouped them according to _id,
that way I will get the first payment information from unwinded tax array.
Then I used $sum to add them in the second $group stage.
I tested with this data:
Machine collection docs:
{
"_id" : ObjectId("5dbf09a4d7912bcbc61ee9e4"),
"tax" : [
{
"payment" : 10
},
{
"payment" : 20
}
]
},
{
"_id" : ObjectId("5dbf09aad7912bcbc61ee9e5"),
"tax" : [
{
"payment" : 30
},
{
"payment" : 40
}
]
},
{
"_id" : ObjectId("5dbf09afd7912bcbc61ee9e6"),
"tax" : [
{
"payment" : 50
},
{
"payment" : 60
}
]
}
The result I got is:
{ "_id" : null, "tot" : 90 }
I hope this fulfills your requirements.
Add $arrayElemAt in your aggregate like this..
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: { $arrayElemAt : [ "$tax.payment", 0 ]}
}}
]
,callback);

total register users of current month with each date

Here, date is register date and with simple group by I got result like this
[
{ date: '2019-09-01', count: 1 },
{ date: '2019-09-02', count: 3 },
{ date: '2019-09-04', count: 2 },
{ date: '2019-09-05', count: 5 },
// ...
]
But I want each and every date if on that date user is not register that display count as 0
[
{ date: '2019-09-01', count: 1 },
{ date: '2019-09-02', count: 3 },
{ date: '2019-09-03', count: 0 },
{ date: '2019-09-04', count: 0 },
// ...
]
If the user is not registered on 3 and 4 dates then displays 0 counts.
monthalldate = [ '2019-09-1', '2019-09-2', '2019-09-3', '2019-09-4', '2019-09-5', '2019-09-6', '2019-09-7', '2019-09-8', '2019-09-9',
'2019-09-10', '2019-09-11', '2019-09-12', '2019-09-13',.......,
'2019-09-30' ]
User.aggregate([
{ "$group": {
"_id": { "$substr": ["$createdOn", 0, 10] },
"count": { "$sum": 1 },
"time": { "$avg": "$createdOn" },
}},
{ "$sort": { "_id": 1 } },
{ "$project": { "date": "$_id", "createdOn": "$count" }},
{ "$group": { "_id": null, "data": { "$push": "$$ROOT" }}},
{ "$project": {
"data": {
"$map": {
"input": monthalldate,
"in": {
"k": "$$this",
"v": { "$cond": [{ "$in": ["$$this", "$data.date" ] }, 1, 0 ] }
}
}
}
}},
{ "$unwind": "$data" },
{ "$group": { "_id": "$data.k", "count": { "$sum": "$data.v" }}}
]).exec(function (err, montlysub) {
// console.log(montlysub);
});
But I got the wrong result
My user collection
{ "_id" : ObjectId("5a0d3123f954955f15fe88e5"), "createdOn" : ISODate("2019-11-16T06:33:07.838Z"), "name":"test" },
{ "_id" : ObjectId("5a0d3123f954955f15fe88e6"), "createdOn" : ISODate("2019-11-17T06:33:07.838Z"), "name":"test2" }
$project transforms input documents. If there is no user record for a particular month, there are no input documents to transform and you won't have any output for that month.
Ways around this:
Create a collection containing the months (only), then start by retrieving the desired date range from this collection and joining user documents to each month.
Add the missing zero data points in the application, either as the results are iterated or as a post-processing step prior to result rendering.

Group by project,user and month , and sume the hours

This keeps a record of each time the worker has worked on that project, and there may be several records in a single day because he works on several things.
I have this collection "WORKSDAYS":
{
"_id" : "00007cASDASDASDAS32423423",
"workDate" : ISODate("2017-01-20T00:00:00.000+00:00"),
"hours" : 6.0,
"worker" : {
"$ref" : "T_WORKERS",
"$id" : "f3f849d1-8777-4066-bdc6-64625475bbff"
},
"project" : {
"$ref" : "T_PROJECTS",
"$id" : "03b80b87-dc3b-4dc3-88c4-2b3334758459"
}
}
I need something similar to this for each project:
{
_id: { $ref: T_WORKERS, $id: id},
january: 168,
febrary: 120,
...
}
Or instead of January put 0 or 1, february = 02, etc.
I tried this that the workers gives me and the total hours of the project, but I would like the hours for each month:
aggregate([
{
$match: {
"project.$id": "03b80b87asdasdasdasd9"
}
},{
$group: {
"_id": "$worker",
"hours":{"$sum":"$hours"}
}
}
])
I had also thought, in passing him the start and end date (example: January and December and to join them)
.aggregate([
{$match: {
"project.$id": "03b80b8asdasdasdasdasd7"
}
}, {
$group: {
"_id": "$worker",
"month": {
"$sum": {
"$cond": [
{ "$gt": [
{ "$subtract": [ISODate("2018-01-01T00:00:00.000Z"), ISODate("2017-01-01T00:00:00.000Z") ] },
new Date().valueOf() - ( 1000 * 60 * 60 * 24 )
]},
"$hours",
0
]
}
}
}
}])

Resources