计算开始和结束日期每天的总小时数

时间:2019-03-17 23:46:07

标签: mongodb mongodb-query aggregation-framework

我有很多这样的文件:

{_id: ObjectId("5adc864eaaf408a2b6e325f7"), employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-04-22 12:06:46.623" }, start: { day: "2018-04-22 11:06:46.623" }, date: "2018-04-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325c8"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-04-22 10:06:46.623" }, start: { day: "2018-04-22 8:06:46.623" }, date: "2018-04-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325f6"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-05-22 12:06:46.623" }, start: { day: "2018-04-22 11:06:46.623" }, date: "2018-05-22 11:06:46.623"}
{_id: ObjectId("5adc864eaaf408a2b6e325c4"),employee: ObjectId("5adc864eaa3c92b3c4c252c1"), end: { day: "2018-05-22 10:06:46.623" }, start: { day: "2018-05-22 8:06:46.623" }, date: "2018-05-22 11:06:46.623"}

这代表每位员工白天的活动。

我需要计算每天的工作时间,以每个活动的开始日期“ start.day”和结束日期“ end.day”之间的小时数为基础,并汇总一个活动的所有活动天。

我尝试了一些聚合,例如编辑,求和,下层,但我不知道要实现这一点需要做些什么逻辑。

1 个答案:

答案 0 :(得分:1)

所以真正要覆盖的第一件事是您当前的“日期”全都是“字符串”,但这确实无济于事。最好将所有内容都转换为BSON日期,因为无论如何这基本上是聚合操作所必需的。

第二点是在一个间隔内获取“每一天”的总数并不容易。实际上,您确实需要在MongoDB上抛出一些表达式才能做到这一点:

db.collection.aggregate([
  { "$addFields": {
    "start": { "$toDate": "$start.day" },
    "end": { "$toDate": "$end.day" },
    "date": { "$toDate": "$date" },
    "dayworking": {
      "$map": {
        "input": {
          "$range": [
            0,
            { "$ceil": {
              "$divide": [
                { "$subtract": [
                  { "$toDate": "$end.day" },
                  { "$toDate": "$start.day" }
                ]},
                1000 * 60 * 60 * 24
              ]
            }}
          ]
        },
        "in": {
          "$toDate": {
            "$add": [
              { "$multiply": ["$$this", 1000 * 60 * 60 * 24 ] },
              { "$subtract": [
                { "$toLong": { "$toDate": "$start.day" } },
                { "$mod": [ { "$toLong": { "$toDate": "$start.day" } }, 1000 * 60 * 60 * 24 ] }
              ]}
            ]
          }
        }
      }
    }
  }},
  { "$unwind": "$dayworking" },
  { "$group": {
    "_id": {
      "employee": "$employee",
      "day": "$dayworking"
    },
    "hours": {
      "$sum": {
        "$floor": {
          "$divide": [
            { "$switch": {
              "branches": [
                { 
                  "case": {
                    "$and": [
                      { "$lt": [ "$dayworking", "$start" ] },
                      { "$gt": [
                        { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] },
                        "$end"
                      ]}
                    ]
                  },
                  "then": { "$subtract": [ "$end", "$start" ] }
                },
                {
                  "case": {
                    "$lt": [
                      "$end",
                      { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] }
                    ]
                  },
                  "then": {
                    "$subtract": [ "$end", "$dayworking" ]
                  }
                },
                {
                  "case": { "$lt": [ "$dayworking", "$start" ] },
                  "then": {
                    "$subtract": [
                      { "$add": [ "$dayworking", 1000 * 60 * 60 * 24 ] },
                      "$start"
                    ]
                  }
                }
              ],
              "default": 1000 * 60 * 60 * 24
            }},
            1000 * 60 * 60
          ]
        }
      }
    }
  }},
  { "$sort": { "_id": 1 } }
])

基本上每天在开始和结束时间间隔内返回(为简洁起见,缩短时间):

{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-04-22T00:00:00Z")
        },
        "hours" : 15
}
{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-04-23T00:00:00Z")
        },
        "hours" : 24
}

.... each day in between ...

{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-05-21T00:00:00Z")
        },
        "hours" : 24
}
{
        "_id" : {
                "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
                "day" : ISODate("2018-05-22T00:00:00Z")
        },
        "hours" : 14
}

在“全天”分配了24小时,而在其他时间分配了部分时间。根据您的示例,第一天的数据生成为:

{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f7"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-04-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-04-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 1
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325c8"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-04-22T10:06:46.623Z"),
        "start" : ISODate("2018-04-22T08:06:46.623Z"),
        "date" : ISODate("2018-04-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 2
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f6"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-04-22T00:00:00Z"),
        "hours" : 12
}

是两个唯一的条目,剩下的12小时组成了最后的15小时和最后一天:

{
        "_id" : ObjectId("5adc864eaaf408a2b6e325f6"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T12:06:46.623Z"),
        "start" : ISODate("2018-04-22T11:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-05-22T00:00:00Z"),
        "hours" : 12
}
{
        "_id" : ObjectId("5adc864eaaf408a2b6e325c4"),
        "employee" : ObjectId("5adc864eaa3c92b3c4c252c1"),
        "end" : ISODate("2018-05-22T10:06:46.623Z"),
        "start" : ISODate("2018-05-22T08:06:46.623Z"),
        "date" : ISODate("2018-05-22T11:06:46.623Z"),
        "dayending" : ISODate("2018-05-22T00:00:00Z"),
        "hours" : 2
}

入场2小时,其余12小时共14个小时。

说明

日期转换和数学

要说明的是,除了明显的“日期转换”之外,本质上需要完成两项主要工作。顺便说一下,可以使用MongoDB 4.0中的$toDate或通过$dateFromString(如果您有MongoDB 3.6)来完成。请注意,在后一种情况下,您还需要为“日期数学”

应用不同的方法

在使用Group result by 15 minutes time interval in MongoDb的较早版本并进行$dateFromString或直接转换的情况下,在$range的MongoDB早期版本中有处理“日期数学” 的详细示例。首先需要您的数据。

投影范围内的日期

完成此工作的下一个主要部分是,您基本上需要构造一个文档应用于源文档内部的日期的数组。这是$subtract表达式的工作方式,它采用一个开始值(在本例中为0和一个结束值),在这里我们将它们应用为“之间的天数” startend日期值。

该差异是从$divide以毫秒为单位返回的,因此在一天的恒定毫秒内使用$ceil来获得整数。在这里使用$mod进行四舍五入,但是$subtract$range一样容易,而该运算符在早期版本中不可用。

那时$map实际上刚刚产生了一个整数值数组,因此将$unwind应用于该数组,以便将它们转换为实际的BSON Date对象,该对象应表示“数据适用的日期”。再次,这只是一些“日期数学” 将数组索引值的加法(当然是+1)应用于原始的四舍五入开始日期。

计算小时数

现在有了来自较早阶段的一系列日期,以及将文档值重新格式化为可用的BSON日期的其他方式,您实际上需要将此“数组”内容与每个startend值进行比较,确定当天要应用多少小时。

第一个基本情况以及我们为此实际创建数组的原因是使用$group,它可以有效地复制每天发生的结果文档间隔。这是一个很小但很重要的步骤,必须$group进行,然后才能真正进行计算。底线是$switch实际上将使用这些值作为“主键” 的一部分进行输出,并与其他日期信息进行比较。

当然,这里的实际工作都是在$cond语句中完成的,同样可以将{em>“嵌套” 用作{ {3}}在早期版本中。在这里,您基本上想分析 三个 可能的情况,当然还要分析“整天”的默认备用。

这些情况基本上是:

  • 当前“分组日” 小于start,而“后续日” 大于{{1 }}日期,只需减去差值即可。

  • 如果不满足上述条件,则当end日期小于用于分组的“后续日” 时,请减去该“分组日” 从当前end日期开始,直到一天end为止的小时数。

  • 如果不满足上述条件,则当“分组日” 小于end(没有更早的其他start条件)时,则小时可以从“后续日期” 中减去endstart到当天结束之间的差额。

  • 如果不正确,则默认显示“全天”,在此示例中显示为24小时。

如果您要申请其他工作时间,则只需调整一下时间即可,即“上午8点”开始的“一天的开始时间” +8小时。相同的事情基本上适用于“一天结束”,在下午5点结束时添加+17即可。但是要实现的逻辑的基本原理仍然与上面显示的相同。

  

注意:这里的主要约束是$range,我认为它来自MongoDB 3.0或3.2。无论如何,您现在可能真的不应该在3.4之前运行任何版本的MongoDB。

     

如果您确实有较早的版本,那么Group and count over a start and end range上会有更多详细信息,我的另一个较早答案显示了使用多个查询甚至start相似过程除了非常相似的$range示例。