如何在mongoDB中聚合

时间:2014-05-29 13:00:09

标签: mongodb mapreduce aggregation-framework

我有一个名为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月的总点击次数? 请帮我解决我的问题。

1 个答案:

答案 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] }

基本上来自$condtrue评估返回“文字”声明值,这是指定数组的方式。还有隐藏的 $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 } 
    }
)

这里的一般教训是,通过更改文档格式和使用聚合框架,您将获得最清晰,最快速的结果。但是这里列出了所有这些方法。