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

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

Related

MongoDB aggregation of daily records hourly basis

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

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.

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.

How can I check for duplicate nested arrays inside of documents in Mongoose?

Here is an example of a nested document that I have in my collection:
{
"title" : "front-end developer",
"age" : 25,
"name" : "John",
"city" : "London",
"skills" : [
{
"name" : "js",
"project" : "1",
"scores" : [
{
max: 76,
date: date
},
{
max: 56,
date: date
}
]
},
{
"name" : "CSS",
"project" : "5",
"scores" : [
{
max: 86,
date: date
},
{
max: 36,
date: date
},
{
max: 56,
date: date
},
]
}
]
}
Is there a simple way of determining whether other documents have an identical/duplicate structure to the skills array only? e.g. has the same keys, value and array indexes? Any help would be greatly appreciated. Thanks!
Here's how you get that:
collection.aggregate({
"$group": {
"_id": "$skills",
"docs": {
"$push": "$$ROOT"
},
"count": {
$sum: 1
}
}
}, {
$match: {
"count": {
$gt: 1
}
}
})
If you are looking for developers with the same skillset, you can use the $all operator:
var john = db.developers.findOne(...);
var devs = db.developers.find({ 'skills.name': { $all: john.skills.map(x => x.name) } });

MongoDb SUM GROUP BY WHERE

I am very new to MongoDb. I have a collection Order which has multiple documents as follows.
{
"vendor": "amazon",
"date": ISODate("2016-12-05T21:10:39.100Z"),
"products" : [
{
"id": NumberLong(590573),
"totalSold": NumberLong(59),
"totalCost": NumberLong(7350),
"variations": [
{
"varId": NumberLong(1),
"totalSoldV": NumberLong(30),
"totalCostV": NumberLong(3000)
},
{
"varId": NumberLong(2),
"totalSoldV": NumberLong(29),
"totalCostV": NumberLong(4350)
},
]
}
]
}
So what I am trying to achieve is for a particular product.id I want to calculate sum(totalSold) and sum(totalCost) group by date. I have been playing around with aggregate but haven't been able to do so.
db.collection.aggregate([
{$unwind: "$products"},
{$match: {"products.id":NumberLong(590573) }},
{
$group: {
_id: {
year : { "$year" : "$date" },
month : { "$month" : "$date" },
day : { "$dayOfMonth" : "$date" },
hour : { "$hour" : "$date" },
minute : { "$minute" : "$date" },
},
sumTotalSold: {$sum: "$products.totalSold"}, sumTotalCost: {$sum: "$products.totalCost"}
}
}
]).pretty();
Result:
{
"_id" : {
"year" : 2016,
"month" : 12,
"day" : 5,
"hour" : 21,
"minute" : 10
},
"sumTotalSold" : NumberLong(79),
"sumTotalCost" : NumberLong(9850)
}

Resources