合并MongoDB中的文档

时间:2014-04-03 11:05:48

标签: python mongodb

我有一个庞大的MongoDB集合(约50万份文档)。

结构是这样的:

{'_id': '.....',
'passid':'ag325gdtew',
'text': '.......',
'count': '.......',
'title': '......',
'body': '.......'
}

字段passid在许多文档中都是相同的,我希望将它们以不同的方式组合到每个字段中。

我想:

  • 保持相同的passid
  • 在每个文档中加入文本和标题(文本+标题),然后在新文档的一个字段中加入最终字符串(text1 + text2 + text3)
  • 使用每个计数[count1,count2,count3]
  • 创建一个字段列表
  • 删除正文字段

所以新文件将是这样的:

{'_id': '.....',
'passid':'ag325gdtew',
'text': '.......', (string)
'count': ['..','...','..'] (list)
}

目前,我正在使用Python,但文件很大,脚本会持续运行数小时。

我做了什么:

  • 与群组合以检索具有唯一passid
  • 的列表
  • 在每个passid
  • 的列表中进行迭代
  • 使用find(passid)检索具有相同passid
  • 的所有文档的游标
  • 使用python
  • 进行字符串和列表的连接和追加
  • 删除旧文件
  • 保存新的

正如我所说,这非常耗时。你知道更快的方法吗?

以下是代码:

passids= db.collection.aggregate({ "$group": {"_id": '$passid'}})

for i in passids['result']:
    doc = {}
    doc['passid'] = i['_id']
    documents = db.collection.find({"passid": i['_id']})
    doc['count'] = []
    doc['text'] = ""

    for d in documents:
        doc['text'] = doc['text'] + " " + d['text']
        doc['text'] = doc['text'] + " " + d['title']
        doc['count'].append(d['count'])
        db.collection.remove(d)
    db.collection.save(doc)

2 个答案:

答案 0 :(得分:0)

如果您试图避免应用程序和数据库之间的大量网络流量,最好的选择通常是尝试尽可能接近(以网络术语)运行代码到数据库以获得最佳速度。< / p>

如果无法做到这一点,并且实际上只应在“一次性”操作中使用,则可以使用db.eval()

在服务器上运行代码
  

警告 必须在考虑使用之前,请仔细阅读db.eval()的手册页。虽然完成工作的最快方法是需要考虑的主要缺点:

     
      
  1. 在整个执行期间获取数据库的写锁定。
  2.   
  3. 除了获取写锁之外,由于JavaScript实现的单线程特性,使用JavaScript解释器的其他任务(例如“mapReduce”作业)也无法运行。
  4.   
  5. 这将无法在分片群集上运行,如果您的主机使用身份验证,则用户帐户将需要除基本读取和写入之外的特殊权限才能执行任务。
  6.   

一旦你考虑了上述所有内容,你就可以从头脑中走出来,承认方法存在并继续前进。

只要您可以处理不同集合的输出,就可以从mapReduce开始,这样可以简化逻辑

您可以定义映射器:

var mapper = function() {

   var passid = this.passid;
   delete this["_id"];
   delete this["body"];

   emit( passid, this );

};

然后定义一个reducer:

var reducer = function(key,values) {

    var reducedObject = {
        "text": "",
        "count": []
    };

    values.forEach(function(value) {
        reducedObject.text = reducedObject.text + " " + value.text;
        reducedObject.text = reducedObject.text + " " + value.title;
        reducedObject.push( value.count );
    });

    return reducedObject;

};

然后你可以运行mapReduce操作:

db.collection.mapReduce(
    mapper,
    reducer,
    {
        "out": { "replace": "newcollection" }
    }
)

使用mapReduce输出就是这样,你不希望在最终输出中这样做,所以你可以像这样改变它:

db.eval(function() {
    db.newcollection.find().forEach(function(doc) {
        var newDoc = {};
        for ( var k in doc.values ) {
            newDoc[k] = doc.values[k];
        }
        db.newcollection.update({ _id: doc._id }, newDoc );
    });
})

这将把事物放入重新整形的集合中,你甚至可以考虑在数据库之间移动它以解决锁定问题。这仍然可能使您处于需要将其与原始集合交换的位置,但有办法实现这一点。


作为替代方案,您基本上可以切换到它并立即运行db.eval()操作。所以这基本上将流程转换为相应的JavaScript:

db.eval(function() {

    var lastid = "";
    var counter = 0;
    var text = "";
    var count = [];

    db.collection.find().forEach(function(doc) {
        if ( (doc.passid != lastid) && (counter != 0) ) {
            db.collection.update(
                { "_id": doc._id },
                { 
                    "passid": lastid,
                    "text": text,
                    "count": count
                }
            );
            text = "";
            count = [];
        }
        text = text + " " + doc.text;
        text = text + " " doc.title;
        count.push( doc.count );
        counter++;
        lastid = passid;
    });
})

因此,批量更改文档绝不是一件好事,但有一些方法可以解决这个问题并将所有操作保留在服务器上。

答案 1 :(得分:0)

根据我的经验,使用mongo进行此类操作的大部分缓慢来自数据库的往返,因此尽可能少地调用它。如果您的文档足够小(就像您的示例所示),以便整个集合适合内存,您可以通过进行单个多次插入和多次删除来节省大量时间:

passids= db.collection.aggregate({ "$group": {"_id": '$passid'}})

new_docs = []

for i in passids['result']:
    doc = {}
    doc['passid'] = i['_id']
    documents = db.collection.find({"passid": i['_id']})
    doc['count'] = []
    doc['text'] = ""

    for d in documents:
        doc['text'] = doc['text'] + " " + d['text']
        doc['text'] = doc['text'] + " " + d['title']
        doc['count'].append(d['count'])

   new_docs.append(doc)

# Instead of removing all the documents one by one, 
# dropping the collection is much faster
db.collection.drop()

db.collection.insert(new_docs)

为了安全起见,我将文档保存在新的集合中,并在检查完所有内容后才删除旧文档。