MongoDB: Calculate dwell time between every status value change
I want to find out the dwell time between every presenceStatus change.
Example collection -
/* 1 */
{
"_id" : ObjectId("5e4889a7c7959f6a13039902"),
"presenceStatus" : 0,
"createdAt" : ISODate("2020-02-16T00:14:35.121Z"),
"updatedAt" : ISODate("2020-02-16T00:14:35.121Z"),
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("5e4889a7c7959f6a1303990c"),
"presenceStatus" : 1,
"createdAt" : ISODate("2020-02-16T00:15:35.121Z"),
"updatedAt" : ISODate("2020-02-16T00:15:35.121Z"),
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("5e4889a9c7959f6a1303995c"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:37.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:37.420Z"),
"updatedAt" : ISODate("2020-02-16T00:15:37.420Z"),
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("5e4889b0c7959f6a130399ff"),
"presenceStatus" : 1,
"createdAt" : ISODate("2020-02-16T00:15:44.316Z"),
"updatedAt" : ISODate("2020-02-16T00:15:44.316Z"),
"__v" : 0
}
/* 5 */
{
"_id" : ObjectId("5e4889b3c7959f6a13039a58"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:47.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:47.181Z"),
"updatedAt" : ISODate("2020-02-16T00:15:47.181Z"),
"__v" : 0
}
/* 6 */
{
"_id" : ObjectId("5e4889b5c7959f6a13039aad"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:49.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:49.545Z"),
"updatedAt" : ISODate("2020-02-16T00:15:49.545Z"),
"__v" : 0
}
/* 7 */
{
"_id" : ObjectId("5e4889b9c7959f6a13039b28"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:53.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:53.389Z"),
"updatedAt" : ISODate("2020-02-16T00:15:53.389Z"),
"__v" : 0
}
/* 8 */
{
"_id" : ObjectId("5e4889bcc7959f6a13039b78"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:56.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:56.007Z"),
"updatedAt" : ISODate("2020-02-16T00:15:56.007Z"),
"__v" : 0
}
/* 9 */
{
"_id" : ObjectId("5e4889bfc7959f6a13039c00"),
"presenceStatus" : 1,
"sensingTime" : ISODate("2020-02-16T00:15:59.000Z"),
"createdAt" : ISODate("2020-02-16T00:15:59.619Z"),
"updatedAt" : ISODate("2020-02-16T00:15:59.619Z"),
"__v" : 0
}
/* 10 */
{
"_id" : ObjectId("5e4889c2c7959f6a13039c4a"),
"presenceStatus" : 0,
"sensingTime" : ISODate("2020-02-16T00:16:02.000Z"),
"createdAt" : ISODate("2020-02-16T00:16:02.100Z"),
"updatedAt" : ISODate("2020-02-16T00:16:02.100Z"),
"__v" : 0
}
As you can see presenceStatus changed in 2nd document from value 0 to 1.
I want to record this dwell Time period when the status remained 1. (basically time difference between status = 1 to status = 0 )
The same process starts again when it finds presenceStatus 1 again and records the time-period when presenceStatus remained 1 for that chunk of data.
Result collection would look like -
{
"_id" : xxx
"occupiedTime" : ISODate("2020-02-16T00:15:35.121Z"), // *updatedAt* value of document 2, cause that's when it changed to 1.
"vacantTime" : ISODate("2020-02-16T00:16:02.100Z"), // *updatedAt* of document 10, because that's when status changed from 0 to 1.
"dwellTime" : (vacant time - occupied time in HH:MM:SS)
"created" : "2019-05-29 07:08:13",
"__v" : 0
}
Your help is appreciated.
Check if this solution meets your requirements.
Explanation
- We join over the same collection. So for each item i we take item i+1. This method gives us where
presenceStatus
has been changed. - We filter documenti i+1 pairs where
presenceStatus
is0 - 1
or1 - 0
. - We group them into single
data
array. - Now we iterate over
data
by 2 steps(i=0;i<data.length;i+=2)
and takeupdatedAt
value.
var occupiedTime = data[i].tmp.updatedAt
var vacantTime = data[i+1].tmp.updatedAt
- We flatten calculated values and restore original document structure.
db.collection.aggregate([
{
$lookup: {
from: "collection",
let: {
root_id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$gt: [
"$_id",
"$$root_id"
]
}
}
},
{
$limit: 1
}
],
as: "tmp"
}
},
{
$match: {
$or: [
{
"presenceStatus": 1,
"tmp.presenceStatus": 0
},
{
"presenceStatus": 0,
"tmp.presenceStatus": 1
}
]
}
},
{
$group: {
_id: null,
data: {
$push: {
$mergeObjects: [
"$$ROOT",
{
tmp: {
$arrayElemAt: [
"$tmp",
0
]
}
}
]
}
}
}
},
{
$addFields: {
data: {
$map: {
input: {
$range: [
0,
{
$size: "$data"
},
2
]
},
as: "idx",
in: {
"occupiedTime": {
$arrayElemAt: [
"$data.tmp.updatedAt",
{
$cond: [
{
$eq: [
{
$arrayElemAt: [
"$data.tmp.presenceStatus",
"$$idx"
]
},
1
]
},
"$$idx",
{
$add: [
"$$idx",
1
]
}
]
}
]
},
"vacantTime": {
$arrayElemAt: [
"$data.tmp.updatedAt",
{
$cond: [
{
$eq: [
{
$arrayElemAt: [
"$data.tmp.presenceStatus",
"$$idx"
]
},
0
]
},
"$$idx",
{
$add: [
"$$idx",
1
]
}
]
}
]
},
"created": {
$arrayElemAt: [
"$data.tmp.createdAt",
"$$idx"
]
},
"_id": {
$arrayElemAt: [
"$data.tmp._id",
"$$idx"
]
},
"__v": 0
}
}
}
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: "$data"
}
},
{
$addFields: {
"dwellTime": {
$dateToString: {
date: {
$toDate: {
$subtract: [
"$vacantTime",
"$occupiedTime"
]
}
},
format: "%H-%M-%S"
}
}
}
}
])
MongoPlayground
@Valijon, @Plancke Thanks all who helped. we chose to take a different approach and turned our a simple for loop was able to do the job for now. Thanks again. Here is final solution if any one was interested:
let prevSensingResults = {};
db.sensingresults.find({updatedAt : {$gt :"",$lte : "")}, presenceStatus : {$exists: 1}}).sort({updatedAt:1})
.forEach(function(doc) {
if (typeof prevSensingResults[doc.deviceId.toString()] !== undefined) {
if (prevSensingResults[doc.deviceId.toString()].presenceStatus !== doc.presenceStatus && doc.presenceStatus === 0) {
db.presenceagg.update({accountId: doc.accountId, buildingId: doc.buildingId, gatewayId: doc.gatewayId, deviceId: doc.deviceId, occupiedTime: prevSensingResults[doc.deviceId.toString()].updatedAt, vacantTime: doc.updatedAt}
, {accountId: doc.accountId, buildingId: doc.buildingId, gatewayId: doc.gatewayId, deviceId: doc.deviceId, occupiedTime: prevSensingResults[doc.deviceId.toString()].updatedAt, vacantTime: doc.updatedAt, dwellPeriodInSeconds: (doc.updatedAt.getTime() - prevSensingResults[doc.deviceId.toString()].updatedAt.getTime()) / 1000}
, {upsert:true});
prevSensingResults[doc.deviceId.toString()] = doc;
} else if (prevSensingResults[doc.deviceId.toString()].presenceStatus !== doc.presenceStatus && doc.presenceStatus === 1)
prevSensingResults[doc.deviceId.toString()] = doc;
}
} else {
prevSensingResults[doc.deviceId.toString()] = doc;
}
})