fpe*_*a06 3 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}}}
)
Run Code Online (Sandbox Code Playgroud)
提前致谢
当然,您还可以大大简化管道阶段,主要是因为连续$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
]
}
}
}}
])
Run Code Online (Sandbox Code Playgroud)
基本上$or“连接”两个结果集,因为它寻找要应用的“任一”范围标准。由于这是除了其他参数之外给出的,因此逻辑是“AND”条件,就像其他参数满足任一$or参数的标准一样。请注意,$gteand$lt组合也是在同一键上表达“AND”条件的另一种形式。
$elemMatch由于数组元素需要“两个”条件,因此应用了 。如果您只是直接使用“点符号”应用它们,那么真正要求的是“至少一个数组元素”匹配每个条件,而不是匹配“两个”条件的数组元素。
稍后的过滤$unwind可以使用“点表示法”,因为数组元素现在已“非规范化”到单独的文档中。因此,现在每个文档只有一个元素可以匹配条件。
当您应用 时$group,而不是仅仅使用{ "$sum": 1 }您,而是通过使用“有条件地评估是否计算它” $cond。由于两个日期范围都在结果内,因此您只需要确定当前“汇总”的文档是否属于一个日期作为“三元”(if/then/else)运算符,这就是所$cond提供的。
它查看"date"文档中的值,如果它与条件集(第一个参数 - if )匹配,则返回1(第二个参数 - then ),否则返回0,实际上不添加到当前计数。
由于这些是“逻辑”条件,因此“AND”用逻辑运算符表示$and,该运算符本身返回trueor false,要求两个包含的条件均为true。
另请注意对象构造函数中的更正Date,因为如果您不使用该表示形式的字符串进行实例化,则结果Date将采用“本地时间”格式,而不是 MongoDB 存储日期的“UTC”格式。仅当您确实这么想时才使用“本地”构造函数,但人们通常并不这么认为。
另一个注意事项是$lt日期更改,该日期应始终比您要查找的最后日期大“一天”。请记住,这些是“一天的开始”日期,因此您通常需要该日期内的所有可能时间,而不仅仅是开始时间。所以正确的条件是“小于第二天”。
根据记录,对于 MongoDB 2.6 版本,最好在“之前”“预过滤”数组内容$unwind。这消除了在“反规范化”过程中生成新文档的开销,该过程与您想要应用于数组元素的条件不匹配。
对于 MongoDB 3.2 及更高版本,请使用$filter:
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
]
}
}
}}
])
Run Code Online (Sandbox Code Playgroud)
或者至少对于 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
]
}
}
}}
])
Run Code Online (Sandbox Code Playgroud)
注意到$ifNull由于 的递归性质,其中有一些有趣的东西是必要的$$DESCEND,因为文档的所有级别都被检查,包括“顶级”文档,然后“下降”到后续数组和成员甚至嵌套对象。由于顶级字段的早期查询选择标准,“status”字段存在并且具有“Closed”值,但当然不存在名为“deliveryMethod”的“顶级”元素,因为它仅位于数组内元素。
这基本上就是像这样使用时需要采取的“小心” $redact,如果文档的结构不允许这样的条件,那么它实际上不是一个选项,所以改为恢复$unwind处理$match。
但如果可能,请优先使用这些方法而不是 then$unwind处理$match,因为通过使用更新的技术将节省大量时间并使用更少的资源。
| 归档时间: |
|
| 查看次数: |
1826 次 |
| 最近记录: |