您当前的架构在这里没有帮助您,因为“每日”字段(我们假设是您的每种类型的点击次数或类似的东西)表示为子文档,这意味着您需要明确命名路径每个字段,以便对其进行处理。
更好的方法是将这些信息放在一个数组中:
{
"name" : "devid",
"date" : ISODate("2014-02-21T11:32:42.392Z"),
"daily": [
{ "type": "3", "clicks": 12 },
{ "type": "12", "clicks": 13 },
{ "type": "25", "clicks": 13 }
]
}
然后你有一个聚合语句是这样的:
db.collection.aggregate([
// Just match the dates in January and February
{ "$match": {
"date": {
"$gte": new Date("2014-01-01"), "$lt": new Date("2014-03-01")
}
}},
// Unwind the "daily" array
{ "$unwind": "$daily" },
// Group the values together by "type" on "January" and "February"
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"type": "$daily.type"
},
"clicks": { "$sum": "$daily.clicks" }
}},
// Sort the result nicely
{ "$sort": {
"_id.year": 1,
"_id.month": 1,
"_id.type": 1
}}
])
这种形式很简单。或者即使您不关心类型作为分组而只想要月份总计:
db.collection.aggregate([
{ "$match": {
"date": {
"$gte": new Date("2014-01-01"), "$lt": new Date("2014-03-01")
}
}},
{ "$unwind": "$daily" },
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
},
"clicks": { "$sum": "$daily.clicks" }
}},
{ "$sort": { "_id.year": 1, "_id.month": 1 }}
])
但是对于当前的子文档表单,您目前拥有的这变得很难看:
db.collection.aggregate([
{ "$match": {
"date": {
"$gte": new Date("2014-01-01"), "$lt": new Date("2014-03-01")
}
}},
{ "$group": {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
},
"clicks": {
"$sum": {
"$add": [
{ "$ifNull": ["$daily.1", 0] },
{ "$ifNull": ["$daily.3", 0] },
{ "$ifNull": ["$daily.9", 0] },
{ "$ifNull": ["$daily.12", 0] },
{ "$ifNull": ["$daily.25", 0] },
{ "$ifNull": ["$daily.30", 0] },
]
}
}
}}
])
这表明除了指定每天下基本上每个可能的字段(因此可能更大)之外,您别无选择。然后我们必须评估,因为给定文档可能不存在该键以返回默认值。
例如,您的第一个文档没有键“daily.3”,因此如果没有 $ifNull 检查,则返回值将是 null 并使整个 $sum 过程无效,因此总数将为“0”。
在第一个聚合示例中对这些键进行分组变得更糟:
db.collection.aggregate([
// Just match the dates in January and February
{ "$match": {
"date": {
"$gte": new Date("2014-01-01"), "$lt": new Date("2014-03-01")
}
}},
// Project with an array to match all possible values
{ "$project": {
"date": 1,
"daily": 1,
"type": { "$literal": ["1", "3", "9", "12", "25", "30" ] }
}},
// Unwind the "type" array
{ "$unwind": "$type" },
// Project values onto the "type" while grouping
{ "$group" : {
"_id": {
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"type": "$type"
},
"clicks": { "$sum": { "$cond": [
{ "$eq": [ "$type", "1" ] },
"$daily.1",
{ "$cond": [
{ "$eq": [ "$type", "3" ] },
"$daily.3",
{ "$cond": [
{ "$eq": [ "$type", "9" ] },
"$daily.9",
{ "$cond": [
{ "$eq": [ "$type", "12" ] },
"$daily.12",
{ "$cond": [
{ "$eq": [ "$type", "25" ] },
"$daily.25",
"$daily.30"
]}
]}
]}
]}
]}}
}},
{ "$sort": {
"_id.year": 1,
"_id.month": 1,
"_id.type": 1
}}
])
使用$cond 创建一个大型条件评估,以将值与我们使用$literal 运算符将所有可能值投影到数组中的“类型”相匹配。
如果您没有 MongoDB 2.6 或更高版本,您始终可以这样做来代替 $literal 运算符语句:
"type": { "$cond": [1, ["1", "3", "9", "12", "25", "30" ], 0] }
基本上来自$cond 的true 评估返回一个“文字”声明值,这是您指定数组的方式。还有一个隐藏的 $const 运算符没有记录,但现在公开为$literal。
正如您所见,这里的结构对您没有任何好处,所以最好的选择是改变它。但是如果你不能或者发现这个聚合概念太难处理,那么mapReduce 提供了一种方法,但是处理会慢得多:
db.collection.mapReduce(
function () {
for ( var k in this.daily ) {
emit(
{
year: this.date.getFullYear(),
month: this.date.getMonth() + 1,
type: k
},
this.daily[k]
);
}
},
function(key,values) {
return Array.sum( values );
},
{
"query": {
"date": {
"$gte": new Date("2014-01-01"), "$lt": new Date("2014-03-01")
}
},
"out": { "inline": 1 }
}
)
这里的一般教训是,通过更改文档格式和使用聚合框架,您将获得最清晰和最快的结果。但是这里列出了所有的方法。