2016-03-02 59 views
1

我有以下的聚合查询给了我一个给定的日期范围期间的计数(countA)。在这种情况下,01/01/2016-03/31/2016。例如04/01/2016-07/31/2016是否可以添加第二个日期范围期限并将它们计为countB?在一个查询中计数多个日期范围

db.getCollection('customers').aggregate(
    {$match: {"status": "Closed"}}, 
    {$unwind: "$lines"}, 
    {$match: {"lines.status": "Closed"}}, 
    {$match: {"lines.deliveryMethod": "Tech Delivers"}}, 
    {$match: {"date": {$gte: new Date('01/01/2016'), $lte: new Date('03/31/2016')}}}, 
    {$group:{_id:"$lines.productLine",countA: {$sum: 1}}} 
) 

在此先感谢

+0

您是否认为可以发布一些示例文档以便我可以尝试? :3谢谢! – Tacocat

回答

3

当然,你也可以简化您的流水线级数相当多,主要是因为连续$match阶段是一个真正的单一阶段,你应该总是使用匹配任何聚合流水线的开始的标准。即使它实际上不“过滤”数组内容,它至少也只是选择包含实际匹配条目的文档。这极大地加快了速度,尤其是对于大型数据集。

对于两个日期范围,以及这只是一个$or查询参数。此外,它将在数组过滤完成之前“应用”,因为毕竟它是以文档级别匹配开始的。如此反复,在第一个管道$match

db.getCollection('customers').aggregate([ 
    // Filter all document conditions first. Reduces things to process. 
    { "$match": { 
     "status": "Closed", 
     "lines": { "$elemMatch": { 
      "status": "Closed", 
      "deliveryMethod": "Tech Delivers" 
     }}, 
     "$or": [ 
      { "date": { 
       "$gte": new Date("2016-01-01"), 
       "$lt": new Date("2016-04-01") 
      }}, 
      { "date": { 
       "$gte": new Date("2016-04-01"), 
       "$lt": new Date("2016-08-01") 
      }} 
     ] 
    }}, 
    // Unwind the array 
    { "$unwind": "$lines" }, 

    // Filter just the matching elements 
    // Successive $match is really just one pipeline stage 
    { "$match": { 
     "lines.status": "Closed", 
     "lines.deliveryMethod": "Tech Delivers" 
    }}, 

    // Then group on the productline values within the array 
    { "$group":{ 
     "_id": "$lines.productLine", 
     "countA": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date", new Date("2016-01-01") ] }, 
         { "$lt": [ "$date", new Date("2016-04-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     }, 
     "countB": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date", new Date("2016-04-01") ] }, 
         { "$lt": [ "$date", new Date("2016-08-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     } 
    }} 
]) 

$or基本上是“连接”两个结果集为它寻找“要么”范围内的标准申请。由于这是除了其他论点之外给出的,所以这个逻辑是一个“AND”条件,与其他条件中的其他条件一样,这个条件与$or论点相符。注意$gte$lt组合也是在同一个键上表达“AND”条件的另一种形式。

应用$elemMatch是因为数组元素需要“两个”条件。如果您直接将它们应用为“点符号”,那么真正要求的是“至少有一个数组元素”匹配每个条件,而不是匹配“两个”条件的数组元素。

$unwind之后的后面的过滤可以使用“点符号”,因为数组元素现在被“解除归一化”为单独的文档。因此,每个文档只有一个元素符合条件。

当您应用$group,而不是仅仅使用{ "$sum": 1 }的你,而“有条件地评估是否通过$cond计数与否。由于这两个日期范围的结果中,你只需要确定当前文档是”卷起来“属于一个日期范围或另一个。作为”三元“(如果/然后/其他)运算符,这是$cond提供。

它查看文档中的"date"中的值,如果它匹配条件设置(第一个参数 - 如果)然后它返回1(第二个参数 - 然后),否则它返回0,有效地不会增加到当前计数

由于这些是“逻辑的”条件,则“与”表示与逻辑$and运算符,它本身返回truefalse,要求既包含条件是true

还要注意对象构造函数中的更正,因为如果您没有用该表示中的字符串实例化,则生成的Date处于“本地时间”,而不是“MongoDB”存储日期的“UTC”格式。只有使用“本地”构造函数才是真正的意思,而且通常人们确实不这样做。

另一个说明是$lt日期更改,应该总是比您要查找的上一个日期大“一天”。请记住,这些是“日期开始”日期,因此您通常需要在日期内的所有可能时间,而不是刚开始。所以它是“不到第二天”的正确条件。


根据记录,从2.6版本的MongoDB,很可能不如“预过滤器”数组内容你$unwind“之前”。这消除了在发生“解规范化”时生成新文档的开销,这与您要应用于数组元素的条件不匹配。

MongoDB的3.2和更大,使用​​:

db.getCollection('customers').aggregate([ 
    // Filter all document conditions first. Reduces things to process. 
    { "$match": { 
     "status": "Closed", 
     "lines": { "$elemMatch": { 
      "status": "Closed", 
      "deliveryMethod": "Tech Delivers" 
     }}, 
     "$or": [ 
      { "date": { 
       "$gte": new Date("2016-01-01"), 
       "$lt": new Date("2016-04-01") 
      }}, 
      { "date": { 
       "$gte": new Date("2016-04-01"), 
       "$lt": new Date("2016-08-01") 
      }} 
     ] 
    }}, 

    // Pre-filter the array content to matching elements 
    { "$project": { 
     "lines": { 
      "$filter": { 
       "input": "$lines", 
       "as": "line", 
       "cond": { 
        "$and": [ 
         { "$eq": [ "$$line.status", "Closed" ] }, 
         { "$eq": [ "$$line.deliveryMethod", "Tech Delivers" ] } 
        ] 
       } 
      } 
     } 
    }}, 

    // Unwind the array 
    { "$unwind": "$lines" }, 

    // Then group on the productline values within the array 
    { "$group":{ 
     "_id": "$lines.productLine", 
     "countA": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date": new Date("2016-01-01") ] }, 
         { "$lt": [ "$date", new Date("2016-04-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     }, 
     "countB": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date", new Date("2016-04-01") ] }, 
         { "$lt": [ "$date", new Date("2016-08-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     } 
    }} 
]) 

或者至少MongoDB的2.6,然后应用$redact代替:

db.getCollection('customers').aggregate([ 
    // Filter all document conditions first. Reduces things to process. 
    { "$match": { 
     "status": "Closed", 
     "lines": { "$elemMatch": { 
      "status": "Closed", 
      "deliveryMethod": "Tech Delivers" 
     }}, 
     "$or": [ 
      { "date": { 
       "$gte": new Date("2016-01-01"), 
       "$lt": new Date("2016-04-01") 
      }}, 
      { "date": { 
       "$gte": new Date("2016-04-01"), 
       "$lt": new Date("2016-08-01") 
      }} 
     ] 
    }}, 

    // Pre-filter the array content to matching elements 
    { "$redact": { 
     "$cond": { 
      "if": { 
       "$and": [ 
        { "$eq": [ "$status", "Closed" ] }, 
        { "$eq": [ 
         { "$ifNull": ["$deliveryMethod", "Tech Delivers" ] }, 
         "Tech Delivers" 
        ] 

      }, 
      "then": "$$DESCEND", 
      "else": "$$PRUNE" 
     } 
    }}, 

    // Unwind the array 
    { "$unwind": "$lines" }, 

    // Then group on the productline values within the array 
    { "$group":{ 
     "_id": "$lines.productLine", 
     "countA": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date": new Date("2016-01-01") ] }, 
         { "$lt": [ "$date", new Date("2016-04-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     }, 
     "countB": { 
      "$sum": { 
       "$cond": [ 
        { "$and": [ 
         { "$gte": [ "$date", new Date("2016-04-01") ] }, 
         { "$lt": [ "$date", new Date("2016-08-01") ] } 
        ]}, 
        1, 
        0 
       ] 
      } 
     } 
    }} 
]) 

注意到有趣的小$ifNull在那里这是由于需要到$$DESCEND的递归性质,由于全部级别的文件被检查,包括“顶级”文件,然后“下降ing“到后续的数组和成员甚至嵌套的对象中。由于先前的顶级字段的查询选择标准,“状态”字段存在并且具有“关闭”的值,但是当然没有称为“deliveryMethod”的“顶级”元素,因为它仅在数组内元素。

,基本上是“照顾”,那么需要使用$redact这样的时候要取,如果结构如果文件不允许这样的条件,那么它是不是一个真正的选择,所以恢复处理$unwind然后$match代替。

但是在可能的情况下,使用这些方法优先于$unwind,然后使用$match处理,因为它可以节省大量时间,并通过使用更新的技术来使用更少的资源。

+0

感谢您的所有信息!不过,我期待得到这两组日期的数量。 CountA和CountB – fpena06

+0

@ fpena06这是使用'$ cond'的简单添加。我会添加更多的编辑来解释,但是每个列表现在已经被修改为在计数时“有条件地”查看这些日期范围。错过了你的问题中的细节。 –

+0

@ fpena06更正。如果我的语法正确,可能会有所帮助,而不会因“切入/过去”而过快。在'$ cond'中对'$ gte'和'$ lt'操作符的逻辑使用再次进行了修正。 –