【问题标题】:MongoDB Aggregation Framework - How To Do Multiple $group QueriesMongoDB 聚合框架 - 如何进行多个 $group 查询
【发布时间】:2014-05-11 13:09:06
【问题描述】:

我有以下 MongoDB 聚合查询,它查找指定月份内的所有记录,按天对记录进行 $groups,然后返回每天的平均价格。我还想返回整个月的平均价格。我可以通过使用多个 $groups 来做到这一点,如果可以,如何?

        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 
});

【问题讨论】:

  • 您无法按照您所描述的方式组合结果,除非您的意思是每个累积的“运行平均值”,否则将月平均值与每日结果相结合也没有多大意义日。否则,两组数据最适合两个查询。无论哪种情况,您的问题都可以从显示您似乎期望的数据中受益。

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


【解决方案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" }
   }}
])

尽管这可能是相当多余的,因为这可以通过一个单独的 group 语句来完成。

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

假设您有一个集合,其中包含“产品”、“类型”、“当前价格”和“时间戳”作为“价格”“更改”的日期。让我们将集合称为“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
           }
        }
    ],
}

但是,如果您希望看到日期和月份的离散值,那么如果不运行单独的查询,这将是不可能的。

【讨论】:

  • Neil,感谢您提供所有这些信息,这非常有帮助。但是,我无法使用任何一种方法来返回每日平均值和每月平均值。第一种方法只返回月平均值,第二个 $group 似乎覆盖了日平均值。如果我删除第二个 $group,则按预期返回每日平均值。
  • 第二种方法导致错误说明:{ [MongoError: exception: invalid operator '$avg'] name: 'MongoError', errmsg: 'exception: invalid operator \'$avg\'' ,代码:15999,确定:0 }
  • @ac360 抱歉,您想达到什么目的?您希望在同一结果集中同时日平均值和月平均值吗?我什至不确定那应该是什么样子,从你的问题中肯定不清楚。也不确定您指的是哪个列表中的错误。可能存在输入错误(我发现了一个),因为这些只是作为示例输入的。
  • @ac360 真的不知道你想在这里实现什么。我添加了一个运行平均值的样本。结果还是有问题吗?
猜你喜欢
  • 1970-01-01
  • 2020-09-22
  • 1970-01-01
  • 2023-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-08
  • 2013-07-23
相关资源
最近更新 更多