如何在大小大于 2 的 mongo 数组中找到聚合



在 mongo 2.6 文档中,请参阅下面的几个

nms:PRIMARY> db.checkpointstest4.find()
{ "_id" : 1, "cpu" : [ 100, 20, 60 ], "hostname" : "host1" }
{ "_id" : 2, "cpu" : [ 40, 30, 80 ], "hostname" : "host1" }

我需要找到每个主机的平均 cpu(每个 cpu 阵列索引(,即基于上述两个,host1的平均值将被[70,25,70],因为cpu[0]100+40=70

当我有 3 个数组元素而不是两个数组元素时,我迷路了,请参阅 mongodb 数组元素的总平均值

最后,下面对我有用:

var map = function () {
    for (var idx = 0; idx < this.cpu.length; idx++) {
        var mapped = {
            idx: idx,
            val: this.cpu[idx]
        };
        emit(this.hostname, {"cpu": mapped});
    }
};
var reduce = function (key, values) {
    var cpu = []; var sum = [0,0,0]; cnt = [0,0,0];
    values.forEach(function (value) {        
        sum[value.cpu.idx] += value.cpu.val;
        cnt[value.cpu.idx] +=1;       
        cpu[value.cpu.idx] = sum[value.cpu.idx]/cnt[value.cpu.idx]
    });   
    return {"cpu": cpu};
};
db.checkpointstest4.mapReduce(map, reduce, {out: "checkpointstest4_result"});

在 MongoDB 3.2 中,includeArrayIndex出现,你可以这样做;

db.test.aggregate(
  {$unwind: {path:"$cpu", includeArrayIndex:"index"}}, 
  {$group: {_id:{h:"$hostname",i:"$index"}, cpu:{$avg:"$cpu"}}}, 
  {$sort:{"_id.i":1}},
  {$group:{_id:"$_id.h", cpu:{$push:"$cpu"}}}
)
// Make a row for each array element with an index field added.
{$unwind: {path:"$cpu", includeArrayIndex:"index"}}, 
// Group by hostname+index, calculate average for each group.
{$group: {_id:{h:"$hostname",i:"$index"}, cpu:{$avg:"$cpu"}}},
// Sort by index (to get the array in the next step sorted correctly)
{$sort:{"_id.i":1}},
// Group by host, pushing the averages into an array in order.
{$group:{_id:"$_id.h", cpu:{$push:"$cpu"}}}

升级将是你最好的选择,如MongoDB 3.2开始可用于$unwind includeArrayIndex所述。

如果你不能做到这一点,那么你总是可以使用mapReduce来处理:

db.checkpointstest4.mapReduce(
    function() {
        var mapped = this.cpu.map(function(val) {
            return { "val": val, "cnt": 1 };
        });
        emit(this.hostname,{ "cpu": mapped });
    },
    function(key,values) {
        var cpu = [];
        values.forEach(function(value) {
            value.cpu.forEach(function(item,idx) {
                if ( cpu[idx] == undefined )
                    cpu[idx] = { "val": 0, "cnt": 0 };
                cpu[idx].val += item.val;
                cpu[idx].cnt += item.cnt
            });
        });
        return { "cpu": cpu };
    },
    {
        "out": { "inline": 1 },
        "finalize": function(key,value) {
            return { 
                "cpu": value.cpu.map(function(cpu) {
                    return cpu.val / cpu.cnt;
                 })
            };
        }
    }
)

因此,"映射器"函数中的步骤是将数组内容转换为包含元素中的"值"和"计数"的对象数组,以便以后作为"reduce"函数的输入引用。您需要这与化简器的工作方式保持一致,并且对于获得平均值所需的总计数是必要的。

在"化简器"本身中,您基本上是将"值"和"计数"的每个位置的数组内容相加。这很重要,因为"reduce"函数可以在整个归约过程中多次调用,在后续调用中将其输出为"输入"。所以这就是为什么映射器和化简器都以这种格式工作的原因。

对于最终的简化结果,调用 finalize 函数来简单地查看每个求和的"值"和"计数",然后除以计数以返回平均值。

里程可能会因现代聚合管道处理或此mapReduce过程是否执行最佳而有所不同,主要取决于数据。以规定的方式使用$unwind肯定会增加要分析的文档数量,从而产生开销。相反,虽然与聚合框架中的本机运算符相比,JavaScript 处理通常会更慢,但这里的文档处理开销减少了,因为这是保留数组。

我给出的建议是,如果升级到 3.2 不是一种选择,那么即使是一个选项,那么至少根据您的数据和预期增长对两者进行基准测试,看看哪个最适合您。


返回

{
        "results" : [
                {
                        "_id" : "host1",
                        "value" : {
                                "cpu" : [
                                        70,
                                        25,
                                        70
                                ]
                        }
                }
        ],
        "timeMillis" : 38,
        "counts" : {
                "input" : 2,
                "emit" : 2,
                "reduce" : 1,
                "output" : 1
        },
        "ok" : 1
}

相关内容

  • 没有找到相关文章

最新更新