我有很多这样的文件:
{_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”之间的小时数为基础,并汇总一个活动的所有活动天。
我尝试了一些聚合,例如编辑,求和,下层,但我不知道要实现这一点需要做些什么逻辑。
答案 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
和一个结束值),在这里我们将它们应用为“之间的天数” start
和end
日期值。
该差异是从$divide
以毫秒为单位返回的,因此在一天的恒定毫秒内使用$ceil
来获得整数。在这里使用$mod
进行四舍五入,但是$subtract
与$range
一样容易,而该运算符在早期版本中不可用。
那时$map
实际上刚刚产生了一个整数值数组,因此将$unwind
应用于该数组,以便将它们转换为实际的BSON Date对象,该对象应表示“数据适用的日期”。再次,这只是一些“日期数学” 将数组索引值的加法(当然是+1)应用于原始的四舍五入开始日期。
计算小时数
现在有了来自较早阶段的一系列日期,以及将文档值重新格式化为可用的BSON日期的其他方式,您实际上需要将此“数组”内容与每个start
和end
值进行比较,确定当天要应用多少小时。
第一个基本情况以及我们为此实际创建数组的原因是使用$group
,它可以有效地复制每天发生的结果文档间隔。这是一个很小但很重要的步骤,必须$group
进行,然后才能真正进行计算。底线是$switch
实际上将使用这些值作为“主键” 的一部分进行输出,并与其他日期信息进行比较。
当然,这里的实际工作都是在$cond
语句中完成的,同样可以将{em>“嵌套” 用作{ {3}}在早期版本中。在这里,您基本上想分析 三个 可能的情况,当然还要分析“整天”的默认备用。
这些情况基本上是:
当前“分组日” 小于start
,而“后续日” 大于{{1 }}日期,只需减去差值即可。
如果不满足上述条件,则当end
日期小于用于分组的“后续日” 时,请减去该“分组日” 从当前end
日期开始,直到一天end
为止的小时数。
如果不满足上述条件,则当“分组日” 小于end
(没有更早的其他start
条件)时,则小时可以从“后续日期” 中减去end
与start
到当天结束之间的差额。
如果不正确,则默认显示“全天”,在此示例中显示为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
示例。