我目前正通过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来完成这项工作,但我会避免做很多数据库调用,并以更智能的方式作为聚合。可能是不可能的,所以我会寻找一条不同的,更“古典”的道路。
感谢您的帮助
答案 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={})
此时,我们有一个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作业和一个聚合管道,但对于那些不想使用消费游标或外部循环的人来说,这是一个替代解决方案。
感谢。