MongoDB:计算每次状态值更改之间的停留时间

Posted

技术标签:

【中文标题】MongoDB:计算每次状态值更改之间的停留时间【英文标题】:MongoDB: Calculate dwell time between every status value change 【发布时间】:2020-07-18 22:14:33 【问题描述】:

我想找出每次 PresenceStatus 更改之间的停留时间。

示例集合 -

   /* 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

如您所见,第二个文档中的出席状态已从值 0 更改为 1。

我想记录这个dwell状态保持为1的时间段。(基本上status = 1到status = 0的时间差)

当它再次找到presenceStatus 1 并记录该数据块的presenceStatus 保持为1 的时间段时,相同的过程再次启动。

结果集合看起来像 -


    "_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

感谢您的帮助。

【问题讨论】:

"07:00:53" 是从哪里来的?我的意思是应该如何计算? 我用更多细节更新了我的问题。 “07:00:53”基本上是从第一个文档中提取的时间值,当发现存在状态为1时。 不胜感激。 @newdeveloper:我猜这个日志记录发生在一个设备上?无论如何,对整个集合执行此操作是不可行的,您可能需要考虑对一天的数据或一个月的数据执行此操作,这也取决于每天积累多少文档以及它多久可以变为 0 的用例- 1 或反之亦然,因为如果设备在 5 个月内为 0,我们只能在 1 个月内执行此操作,因为我们将错过 4 个月的休息时间.. @whoami 这实际上是我们每天运行的数据聚合作业的一部分。记录发生在几个设备上,我必须找到一种方法来找到每个设备提到的停留时间。我正在一步一步地发布第一个问题,即每次值从 1 变为 0 时如何找到停留时间。 【参考方案1】:

您可以在集合上使用Map-Reduce,这将以秒为单位输出差异,但您应该相对容易更改。

我没有在任何大型集合上使用这种方法,最近也没有,所以我不知道以这种方式执行它会影响性能,请参阅文档以获取更多信息。

db.collection.mapReduce(
  function() 
    emit(0, this);
  ,
  function(key,values)
    var state = values[0].presenceStatus;
    var stateTimestamp = values[0].updatedAt;
    var result = 
      changes: []
    ;

    for (var i = 1; i < values.length; i++)
      var value = values[i];
      if (value.presenceStatus !== state) 
        if (state === 1) 
          result.changes.push(
            "dwellTime": value.updatedAt - stateTimestamp,
            "occupiedTime": value.updatedAt,
          );
        

        state = value.presenceStatus;
        stateTimestamp = value.updatedAt;
      
    

    return result;
  ,
  
    out:  inline: 1 
  
).results[0].value.changes;
[
        
                "dwellTime" : 26979,
                "occupiedTime" : ISODate("2020-02-16T00:16:02.100Z")
        
]

【讨论】:

你在 mongo 操场上试过这个吗? 我在这里尝试了解决方案并给出了错误 - mongoplayground.net/p/1osX3aBxd0P 另外,我还需要找到“vacantTime”,这实际上是状态从 1 变为 0 的下一个文档时间。【参考方案2】:

检查此解决方案是否满足您的要求。

说明

    我们加入了同一个集合。因此,对于每个 item i,我们采用 item i+1。这种方法可以告诉我们presenceStatus 的更改位置。 我们过滤文档i+1 对,其中presenceStatus0 - 11 - 0。 我们将它们分组到单个 data 数组中。 现在我们将data 迭代两步(i=0;i&lt;data.length;i+=2) 并获取updatedAt 的值。
    var occupiedTime = data[i].tmp.updatedAt
    var vacantTime   = data[i+1].tmp.updatedAt
    我们将计算值展平并恢复原始文档结构。

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

【讨论】:

这是一项很棒的工作。请再看一遍 - mongoplayground.net/p/kYa7bAAim20 我更改了第一个文档的出席状态,现在结果显示停留时间为空。 我们必须在第一个带有 presenceStatus 的文档为 1 时开始聚合。所以如果第一个文档的 presenceStatus = 1 那么这就是我们的第一个 tmp 文档。然后第一个 presenceStatus = 0 是我们的断点。 另外,我厌倦了过滤出席状态: $in: [0, 1] ,在 $match 中但不起作用。我们只需要考虑具有 0 和 1 值的存在状态。 @newdeveloper 检查我的答案,我已经更新了代码。 我刚刚检查了您的新 Playground 更改,结果仍然显示 null 停留时间。【参考方案3】:

@Valijon,@Plancke 感谢所有帮助过的人。我们选择了一种不同的方法,让我们的一个简单的 for 循环现在能够完成这项工作。再次感谢。 如果有人感兴趣,这是最终解决方案:

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;
   
)

【讨论】:

以上是关于MongoDB:计算每次状态值更改之间的停留时间的主要内容,如果未能解决你的问题,请参考以下文章

XState 不会停留在空闲状态

HTTP 请求之间具有持久状态的模型

React 父/子状态更改设计问题

更改状态栏背景颜色

MongoDB聚合:计算数组元素或距离之间的差异

VBA UDF 在每次更改后进行评估