gpt4 book ai didi

mongodb - MongoDb聚合-分成时间段

转载 作者:行者123 更新时间:2023-12-01 14:46:53 25 4
gpt4 key购买 nike

是否可以使用MongoDB聚合框架生成时间序列输出,将认为属于每个存储桶中的任何源文档添加到该存储桶中?

说我的收藏看起来像这样:

/*light_1 on from 10AM to 1PM*/
{
"_id" : "light_1",
"on" : ISODate("2015-01-01T10:00:00Z"),
"off" : ISODate("2015-01-01T13:00:00Z"),

},
/*light_2 on from 11AM to 7PM*/
{
"_id" : "light_2",
"on" : ISODate("2015-01-01T11:00:00Z"),
"off" : ISODate("2015-01-01T19:00:00Z")
}

..并且我使用6小时的时段间隔来生成2015年1月1日的报告。我希望我的结果看起来像这样:

    {
"start" : ISODate("2015-01-01T00:00:00Z"),
"end" : ISODate("2015-01-01T06:00:00Z"),
"lights" : []
},
{
"start" : ISODate("2015-01-01T06:00:00Z"),
"end" : ISODate("2015-01-01T12:00:00Z"),
"lights_on" : ["light_1", "light_2"]
},
{
"start" : ISODate("2015-01-01T12:00:00Z"),
"end" : ISODate("2015-01-01T18:00:00Z"),
"lights_on" : ["light_1", "light_2"]
},
{
"start" : ISODate("2015-01-01T18:00:00Z"),
"end" : ISODate("2015-01-02T00:00:00Z"),
"lights_on" : ["light_2"]
}

如果某盏灯的“开”值<铲斗“结束”且其“关”值> =铲斗“开始”,则认为该灯在某个范围内为“开”

我知道我可以使用$ group和聚合日期运算符按开始时间或结束时间进行分组,但是在那种情况下,它是一对一的映射。在这里,如果一个原始文档跨越多个存储桶,则可能会分成多个时间存储桶。

直到运行时才知道报告范围和间隔范围。

最佳答案

介绍

您的目标在这里需要考虑一下何时记录事件的注意事项,因为您已将事件结构化成给定的时间段汇总。显而易见的一点是,代表您的单个文档实际上可以代表最终汇总结果中“多个”时间段内要报告的事件。

事实证明,由于要查找的时间段,分析是aggregation framework范围之外的问题。某些事件需要在可以分组的内容之外“生成”,您应该可以看到。

为了执行此“生成”,您需要mapReduce。它通过JavaScript具有“流控制”功能,因为它是一种处理语言,能够从本质上确定打开/关闭之间的时间是否跨越一个以上的周期,并因此发出在多个周期中的一个以上发生的数据。

附带说明一下,“灯光”可能不是最适合_id的代码,因为它可能在给定的一天中多次打开/关闭。因此开/关的“实例”可能会更好。但是,我只是在这里按照您的示例进行操作,因此要转置该代码,只需将映射器代码中对_id的引用替换为表示光源标识符的任何实际字段。

但是到代码上:

// start date and next date for query ( should be external to main code )
var oneHour = ( 1000 * 60 * 60 ),
sixHours = ( oneHour * 6 ),
oneDay = ( oneHour * 24 ),
today = new Date("2015-01-01"), // your input
tomorrow = new Date( today.valueOf() + oneDay ),
yesterday = new Date( today.valueOf() - sixHours ),
nextday = new Date( tomorrow.valueOf() + sixHours);

// main logic
db.collection.mapReduce(
// mapper to emit data
function() {
// Constants and round date to hour
var oneHour = ( 1000 * 60 * 60 )
sixHours = ( oneHour * 6 )
startPeriod = new Date( this.on.valueOf()
- ( this.on.valueOf() % oneHour )),
endPeriod = new Date( this.off.valueOf()
- ( this.off.valueOf() % oneHour ));

// Hour to 6 hour period and convert to UTC timestamp
startPeriod = startPeriod.setUTCHours(
Math.floor( startPeriod.getUTCHours() / 6) * 6 );
endPeriod = endPeriod.setUTCHours(
Math.floor( endPeriod.getUTCHours() / 6) * 6 );

// Init empty reults for each period only on first document processed
if ( counter == 0 ) {
for ( var x = startDay.valueOf(); x < endDay.valueOf(); x+= sixHours ) {
emit(
{ start: new Date(x), end: new Date(x + sixHours) },
{ lights_on: [] }
);
}
}

// Emit for every period until turned off only within the day
for ( var x = startPeriod; x <= endPeriod; x+= sixHours ) {
if ( ( x >= startDay ) && ( x < endDay ) ) {
emit(
{ start: new Date(x), end: new Date(x + sixHours) },
{ lights_on: [this._id] }
);
}
}
counter++;
},

// reducer to keep all lights in one array per period
function(key,values) {
var result = { lights_on: [] };
values.forEach(function(value) {
value.lights_on.forEach(function(light){
if ( result.lights_on.indexOf(light) == -1 )
result.lights_on.push(light);
});
});
result.lights_on.sort();
return result;
},

// options and query
{
"out": { "inline": 1 },
"query": {
"on": { "$gte": yesterday, "$lt": tomorrow },
"$or": [
{ "off": { "$gte:" today, "$lt": nextday } },
{ "off": null },
{ "off": { "$exists": false } }
]
},
"scope": {
"startDay": today,
"endDay": tomorrow,
"counter": 0
}
}
)

映射并缩小

本质上,“映射器”功能查看当前记录,将每个打开/关闭时间四舍五入为小时,然后计算出事件发生的六个小时内的开始时间。

使用这些新的日期值,将启动一个循环以获取开始的“开启”时间,并在该时间段内在单个元素数组内发出一个事件,指示当前“灯”被打开,如后面所述。每个循环将开始时间增加六个小时,直到达到“熄灭”时间为止。

这些出现在reducer函数中,该函数需要与返回相同的预期输入,因此在value对象内的时间段内打开了灯光阵列。它在与这些值对象的列表相同的键下处理发出的数据。

首先迭代要减少的值列表,然后查看内部的灯光数组(可能来自先前的减少通过),并将每个灯光处理成唯一的唯一灯光结果数组。只需在结果数组中查找当前的光照值并将其压入不存在的那个数组即可。

注意“上一遍”,就好像您不熟悉mapReduce的工作原理一样,那么您应该了解reducer函数本身会发出可能无法通过处理“键”的所有“可能”值获得的结果。单次通过。它只能并且通常仅处理密钥的已发射数据的“子集”,因此将以与从映射器发射数据相同的方式获得“精简”结果作为输入。

设计的原因是为什么映射器和化简器都需要以相同的结构输出数据,因为化简器本身也可以从以前还原的数据中获取输入。这就是mapReduce处理大型数据集的方式,该数据集发出大量相同的键值。它通常以“块”方式处理,而不是一次全部处理。

结束减少归结为该期间打开的灯光列表,每个期间的开始和结束均作为发出的键。像这样:

    {
"_id": {
"start": ISODate("2015-01-01T06:00:00Z"),
"end": ISODate("2015-01-01T12:00:00Z")
},
{
"result": {
"lights_on": [ "light_1", "light_2" ]
}
}
},

该“_id”,“结果”结构只是所有mapReduce输出如何输出的一个属性,但是所需的值都在那里。

询问

现在,这里还有一个关于查询选择的注释,该注释需要考虑到,在当日开始之前的某个日期,通过其收集条目可能已经“打开”了灯。同样,可以在报告当前日期后将其“关闭”,这实际上也可以是 null值,也可以在文档中没有“off”键,这取决于存储数据的方式和内容。实际正在观察的一天。

该逻辑从要报告的第一天开始就创建了一些必需的计算,并考虑了该日期前后的六个小时的时间段,其中列出了查询条件:

        {
"on": { "$gte": yesterday, "$lt": tomorrow },
"$or": [
{ "off": { "$gte:" today, "$lt": nextday } },
{ "off": null },
{ "off": { "$exists": false } }
]
}

那里的基本选择器使用 $gte $lt 的范围运算符在要测试其值的字段上分别查找大于或等于和小于的值,以便在合适的范围内查找数据。

$or 条件内,考虑了“off”值的各种可能性。要么是它在范围标准之内,要么是 null值,或者可能是通过 $exists 运算符在文档中根本不存在任何键。这取决于在 $or内尚未满足那些条件的灯光的情况下,如何实际表示“关闭”,但这是合理的假设。

像所有MongoDB查询一样,除非另有说明,否则所有条件都是“AND”条件的隐式条件。

这仍然有些瑕疵,这取决于预期要打开灯多长时间。但是,出于对您的需求进行调整的目的,所有变量都特意在外部列出,并考虑了在报告日期之前或之后的预期获取时间。

创建空时间序列

这里的另一个注意事项是,数据本身很可能没有任何事件显示给定时间段内灯亮。因此,映射器函数中嵌入了一个简单的方法,用于查看我们是否在结果的第一次迭代中。

仅在第一次时,会发出一组可能的周期密钥,其中包括一个空数组,用于在每个周期中打开的灯。这样一来,报告就可以显示出在将其插入发送到减速器并输出的数据中根本不亮的那些时段。

您可能会对此方法有所不同,因为它仍然取决于是否有一些满足查询条件的数据才能输出任何内容。因此,为了迎合一个真正的“空白日”,即没有数据记录或不符合标准,那么最好创建一个外部散列表,将所有的键显示为空,以显示所有结果。然后将mapReduce操作的结果“合并”到这些预先存在的键中以生成报告。

摘要

关于日期,这里有许多计算,并且不知道实际的最终语言实现,我只是在声明独立于实际mapReduce操作外部的任何内容。因此,在此看起来像重复的所有操作都是针对此目的而完成的,从而使逻辑语言的该部分独立。大多数编程语言都支持根据所用方法操纵日期的功能。

然后,所有特定于语言的输入将作为选项块传入,显示为mapReduce方法的最后一个参数。值得注意的是,存在带有参数值的查询,这些值都是从要报告的日期算起的。然后是“作用域”,这是一种传递可以由mapReduce操作中的函数使用的值的方法。

考虑到这些因素,映射器和化简器的JavaScript代码保持不变,因为这是该方法作为输入所期望的。作用域的任何变量都由作用域和查询结果提供,以便在不更改该代码的情况下获得结果。

因此,主要是因为“亮灯”的持续时间可以跨越要报告的不同时段,所以这成为聚合框架不打算执行的操作。它无法执行获得结果所需的“循环”和“数据发射”,因此为什么我们将mapReduce用于此任务。

也就是说,很好的问题。我不知道您是否已经在此处考虑过如何实现结果的概念,但是至少现在有了针对类似问题的人的指南。

关于mongodb - MongoDb聚合-分成时间段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31696523/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com