MongoDB 2.6聚合更新了$ out集合

时间:2014-05-02 10:43:30

标签: mongodb aggregation-framework

我目前正通过MongoHQ使用MongoDB 2.6。我有几个mapreduces工作,它们从一个集合(c1)中处理原始数据以生成一个新的集合(c2)。 我还有一个聚合管道,它解析(c2)生成一个带有$ out运算符的新集合(c3)。

但是,我需要在聚合管道之外的(c3)添加额外的字段,并在新的聚合运行后保留它们,但似乎聚合,基于_id键只是覆盖内容而不更新它。所以,如果我之前添加了一个额外的字段,比如foo:'bar'到(c3),我重新运行聚合,我将松开foo字段。

基于文档(http://docs.mongodb.org/manual/reference/operator/aggregation/out/#pipe._S_out

  

替换现有收藏

     

如果$ out操作指定的集合已经存在,那么在聚合完成后, $ out阶段将原始集合原子替换为新的结果集合。 $ out操作不会更改先前集合中存在的任何索引。如果聚合失败,$ out操作不会对预先存在的集合进行任何更改。

是否有更好的方法或棘手的方法:-)更新$ out集合而不是用相同的_id覆盖记录?我可以写一个python脚本或javascript来完成这项工作,但我会避免做很多数据库调用,并以更智能的方式作为聚合。可能是不可能的,所以我会寻找一条不同的,更“古典”的道路。

感谢您的帮助

3 个答案:

答案 0 :(得分:4)

好吧,不是直接使用$out运算符和mapReduce输出一样,这几乎是一个“覆盖”操作(尽管mapReduce也有“合并”和“减少”模式)

但是因为你有一个MongoDB 2.6版本,你实际上会返回一个“光标”。因此,虽然“客户端/服务器”交互可能不是您想要的最佳,但您也有“批量更新”操作,因此您可以执行以下操作:

var cursor = db.collection.aggregate([
    // pipeline here
]);

var batch = [];

while ( cursor.hasNext() ) {
    var doc = cursor.next();

    var updoc = {
        "q": { "_id": doc._id },
        "u": {
            // only new fields except for
            "$setOnInsert": {
                // the fields you expect to add from before
            },
            "upsert": true
        }
    };

    batch.push(updoc);

    // try to do sensible under 16MB updates, number may vary
    if ( ( batch.length % 500 ) == 0 ) {
        db.runCommand({
            "update": "newcollection",
            "updates": batch
        });
        batch = [];    // reset the content
    }

}

db.runCommand({
    "update": "newcollection",
    "updates": batch
});

当然,虽然会有很多反对者,但并非没有理由,因为你真的需要权衡后果(非常真实),你总是可以用db.eval()包装本质上是一个JavaScript调用为了让服务器端完整执行。

但是在可能的情况下(除非你有一个完全远程的数据库解决方案),通常建议采用“客户端/服务器”选项,但保持流程“关闭”(在网络术语中)到服务器尽可能。

答案 1 :(得分:2)

与Map reduce不同,似乎聚合框架中的$out运算符具有一组非常具体的预定义行为(http://docs.mongodb.org/manual/reference/operator/aggregation/out/#behaviors),但是,似乎{{1} }选项可能会更改,我没有找到与此特定案例相关的JIRA,但其他人已发布更改(https://jira.mongodb.org/browse/SERVER-13201)。

至于现在解决你的问题,你要么被迫恢复到Map Reduce(我不知道运行它的场景),要么以某种方式聚合,允许你输入新的数据以及你需要的旧数据。

实现此目标的最常见方法可能是使用新数据更新原始行,也可以将原始行聚合回自身。

答案 2 :(得分:2)

感谢您的所有消息。 由于我不想使用游标(请求消费),我尝试通过组合2个地图减少作业和一个聚合来获得工作。它非常“胖”,但它有效,可以为其他人提供一些想法。 当然,我很高兴听到你其他很好的选择。

所以,我有一个集合c1 ,它是之前mapreduce作业的结果,正如您可以通过值对象看到的那样。 c1:{ id:'xxxx',值:{语言:'...',关键字:'...',参数:'...',field1:val1,field2:val2} } xxxx唯一ID键是value.language,value.keyword和value.params的串联,如下所示: * xxxx = _ *

我有另一个集合c2:{_ id:ObjectID,语言:'...',关键字:'...',field1:val1,field2:val2,标签:'yyyyy'} < / strong>这是相当的 c1集合的投影,但带有额外的字段标签,这是一个以逗号分隔的不同标签的字符串。此c2集合是语言和关键字及其附加字段值的所有组合的中央存储库。

:定位

  

目标是根据c1集合中的所有记录进行分组   组密钥_,进行一些计算   其他字段并将结果存储到c2集合中,但保留   来自c2的旧“标签”字段具有相同的键。所以fields1&amp; 2的   每次我们启动整个时都会重新计算这个c2集合   批处理但标签字段将保持不变。

正如我的第一条消息所述,通过使用聚合或mapreduce作业,您无法达到此目标,因为“标签”字段将被删除。

因为我不想使用游标和其他foreach循环,这是非常网络和数据库请求消耗(我有一个大集合,我使用MongoHQ服务) 我尝试使用mapreduce和聚合作业解决问题。

第一阶段

所以,首先我运行mapreduce作业( m1 ),这是c2集合的一种副本,但是清除了field1&amp;的值。结果将存储在 c3集合

function m1Map(){

    language = this['value']['language'];
    keyword = this['value']['keyword'];
    labels = this['labels'];
    key = language + '_' + keyword;

    emit(key,{'language':language,'keyword':keyword,'field1': 0, 'field2': 0.0, 'labels' : labels});
}

function m1Reduce(key,values){

    language = values[0]['language'];
    keyword = values[0]['keyword'];
    labels = values[0]['labels'];

    return {'language':language,'keyword':keyword,'field1': 0, 'field2': 0.0, 'labels' : labels}};
}

现在, c3 是c2集合的副本,其中field1&amp; 2设置为0.以下是此集合的形状: c3:{ id:'',值:{语言:'...',关键字:'...',字段1:0,字段2:0.0,标签:'.. 。“}}

第二阶段

在第二步中,我运行一个mapreduce作业( m2 ),它按键_对c1集合值进行分组,并在我的项目中投影一个额外的字段'labels',其中包含固定值'x'例。这个'x'值永远不会用在c2集合上,这是一个特殊值。此m2 mapreduce作业的输出将存储在相同的先前c3集合中,并在out指令中使用'reduce'选项。 python脚本将进一步描述。

function m2Map(){

    language = this['value']['language'];
    keyword = this['value']['keyword'];
    field1 = this['value']['field1'];
    field2 = this['value']['field2'];
    key = language + '_' + keyword;

    emit(key,{'language':language,'keyword':keyword,'field1': field1, 'field2': field2, 'labels' : 'x'});
}

然后我对Reduce函数做了一些计算:

function m2Reduce(key,values){

    // Init
    language = values[0]['language'];
    keyword = values[0]['keyword'];

    field1 = 0;
    field2 = 0;
    bLabel = 0;
    for (var i = 0; i < values.length; i++){
            if (values[i]['labels'] == 'x') {
                    // We know these emit values are coming from the map and not from previous value on the c2 collection
                    // 'x' is never used on the c2 collection
                    field1 += parseInt(values[i]['field1']);
                    field2 += parseFloat(values[i]['field2']);
            } else {
                    // these values are from the c2 collection
                    if (bLabel == 0)        {
                            // we keep the former value for the 'labels' field
                            labels = values[i]['labels'];
                            bLabel = 1;
                    } else {
                            // we concatenate the 'labels' field if we have 2 records but theorytically it is impossible as c2 has only one record by unique key
                            // anyway, a good check afterwards :-)
                            labels += ','+values[i]['labels'];
                    }
            }
    }
    if (bLabel == 0)        {
        // if values are only coming from the map emit, we force again the 'x' value for labels, it these values are re-used in another reduce call
        labels = 'x';
    }

    return {'language':language,'keyword':keyword, 'field1': field1, 'field2': field2, 'labels' : labels};
}

Python mapreduce脚本,它调用两个m1&amp; m2 mapreduce工作 (参见pymongo for import:http://api.mongodb.org/python/2.7rc0/installation.html

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pymongo import MongoClient
from pymongo import MongoReplicaSetClient
from bson.code import Code
from bson.son import SON

# MongoHQ
uri = 'mongodb://user:passwd@url_node1:port,url_node2:port/mydb'
client = MongoReplicaSetClient(uri,replicaSet='set-xxxxxxx')
db = client.mydb
coll1 = db.c1
coll2 = db.c2

#Load map and reduce functions
m1_map = Code(open('m1Map.js','r').read())
m1_reduce = Code(open('m1Reduce.js','r').read())
m2_map = Code(open('m2Map.js','r').read())
m2_reduce = Code(open('m2Reduce.js','r').read())

#Run the map-reduce queries
results = coll2.map_reduce(m1_map,m1_reduce,"c3",query={})
results = coll1.map_reduce(m2_map,m2_reduce,out=SON([("reduce", "c3")]),query={})

第3阶段

此时,我们有一个c3集合,其中包含所有字段1和1。保留2个计算值和标签。所以现在,我们必须运行最后一个聚合管道,将c3内容(带有复合值的mapreduce形式)复制到更经典的集合c2,其中包含没有值对象的展平字段。

db.c3.aggregate([{$project : { _id: 0, keyword: '$value.keyword', language: '$value.language', field1: '$value.field1', field2 : '$value.field2', labels : '$value.labels'}},{$out:'c2'}])

Etvoilà!达到了目标。这个解决方案很长,有2个mapreduce作业和一个聚合管道,但对于那些不想使用消费游标或外部循环的人来说,这是一个替代解决方案。

感谢。