Consider this pipeline.
let Pipeline = [
{ $match: {
}},
{ $group: {
workHours: { $sum: { $divide: [ { $subtract: ['$workTime.end', '$workTime.start'] } , { $multiply: [3600, 1000] }] }},
breakHours: { $sum: { $divide: [ { $subtract: ['$breakTime.end', '$breakTime.start'] } , { $multiply: [3600, 1000] }] }},
weekEndHours: {
$cond: [
{ $or : [ { $eq : [{ $dayOfWeek : '$workTime.start' }, 1 ] }, {$eq : [{ $dayOfWeek : '$workTime.start' }, 7 ] }] },
{ $sum: { $divide: [ { $subtract: ['$workTime.end', '$workTime.start'] } , { $multiply: [3600, 1000] }] }},
0
]
}
}},
];
I'm trying to get sum of work hours, break hours and weekend hours. Work hours and break hours works fine. Now I want to use condition inside weekend hours which is if the day of the date is either 1 or 7 only then count the total hours in the key.
Then it is showing me the error 'The $cond accumulator is a unary operator'. I don't know what is wrong with my group query.
Got Solution and also extended pipeline conditions.
let Pipeline = [
{ $match: {
}},
{ $group: {
_id: '$memberId',
workHours: { $sum: { $divide: [ { $subtract: ['$workTime.end', '$workTime.start'] } , { $multiply: [3600, 1000] }] }},
breakHours: { $sum: { $divide: [ { $subtract: ['$breakTime.end', '$breakTime.start'] } , { $multiply: [3600, 1000] }] }},
weekEndHours: {
$sum: {
$cond: [
{ $or : [ { $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 1 ] }, {$eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 7 ] }] },
{ $divide: [ { $subtract: ['$workTime.end', '$workTime.start'] } , { $multiply: [3600, 1000] }] },
0
]
}
},
weekDayHours: {
$sum: {
$cond: [
{ $or : [
{ $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 2 ] },
{ $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 3 ] },
{ $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 4 ] },
{ $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 5 ] },
{ $eq : [{ $dayOfWeek : { date: '$workTime.start', timezone: timezoneOffset}}, 6 ] }
]
},
{ $divide: [ { $subtract: ['$workTime.end', '$workTime.start'] } , { $multiply: [3600, 1000] }] },
0
]
}
},
}},
{ $addFields:{
totalWorkedHours: { $subtract: [ '$workHours', '$breakHours'] }
}}
];
Related
Hello I heard that mongoDB was very good at aggregating data compared to, for example SQL server.
I tried to translate an SQL query to a mongoDB query but it's a complete failure :
For SQL server we had like 4 minutes which is manageable and now we top at 34 minutes for 128 days using this request in mongoDB :
function(thresholdObs, days)
{
var name = "period-" + days + "j"
var startDate = new Date('2020', '00', '01');
var endDate = new Date(startDate.getTime() + 1000 * 60 * 60 * 24 *[days]);
db.getCollection('measures').aggregate([
{
$match: {
$and: [
{
measureDate: {
$gte: startDate
}
},
{
measureDate: {
$lte: endDate
}
}
]
}
},
{
$addFields: {
multiplyIahobs: {
$cond: [
{$or: [
{$eq: ["$averageIAH", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averageIAH", "$obs"] }
]
},
multiplyPressionobs: {
$cond: [
{$or: [
{$eq: ["$averagePressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averagePressure", "$obs"] }
]
},
multiplyFuitesobs: {
$cond: [
{$or: [
{$eq: ["$averageLeakage", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$averageLeakage", "$obs"] }
]
},
multiplyinspiratoryPressureobs: {
$cond: [
{$or: [
{$eq: ["$inspiratoryPressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$inspiratoryPressure", "$obs"] }
]
},
multiplyexpiratoryPressureobs: {
$cond: [
{$or: [
{$eq: ["$expiratoryPressure", null]},
{$eq: ["$obs", null]}
]},
0,
{ $multiply: ["$expiratoryPressure", "$obs"] }
]
},
enoughDays: {
$cond: [ { $gte: ["$obs", thresholdObs ] }, 1, 0]
},
missingDays: {
$cond: [ { $lt: ["$obs", thresholdObs ] }, 1, 0]
},
daysWithoutUsage: {
$cond: [ { $eq: ["$obs", 0 ] }, 1, 0]
},
daysWithData: { $sum: 1 }
}
},
{
$group: {
_id: "$deviceID",
obsUsage: { $avg: "$obs" },
enoughDays: { $sum: "$enoughDays" },
missingDays: { $sum: "$missingDays" },
daysWithoutUsage: { $sum: "$daysWithoutUsage" },
daysWithData: { $sum: "$daysWithData" },
sumobs: { $sum: "$obs" },
sumMultiplyIahobs: { $sum: "$multiplyIahobs" },
sumMultiplyPressureObs: { $sum: "$multiplyPressionobs" },
sumMultiplyLeakageObs: { $sum: "$multiplyFuitesobs" },
sumMultiplyinspiratoryPressureobs: { $sum: "$multiplyinspiratoryPressureobs" },
sumMultiplyexpiratoryPressureobs: { $sum: "$multiplyexpiratoryPressureobs" },
}
},
{
$addFields: {
fullObs: { $divide: [ "$sumobs", days ] },
daysWithoutData: { $subtract: [ days, "$daysWithData" ]},
averageIAH: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyIahobs", "$sumobs"] } ] },
averagePressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyPressureObs", "$sumobs"] } ] },
averageLeakage: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyLeakageObs", "$sumobs"] } ] },
inspiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyinspiratoryPressureobs", "$sumobs"] } ] },
expiratoryPressure: { $cond: [ { $eq: [ "$sumobs", 0 ] }, 0, { $divide: ["$sumMultiplyexpiratoryPressureobs", "$sumobs"] } ] },
}
},
{
$project: {
_id: 1,
[name] : {
obsUsage: "$obsUsage",
enoughDays: "$enoughDays",
missingDays: "$missingDays",
daysWithoutUsage: "$daysWithoutUsage",
daysWithData: "$daysWithData",
sumobs: "$sumobs",
fullObs: "$fullObs",
daysWithoutData: "$daysWithoutData",
averageIAH: "$averageIAH",
averagePressure: "$averagePressure",
averageLeakage: "$averageLeakage",
inspiratoryPressure: "$inspiratoryPressure",
expiratoryPressure: "$expiratoryPressure"
}
}
},
{
$merge: {
into: "periods",
on: "_id",
whenMatched: "merge",
whenNotMatched: "insert"
}
}
],{allowDiskUse: true})
}
Before throwing out the baby (mongoDB) with the bathwater (the query). I came here asking if my understanding on how to write a good query is off. Should I try to alter this query to make it work under 4 minutes ? Is it possible ? How ?
NB : measures collection contains 374.670.449 documents.
Sample documents :
{
_id: ObjectId('6127a15fef44a9ed52a5bf62'),
deviceId: 5,
measureDateAdded: ISODate('2013-03-15T10:30:35.753Z'),
measureDate: ISODate('2012-03-20T06:00:00.000Z'),
obs: 20,
averageIAH: NumberDecimal('5.50'),
averagePressure: NumberDecimal('6.30'),
averageLeakage: NumberDecimal('13.00'),
inspiratoryPressure: null,
expiratoryPressure: null
},
{
_id: ObjectId('6127a15fef44a9ed52a5bfc6'),
deviceId: 5,
measureDateAdded: ISODate('2013-03-15T10:30:40.063Z'),
measureDate: ISODate('2012-06-28T05:00:00.000Z'),
obs: 197,
averageIAH: NumberDecimal('5.30'),
averagePressure: NumberDecimal('6.90'),
averageLeakage: NumberDecimal('15.00'),
inspiratoryPressure: null,
expiratoryPressure: null
},
{
_id: ObjectId('6127aa2bef44a9ed52a0922a'),
deviceId: 367959,
measureDateAdded: ISODate('2019-01-19T14:13:19.620Z'),
measureDate: ISODate('2019-01-16T11:00:00.000Z'),
obs: 375,
averageIAH: NumberDecimal('2.00'),
averagePressure: NumberDecimal('9.60'),
averageLeakage: NumberDecimal('6.00'),
inspiratoryPressure: null,
expiratoryPressure: null
}
What cost the most seems to be the group with deviceId 4+minutes
I have below document structure:
{
Id: 1,
StartDate: <any date>,
EndDate: <any date>,
TotalValue: <any double value>
}
I want one array in output which contains each date between StartDate and EndDate with value, which is TotalValue divided by days difference of StartDate, EndDate.
So result would look like this:
{
Id: 1,
StartDate: <any date>,
EndDate: <any date>,
Allocations: [{date: date1, val: TotalValue/number of Days}, {date: date2, val: TotalValue/number of Days},,,,{date: daten, val: TotalValue/number of Days}]
}
[ Updated on 17-Feb ]
Now, need to go a step deeper than this. Suppose, I have situation where, for few objects in array of root objects, I might got allocations array too, but not day-wise. I would be getting that range wise. So array might look like this:
{
Id: 1,
StartDate: ISODate("2019-01-01"),
EndDate: ISODate("2019-12-31"),
TotalValue: 3500,
Allocations: [{startDate: ISODate("2019-01-01"), endDate: ISODate("2019-07-30"), val: 2000}, {startDate: ISODate("2019-07-01"), endDate: ISODate("2019-12-31"), val: 1500}]
},
{
Id: 1,
StartDate: ISODate("2020-01-01"),
EndDate: ISODate("2020-12-31"),
TotalValue: 5000
}
So in any variant, I would like to see datewise allocations. If allocations are already provided, use that to calculate daily, otherwise divide TotalValue evenly.
This aggregation gives the desired output of Allocations. Using the following as an example input document:
{
_id : ObjectId("5e42154155533a54ed5fb852"),
Id: 1,
StartDate: ISODate("2020-01-30"),
EndDate: ISODate("2020-02-05"),
TotalValue: 100
}
The query:
db.collection.aggregate( [
{
$addFields: {
daysArr: {
$range: [ 0, { $add: [ { $divide: [ { $subtract: [ "$EndDate", "$StartDate" ] }, 86400000 ] } , 1 ] } ]
}
}
},
{
$addFields: {
Allocations: {
$map: {
input: "$daysArr",
in: {
date: { $add: [ "$StartDate", { $multiply: [ 86400000, "$$this" ] } ] },
val: { $divide: [ "$TotalValue", { $add: [ { $divide: [ { $subtract: [ "$EndDate", "$StartDate" ] }, 86400000 ] }, 1 ] } ] }
}
}
}
}
},
{
$project: { daysArr: 0 }
}
] )
The output document:
{
"_id" : ObjectId("5e42154155533a54ed5fb852"),
"Id" : 1,
"StartDate" : ISODate("2020-01-30T00:00:00Z"),
"EndDate" : ISODate("2020-02-05T00:00:00Z"),
"TotalValue" : 100,
"Allocations" : [
{
"date" : ISODate("2020-01-30T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-01-31T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-02-01T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-02-02T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-02-03T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-02-04T00:00:00Z"),
"val" : 14.285714285714286
},
{
"date" : ISODate("2020-02-05T00:00:00Z"),
"val" : 14.285714285714286
}
]
}
[ Updated as per the enhancement of 17-Feb ]
This aggregation will take care of the two types of input documents:
{
"_id" : ObjectId("5e4a16e96d13751bfb64572d"),
"Id" : 1,
"StartDate" : ISODate("2019-01-01T00:00:00Z"),
"EndDate" : ISODate("2019-12-12T00:00:00Z"),
"TotalValue" : 3500,
"Allocations" : [
{
"startDate" : ISODate("2019-01-01T00:00:00Z"),
"endDate" : ISODate("2019-01-10T00:00:00Z"),
"val" : 2000
},
{
"startDate" : ISODate("2019-01-11T00:00:00Z"),
"endDate" : ISODate("2019-01-12T00:00:00Z"),
"val" : 1500
}
]
}
{
"_id" : ObjectId("5e42154155533a54ed5fb852"),
"Id" : 11,
"StartDate" : ISODate("2020-02-01T00:00:00Z"),
"EndDate" : ISODate("2020-02-05T00:00:00Z"),
"TotalValue" : 100
}
The aggregation query:
db.collection.aggregate( [
{
$addFields: {
Allocations: {
$ifNull: [
"$Allocations",
[ { startDate: "$StartDate", endDate: "$EndDate", val: "$TotalValue" } ]
]
}
}
},
{
$addFields: {
Allocations: {
$map: {
input: "$Allocations",
in: {
$mergeObjects: [
"$$this",
{ range: {
$range: [
0,
{ $add: [ { $divide: [ { $subtract: [ "$$this.endDate", "$$this.startDate" ] }, 86400000 ] } , 1 ] }
]
} }
]
}
}
}
}
},
{
$unwind: "$Allocations"
},
{
$addFields: {
Allocations: {
$map: {
input: "$Allocations.range",
in: {
date: { $add: [ "$Allocations.startDate", { $multiply: [ 86400000, "$$this" ] } ] },
val: { $divide: [ "$Allocations.val", { $size: "$Allocations.range" } ] }
}
}
}
}
},
{
$group: {
_id: "$_id",
Allocations: { $push: "$Allocations" },
doc: { $first: "$$ROOT" }
}
},
{
$addFields: {
"doc.Allocations": {
$reduce: {
input: "$Allocations", initialValue: [ ],
in: { $concatArrays : ["$$value", "$$this"] }
}
}
}
},
{
$replaceRoot: { newRoot: "$doc" }
}
] )
I have documents in db in the following format:
{
"_id" : ObjectId("5d6fb50852020c4a182fc773"),
"startTimestamp" : "1567601927157"
}
What I want to achieve is, using the "startTimestamp" value, create the following new fields:
date (in the format "04-09-2019")
hour (like "18")
month (like "9")
time (like "18:28:47")
weekDay (like "Wednesday")
Can I get a query to do the above operation in all the documents and finally create respective documents in the following format:
{
"startTimestamp" : "1567601927157",
"date" : "04-09-2019",
"hour" : "18",
"month" : "9",
"time" : "18:28:47",
"weekDay" : "Wednesday",
}
Edit:
"startTimestamp" is not the only field present in the documents, it has other fields as well, like below:
{
"useCaseStatus" : "In Progress",
"feedbackRequested" : false,
"userFeedback" : null,
"startTimestamp" : "1567669352778"
}
By adding new fields to the above document, I dont want to delete the fields that are already present(because all the solutions I have got so far removes the other fields present in the documents). Also, adding one more expected document below (Please note that hour and month fields are in string format, not int):
{
"useCaseStatus" : "In Progress",
"feedbackRequested" : false,
"userFeedback" : null,
"startTimestamp" : "1567669352778",
"endTimestamp" : null,
"date" : "05-09-2019",
"hour" : "13",
"month" : "9",
"time" : "13:12:32",
"weekDay" : "Thursday"
}
You can use below aggregation
db.collection.aggregate([
{ "$replaceRoot": {
"newRoot": {
"$let": {
"vars": { "date": { "$toDate": { "$toLong": "$startTimestamp" } } },
"in": {
"$mergeObjects": [
{
"date": { "$dateToString": { "date": "$$date", "format": "%d-%m-%Y" } },
"month": { "$toString": { "$month": "$$date" } },
"hour": { "$toString": { "$hour": "$$date" } },
"time": { "$dateToString": { "date": "$$date", "format": "%H-%M-%S" } },
"weekDay": { "$dayOfWeek": "$$date" }
},
"$$ROOT"
]
}
}
}
}},
{ "$out": "collectionName" }
])
Output
{
"date": "04-09-2019",
"hour": 12,
"month": 9,
"startTimestamp": "1567601927157",
"time": "12-58-47",
"weekDay": 4
}
You need to start with $toLong and $toDate to parse your string. Then you can use $dateToParts and $dayOfWeek. To translate number into string you can use $switch
db.collection.aggregate([
{
$addFields: {
date: {
$toDate: {
$toLong: "$startTimestamp"
}
}
}
},
{
$addFields: {
dateParts: { $dateToParts: { date: "$date" } },
dayOfWeek: { $dayOfWeek: "$date" }
}
},
{
$project: {
startTimestamp: 1,
date: { $dateToString: { date: "$date", format: "%d-%m-%Y" } },
hour: "$dateParts.hour",
month: "$dateParts.month",
time: { $dateToString: { date: "$date", format: "%H:%M:%S" } },
weekDay: {
$switch: {
branches: [
{ case: { $eq: [ "$dayOfWeek", 1 ] }, then: "Sunday" },
{ case: { $eq: [ "$dayOfWeek", 2 ] }, then: "Monday" },
{ case: { $eq: [ "$dayOfWeek", 3 ] }, then: "Tuesday" },
{ case: { $eq: [ "$dayOfWeek", 4 ] }, then: "Wednesday" },
{ case: { $eq: [ "$dayOfWeek", 5 ] }, then: "Thursday" },
{ case: { $eq: [ "$dayOfWeek", 6 ] }, then: "Friday" }
],
default: "Saturday"
}
}
}
}
])
Mongo Playground
You need to implement aggregate pipeline and use date operators available but as you have millisecond saved in string first we have to convert it to int then date then perfrom date operators notice some of them will need timezone to give accurate result instead will just give utc results
db.collection.aggregate([
{
$addFields: {
longMillis: {
$toLong: "$startTimestamp"
}
}
},
{
$project: {
startTimestamp: 1,
"date": {
"$add": [
new Date(0),
"$longMillis"
]
}
}
},
{
$project: {
startTimestamp: 1,
month: {
$month: "$date"
},
day: {
$switch: {
branches: [
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
1
]
},
then: "Sunday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
2
]
},
then: "Monday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
3
]
},
then: "Tuesday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
4
]
},
then: "Wednesday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
5
]
},
then: "Thursday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
6
]
},
then: "Friday"
},
{
case: {
$eq: [
{
$dayOfMonth: "$date"
},
7
]
},
then: "Saturday"
},
],
default: 6
}
},
hour: {
$hour: {
"date": "$date",
"timezone": "+05:30"
}
},
date: {
$dateToString: {
format: "%d-%m-%Y",
date: "$date"
}
},
time: {
$dateToString: {
format: "%H:%M:%S",
date: "$date",
timezone: "+05:30"
}
},
}
}
])
Giving result:
[
{
"date": "04-09-2019",
"day": "Wednesday",
"hour": 18,
"month": 9,
"startTimestamp": "1567601927157",
"time": "18:28:47"
}
]
I have a collection called "project" which is having a field expected time and actual time both are in string format
{
"_id" : ObjectId("5ce7455d77af2d1143f84d49"),
"project_name" : "p1",
"expected" : "0:11:30",
"actual" : "7:30:00",
}
How can I compare two string format times using mongodb?
I want to find if actual time is more than expected
You can use $split with $toInt (MongoDB 4.0 or newer) to convert your string values to a number of seconds and then use $expr to compare both fields:
db.col.aggregate([
{
$addFields: {
expected: {
$let: {
vars: {
parts: {
$split: [ "$expected", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
},
actual: {
$let: {
vars: {
parts: {
$split: [ "$actual", ":" ]
}
},
in: {
$sum: [
{ $toInt: { $arrayElemAt: [ "$$parts", 2 ] } },
{ $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] },
{ $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] }
]
}
}
}
}
},
{
$match: {
$expr: { $gt: [ "$expected", "$actual" ] }
}
}
])
You can convert time to any date you want using $dateFromString operator and then can easily use $lte $gte to perform simple match operations.
db.collection.find({
"$expr": {
"$gt": [
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$actual"]
}
}},
{ "$dateFromString": {
"dateString": {
"$concat": ["2018-09-19", "T", "$expected"]
}
}}
]
}
})
I want to subtract checkin date from checkout date in MongoDB
How Can I do that ??
Data from MongoDB :-
"timeLogs" : [
{
"type" : "chekin",
"date" : ISODate("2017-05-02T05:10:00.000Z"),
"_id" : ObjectId("590822c66216cf3864740e3d")
},
{
"type" : "checkout",
"date" : ISODate("2017-05-02T14:10:00.000Z"),
"_id" : ObjectId("59088037feb56c0fcd60a090")
}
]
Any help will be appriciated ..
I am not quite sure how do you want the format, bu you can try those options as a date or an hour.
db.getCollection('Date').aggregate([
{
$project: {
_id: "$_id",
startDate: {
$arrayElemAt: ["$timeLogs", 0]
},
endDate: {
$arrayElemAt: ["$timeLogs", 1]
}
}
}, {
$project: {
timeDiff: {
$divide: [{
$subtract: ["$endDate.date", "$startDate.date"]
}, 1000 * 60 * 60]
}
}
}
])
or as a date
db.getCollection('sample date').aggregate([
{
$project: {
_id: "$_id",
startDate: {
$arrayElemAt: ["$timeLogs", 0]
},
endDate: {
$arrayElemAt: ["$timeLogs", 1]
}
}
}, {
$project: {
timeDiff: {
$add: [new Date(0), {
$subtract: ["$endDate.date", "$startDate.date"]
}]
}
}
}
])
For mongo <3.2
db.getCollection('Date').aggregate([
{
$unwind: "$timeLogs"
},
{
$group: {
_id: "$_id",
startDate: {
$first: "$timeLogs"
},
endDate: {
$last: "$timeLogs"
}
}
}, {
$project: {
timeDiff: {
$add: [new Date(0), {
$subtract: ["$endDate.date", "$startDate.date"]
}]
}
}
}
])