【问题标题】:Counting number of records that where date is in date range?计算日期在日期范围内的记录数?
【发布时间】:2016-05-18 12:00:49
【问题描述】:

我有一个包含如下文档的集合:

{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 之间每个日期的记录。除了这些记录中的每一个,我还想返回 startDateendDate 包含此日期的记录数。

所以对于我上面的例子,最小startDate1/2/2016,最大endDate1/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?我应该担心在这里使用循环并尝试使用聚合框架还是这是一个有效的解决方案?

【问题讨论】:

    标签: mongodb mapreduce mongodb-query aggregation-framework


    【解决方案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"
    

    希望对你有帮助

    【讨论】:

    • 这是相关的,但不能完全返回我需要的数据。基本上它返回一个不同的开始/结束对以及有多少其他记录具有该开始/结束对。再次 - 类似于我需要的,但不完全相同。
    【解决方案2】:

    您最好的选择是 mapReduce。这是因为您可以在每个文档内的“startDate”和“endDate”之间循环值,并在这些值之间的每一天(或其他所需的时间间隔)发出。那么这只是从所有数据中累积每个发出的日期键的问题:

    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
    }
    

    您的日期在示例中被四舍五入为一天,但如果它们不是真实数据,那么它只是一个简单的日期数学问题,用于按时间间隔四舍五入。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多