我有一个名为user.monthly的文档,因为我使用了商店' day' :不。点击次数 在这里,我给出了不同日期的2个样本
1月份
{
name : "devid",
date : ISODate("2014-01-21T11:32:42.392Z"),
daily: {'1':12,'9':13,'30':13}
}
2月份
{
name : "devid",
date : ISODate("2014-02-21T11:32:42.392Z"),
daily: {'3':12,'12':13,'25':13}
}
如何汇总这一点并获得1月和2月的总点击次数? 请帮我解决我的问题。
答案 0 :(得分:1)
您当前的架构在这里没有帮助,因为“每日”字段(我们假设您的每种类型的点击次数或类似内容)被表示为子文档,这意味着您需要明确地将路径命名为每个领域都是为了做点什么。
更好的方法是将此信息放在数组中:
{
"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 }
}
)
这里的一般教训是,通过更改文档格式和使用聚合框架,您将获得最清晰,最快速的结果。但是这里列出了所有这些方法。