使用.aggregate()
无法获得您想要的结果。您“可以”更改结构以使用数组而不是命名键,操作实际上非常简单。
因此,对于像这样的文档:
{
"_id" : "9aa072e4-b706-47e6-9607-1a39e904a05a",
"customerId" : "2164289-4",
"channelStatuses" : [
{
"channel": "FOO",
"status" : "done"
},
{
"channel": "BAR",
"status" : "error"
}
],
"channel" : "BAR",
}
在现代版本中,您可以使用$filter
、$map
和$arrayElemAt
来执行以下操作:
{ "$group": {
"_id": {
"customerId" : "$customerId",
"channel" : "$channel",
"status": {
"$arrayElemAt": [
{ "$map": {
"input": { "$filter": {
"input": "$chanelStatuses",
"as": "el",
"cond": { "$eq": [ "$$el.channel", "$channel" ] }
}},
"as": "el",
"in": "$$el.status"
}},
0
]
}
},
"count": { "$sum": 1 }
}}
旧版本的MongoDB需要使用$unwind
才能访问匹配的数组元素。
在MongoDB 2.6中,您仍然可以在展开之前“预过滤”数组:
[
{ "$project": {
"customerId": 1,
"channel": 1,
"status": {
"$setDifference": [
{ "$map": {
"input": "$channelStatuses",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.channel", "$channel" ] },
"$$el.status",
false
]
}
}},
[false]
]
}
}},
{ "$unwind": "$status" },
{ "$group": {
"_id": {
"customerId": "$customerId",
"channel": "$channel",
"status": "$status"
},
"count": { "$sum": 1 }
}}
]
而在此之前,您可以在$unwind
之后进行“过滤”:
[
{ "$unwind": "$channelStatuses" },
{ "$project": {
"customerId": 1,
"channel": 1,
"status": "$channelStatuses.status",
"same": { "$eq": [ "$channelStatuses.status", "$channel" ] }
}},
{ "$match": { "same": true } },
{ "$group": {
"_id": "$_id",
"customerId": { "$first": "$customerId" },
"channel": { "$first": "$channel" },
"status": { "$first": "$status" }
}},
{ "$group": {
"_id": {
"customerId": "$customerId",
"channel": "$channel",
"status": "$status"
},
"count": { "$sum": 1 }
}}
]
在比MongoDB 2.6版本低的版本中,您还需要
$project
两个字段之间相等测试的结果,然后在单独的阶段中对结果进行
$match
。您可能还注意到了“两个”
$group
阶段,因为第一个阶段通过
$first
累加器删除了任何可能的
"channel"
值重复项。以下
$group
与前面的列表完全相同。
但是,如果您无法更改结构并且需要在无法提供每个名称的情况下进行“灵活”的键匹配,则必须使用mapReduce:
db.collection.mapReduce(
function() {
emit({
"customerId": this.customerId,
"channel": this.channel,
"status": this.channelStatuses[this.channel].status
},1);
},
function(key,values) {
return Array.sum(values);
},
{ "out": { "inline": 1 } }
)
当然,你可以使用这种符号表示法
$filter
和$map
这样的操作的整个目的就是“避免”使用$unwind
。由于$unwind
所做的事情的本质,它是一个“巨大”的性能瓶颈。除非您打算从数组中包含的值跨文档进行聚合,否则应避免使用$unwind
。您可以做同样的事情,但成本要高得多。最好的方法是所示的方法。 - Blakes Seven