MongoDB聚合框架 - 如何执行多个$ group查询

时间:2014-03-31 23:54:42

标签: javascript node.js mongodb mongoose aggregation-framework

我有以下MongoDB聚合查询,它查找指定月份内的所有记录,$按天分组记录,然后返回每天的平均价格。我还希望返回整个月的平均价格。我可以通过使用多个$组来实现这一点,如果是这样,怎么做?

        PriceHourly.aggregate([
                { $match: { date: { $gt: start, $lt: end } } },
                { $group: { 
                    _id: "$day", 
                    price: { $avg: '$price' },
                    system_demand: { $avg: '$system_demand'}
                }}
        ], function(err, results){

                results.forEach(function(r) {
                    r.price          = Helpers.round_price(r.price);
                    r.system_demand  = Helpers.round_price(r.system_demand);
                });
                console.log("Results Length: "+results.length, results);
                res.jsonp(results);
        }); // PriceHourly();  

这是我的模特:

// Model
var PriceHourlySchema = new Schema({
    created: {
        type: Date,
        default: Date.now
    },
    day: {
        type: String,
        required: true,
        trim: true
    },
    hour: {
        type: String,
        required: true,
        trim: true
    },
    price: {
        type: Number,
        required: true
    },
    date: {
        type: Date,
        required: true
    }
}, 
{ 
    autoIndex: true 
});

1 个答案:

答案 0 :(得分:1)

简短的回答是“只是将您的日期范围扩展到包含一个月内的所有日期有什么问题?”,因此您只需更改即可获得结果。

你可以“嵌套”分组阶段吗?是的,您可以向管道添加其他阶段,这就是管道的用途。因此,如果您首先想要每天“平均”然后在一个月的所有日子里取平均值,那么您可以这样形成:

PriceHourly.aggregate([
   { "$match": { 
       "date": { 
           "$gte": new Date("2014-03-01"), "$lt": new Date("2014-04-01")
       }  
   }},
   { "$group": { 
       "_id": "$day", 
       "price": { "$avg": "$price" },
       "system_demand": { "$avg": "$system_demand" }
   }},
   { "$group": { 
       "_id": null, 
       "price": { "$avg": "$price" },
       "system_demand": { "$avg": "$system_demand" }
   }}
])

即使这可能是相当多余的,因为可以说这可以通过一个单一的群体陈述来完成。

但是这个架构有更长的评论。除了获得平均值或模式意图包含的内容之外,您实际上并没有说明您正在做的事情的大部分目的。所以我想描述一些可能有点不同的东西。

假设您有一个包含“产品”的集合,“键入”“当前价格”和“时间戳”作为“价格”“更改”的日期。我们称之为“PriceChange”。因此,每次发生此事件时,都会创建一个新文档。

{
    "product": "ABC",
    "type": 2,
    "price": 110,
    "timestamp": ISODate("2014-04-01T00:08:38.360Z")
}

这可能会在一小时,一天或任何情况下多次改变。

因此,如果您对本月每件产品的“平均”价格感兴趣,可以这样做:

PriceChange.aggregate([
    { "$match": {
        "timestamp": { 
            "$gte": new Date("2014-03-01"), "$lt": new Date("2014-04-01")
        }
    }},
    { "$group": {
        "_id": "$product",
        "price_avg": { "$avg": "$price" }
    }}
])

此外,如果没有任何其他字段,您可以获得每月每个产品的平均价格:

PriceChange.aggregate([
    { "$match": {
        "timestamp": { 
            "$gte": new Date("2014-03-01"), "$lt": new Date("2014-04-01")
        }
    }},
    { "$group": {
        "_id": {
            "day": { "$dayOfMonth": "$timestamp" },
            "product": "$product"
        },
        "price_avg": { "$avg": "$price" }
    }}
])

或者您甚至可以获得一整年中每个月的最后价格:

PriceChange.aggregate([
    { "$match": {
        "timestamp": { 
            "$gte": new Date("2013-01-01"), "$lt": new Date("2014-01-01")
        }
    }},
    { "$group": {
        "_id": {
            "date": {
                "year": { "$year" : "$timestamp" },
                "month": { "$month": "$timestamp" }
            },
            "product": "$product"
        },
        "price_last": { "$last": "$price" }
    }}
])

因此,您可以使用Date Aggregation Operators中的构建来实现各种结果。这些甚至可以帮助收集这些信息,以便写入新的“预聚合”集合,用于更快的分析。

我想有一种方法可以使用mapReduce将“运行”平均值与所有价格相结合。再次来自我的样本:

PriceHourly.mapReduce(
    function () {
        emit( this.timestamp.getDate(), this.price )
    },
    function (key, values) {
        var sum = 0;
        values.forEach(function(value) {
            sum += value;
        });
        return ( sum / values.length );
    },
    { 
        "query": {
            "timestamp": {
                "$gte": new Date("2014-03-01"), "$lt": new Date("2014-04-01")
            }
        },
        "out": { "inline": 1 },
        "scope": { "running": 0, "counter": 0 },
        "finalize": function(key,value) {
            running += value;
            counter++;
            return { "dayAvg": value, "monthAvg": running / counter };
        }
    }
)

这将返回类似的内容:

{
    "results" : [
        {
            "_id" : 1,
            "value" : {
                "dayAvg" : 105,
                "monthAvg" : 105
            }
        },
        {
            "_id" : 2,
            "value" : {
                "dayAvg" : 110,
                "monthAvg" : 107.5
           }
        }
    ],
}

但如果您希望看到日期和月份的离散值,那么如果不运行单独的查询则无法实现。