计算查询中的多个日期范围

时间:2016-03-02 22:52:47

标签: mongodb mongodb-query aggregation-framework

我有以下聚合查询,它给出了给定日期范围期间的计数(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}}}
)

提前致谢

1 个答案:

答案 0 :(得分:3)

当然,您也可以相当多地简化您的管道阶段,主要是因为连续的$match阶段实际上是一个阶段,并且您应该始终使用匹配标准<任何聚合管道的强>开始。即使它实际上没有&#34;过滤&#34;在数组内容中,它至少只选择包含实际匹配的条目的文档。这极大地加快了速度,特别是对于大型数据集。

对于两个日期范围,这只是一个$or查询参数。它也适用于&#34;之前&#34;数组过滤完成后,因为毕竟它是一个文档级匹配开始。再次,在第一个管道$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基本上&#34;加入&#34;它寻找的两个结果集&#34;要么&#34;范围标准适用。除了其他参数之外,还给出了这个逻辑,它是一个&#34; AND&#34;条件与其他标准符合$or参数。请注意,$gte$lt组合也是表达&#34; AND&#34;的另一种形式。同一把钥匙的条件。

自{&#34}以来都应用了$elemMatch&#34;数组元素需要条件。如果你只是用&#34;点符号&#34;直接应用它们,那么所有真正要求的是&#34;至少一个数组元素&#34;匹配每个条件,而不是匹配&#34;两者的数组元素&#34;条件。

$unwind之后的后续过滤可以使用&#34;点符号&#34;因为数组元素现在已经去标准化了#34;分成单独的文件。因此,现在每个文档只有一个元素符合条件。

当您应用$group时,而不是仅仅使用{ "$sum": 1 }而不是&#34;使用$cond有条件地评估是否对其进行计数。由于两个日期范围都在结果范围内,因此您只需要确定当前文档是否已卷起&#34;属于一个日期范围或另一个。作为一个&#34;三元&#34; (if / then / else)运算符,这是$cond提供的。

它查看文档中"date"内的值,如果它与条件集匹配(第一个参数--if),则返回1(第二个参数 - 然后),否则返回{{ 1}},实际上没有添加到当前计数。

因为这些是&#34;逻辑&#34;条件然后&#34; AND&#34;用逻辑$and运算符表示,该运算符本身返回0true,要求两个包含的条件都为false

还要注意true对象构造函数中的更正,因为如果不使用该表示形式的字符串进行实例化,则生成的Date位于&#34; localtime&#34;而不是&#34; UTC&#34; MongoDB存储日期的格式。只使用&#34; local&#34;构造函数,如果你真的是这个意思,通常人们真的不喜欢。

另一个注释是Date日期更改,应该始终为&#34;有一天&#34;大于您要查找的最后日期。记住这些是&#34;一天的开始&#34;日期,因此您通常希望在日期内的所有可能时间,而不仅仅是开始。所以它不到第二天就会#34;作为正确的条件。

对于记录,使用2.6的MongoDB版本,它可能更好地预先过滤&#34;数组内容&#34;之前&#34;你$lt。这消除了在&#34;去标准化&#34;中生成新文档的开销。发生的情况与您要应用于数组元素的条件不匹配。

对于MongoDB 3.2及更高版本,请使用$filter

$unwind

至少对于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
    { "$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
                ]
            }
        }
    }}
])

由于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,因为所有级别的文档都会被检查,包括&#34 ;顶级&#34;文件,然后&#34;降序&#34;进入后续数组和成员甚至嵌套对象。 &#34;状态&#34;字段存在且值为&#34;已关闭&#34;由于顶级字段的早期查询选择标准,但当然没有&#34;顶级&#34;元素称为&#34; deliveryMethod&#34;,因为它只在数组元素中。

那基本上是&#34;关心&#34;然后需要在使用这样的$$DESCEND时采取,如果文档中的结构不允许这样的条件,那么它实际上不是一个选项,所以恢复为处理$redact然后{ {1}}而不是。

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