我有一个集合,其中包含如下文档:
{startDate: ISODate("2016-01-02T00:00:00Z"), endDate: ISODate("2016-01-05T00:00:00Z")},
{startDate: ISODate("2016-01-02T00:00:00Z"), endDate: ISODate("2016-01-08T00:00:00Z")},
{startDate: ISODate("2016-01-05T00:00:00Z"), endDate: ISODate("2016-01-08T00:00:00Z")},
{startDate: ISODate("2016-01-05T00:00:00Z"), endDate: ISODate("2016-01-10T00:00:00Z")},
{startDate: ISODate("2016-01-07T00:00:00Z"), endDate: ISODate("2016-01-10T00:00:00Z")}
我想为最小startDate
和最大endDate
之间的每个日期返回一条记录。 除了这些记录中的每一个,我还想返回startDate
和endDate
包含此日期的记录数。
因此,对于我上面的例子,最小startDate
是1/2/2016
,最大endDate
是1/10/2016
,所以我想返回这两个之间的所有日期以及计数。 请参阅下面的所需输出:
{date: ISODate("2016-01-02T00:00:00Z"), count: 2}
{date: ISODate("2016-01-03T00:00:00Z"), count: 2}
{date: ISODate("2016-01-04T00:00:00Z"), count: 2}
{date: ISODate("2016-01-05T00:00:00Z"), count: 4}
{date: ISODate("2016-01-06T00:00:00Z"), count: 3}
{date: ISODate("2016-01-07T00:00:00Z"), count: 4}
{date: ISODate("2016-01-08T00:00:00Z"), count: 4}
{date: ISODate("2016-01-09T00:00:00Z"), count: 2}
{date: ISODate("2016-01-010T00:00:00Z"), count: 2}
如果这没有意义,请告诉我,我可以尝试更详细地解释。
我能够使用如下所示的循环来做到这一点:
var startDate = ISODate("2016-01-02T00:00:00Z")
var endDate = ISODate("2016-02-10T00:00:00Z")
while(startDate < endDate){
var counts = db.data.find(
{
startDate: {$lte: startDate},
endDate: {$gte: startDate}
}
).count()
print(startDate, counts)
startDate.setDate(startDate.getDate() + 1)
}
但我想知道是否有办法使用聚合框架来做到这一点? 我来自一个主要是SQL背景,循环获取数据通常是一个坏主意。 同样的规则适用于MongoDB吗? 我应该担心在这里使用循环并尝试使用聚合框架还是这是一个有效的解决方案?
你最好的选择是mapReduce。这是因为您可以在每个文档中的"开始日期"和"结束日期"之间循环值,并在这些值之间每天(或其他所需的间隔(发出值。那么只需从所有数据中累积每个发出的日期键即可:
db.collection.mapReduce(
function() {
for( var d = this.startDate.valueOf(); d <= this.endDate.valueOf(); d += 1000 * 60 * 60 * 24 ) {
emit(new Date(d), 1)
}
},
function(key,values) {
return Array.sum(values);
},
{ "out": { "inline": 1 } }
)
这将产生如下结果:
{
"results" : [
{
"_id" : ISODate("2016-01-02T00:00:00Z"),
"value" : 2
},
{
"_id" : ISODate("2016-01-03T00:00:00Z"),
"value" : 2
},
{
"_id" : ISODate("2016-01-04T00:00:00Z"),
"value" : 2
},
{
"_id" : ISODate("2016-01-05T00:00:00Z"),
"value" : 4
},
{
"_id" : ISODate("2016-01-06T00:00:00Z"),
"value" : 3
},
{
"_id" : ISODate("2016-01-07T00:00:00Z"),
"value" : 4
},
{
"_id" : ISODate("2016-01-08T00:00:00Z"),
"value" : 4
},
{
"_id" : ISODate("2016-01-09T00:00:00Z"),
"value" : 2
},
{
"_id" : ISODate("2016-01-10T00:00:00Z"),
"value" : 2
}
],
"timeMillis" : 35,
"counts" : {
"input" : 5,
"emit" : 25,
"reduce" : 9,
"output" : 9
},
"ok" : 1
}
您的日期在样本中四舍五入为一天,但如果它们不在实际数据中,那么只需应用日期数学即可按间隔舍入。
在mongodb聚合框架中,有阶段而不是循环。它是一个管道,它经过每个阶段,直到到达指定的最后一个阶段。这就是为什么在使用聚合框架时会看到 [] 的原因。有几个阶段,仅举几例(比赛、小组和项目(。看看他们的文档非常简单。无论如何,这非常简短。至于你的问题,这是我的主张:
我没有尝试过这个。如果您可以尝试此操作并让我知道它是否有效:
首先,您只使用$match保留日期在所需范围内的日期。然后进入$group阶段。例:
db.collection.aggregate{
[
{$match: {
$and : [
{startDate: {$gte:ISODate("2016-01-02T00:00:00Z")},
{endDate: {$lte:ISODate("2016-02-10T00:00:00Z")}
]
},
{$group:
{_id: {startDate:"$startDate",endDate:"$endDate"},
count:{$sum:1}
}
}
]
}
如果您只想使用 startDate 进行分组,如示例中所示,请替换
_id: {startDate:"$startDate",endDate:"$endDate"
有了这个:
_id: "$startDate"
我希望这有所帮助