从mongodb中的子文档数组中拉出(除了一个)文档

问题描述 投票:0回答:3

我有一组数据,我已经导入了一段时间。在每次导入时,我都会将“历史”子文档附加到历史数组中。整体结构是这样的,但有更多的领域:

{ _id: ObjectId('000000000000000001'),
  history: [ {date: ISODate("2014-05-25T22:00:00Z"), value: 1},
             {date: ISODate("2014-05-26T22:00:00Z"), value: 1},
             {date: ISODate("2014-05-26T22:00:00Z"), value: 1} 
  ]
}

问题是,在一些情况下,导入很糟糕,我最终得到了同一日期的重复历史记录。我想删除所有重复项。我尝试使用$pull更新运算符执行此操作,并将重复调用它,直到每个日期都有适当数量的历史记录条目。问题是,我有超过一百万个数据点,每个数据点都有不同数量的重复数据 - 有些数据点多达12个。有没有办法在没有使用mapReduce的情况下拉除除一个之外的所有东西?我想的是:

db.test.update({'history.date': new Date(2014,4,26)},
               {
                $pullAll : 
                   {'history': {date: new Date(2014,4,27)}},
                $push : {'history' : {}}
               }, {multi:true})
javascript mongodb mongodb-query aggregation-framework
3个回答
2
投票

试试这个,效果很好:

db.collection.find().forEach(function(doc) {
     db.collection.update(
         { "_id": doc._id },
         { "$set": { "history": [doc.history] } }
     );
})

1
投票

您提出的问题是,您实际上最终会在语句中出现冲突路径,因为两个操作都在“历史”数组上运行。因此,这些操作实际上并不像您想象的那样“按顺序”执行,这会导致冲突,在尝试解析查询时会产生错误。

你也基本上“擦”了数组的内容,如果你的符号只是真正的简写,而不是打算只是“推”和空对象{},那么真的没有现在的方法来更新基于现有的文档在该文件中找到的值。

所以最终方法是循环来做到这一点,这真的不是那么糟糕:

 db.collection.find().forEach(function(doc) {
     db.collection.update(
         { "_id": doc._id },
         { "$set": { "history": [] } }
     );
     db.collection.update(
         { "_id": doc._id },
         { "$addToSet": { "history": { "$each": doc.history } } }
     );
 })

当然,如果您有MongoDB 2.6或更高版本,您可以在批量操作中执行此操作,从而提高效率:

 var count = 0;
 var bulk = db.collection.initializeOrderedBulkOp();

 db.collection.find().forEach(function(doc) {

    bulk.find({ "_id": doc._id }).update({
        "$set": { "history": [] }
    });
    bulk.find({ "_id": doc._id }).update({
        "$addToSet": { "history": { "$each": doc.history } }
    });
    count++;

    if ( count % 500 == 0 ) {
        bulk.execute();
        bulk = db.collection.initializeOrderedBulkOp();
        count = 0;
    }

 });

 if ( count > 0 )
     bulk.execute();

因此,对操作组合并发送500或1000组操作,这些操作应安全地遵循BSON 16MB限制,当然您可以根据需要进行调整。虽然每个更新实际上是按顺序执行的,但在此示例中,每500个项目仅对服务器发送一次实际发送/响应。

您也可以考虑使用聚合方法查找包含重复项的文档,以便通过不更新不需要更新的文档来提高效率:

db.collection.aggregate([
    { "$project": {
       "_id": "$$ROOT",
       "history": 1
    }},
    { "$unwind": "$history" },
    { "$group": {
        "_id": { "date": "$history.date", "value": "$history.value" },
        "orig": { "$first": "_id" }
    }},
    { "$group": {
        "_id": "$orig._id",
        "history": { "$first": "$orig.history" }
    }}
]).forEach(function(doc) {
    // same as above

或者甚至使用它作为跳板来删除重复项,因此您只需使用$set通过删除重复项为每个循环发送一个更新

 var count = 0;
 var bulk = db.collection.initializeOrderedBulkOp();

db.collection.aggregate([
    { "$unwind": "$history" },
    { "$group": {
        "_id": { "date": "$history.date", "value": "$history.value" },
        "orig": { "$first": "_id" }
    }},
    { "$group": {
        "_id": "$orig._id",
        "history": { "$push": "$_id" }
    }}
]).forEach(function(doc) {

    bulk.find({ "_id": doc._id }).update({
        "$set": { "history": doc.history }
    });
    count++;

    if ( count % 500 == 0 ) {
        bulk.execute();
        bulk = db.collection.initializeOrderedBulkOp();
        count = 0;
    }
]);

 if ( count > 0 )
     bulk.execute();

因此,有一些方法可以摆脱那些您可以考虑并适应您需求的重复条目。


0
投票

当我想到我可以在mongo shell中分三步执行此操作时,我正准备实现上面提到的一个脚本:

date = new Date(2014,4,26);
temp = 'SOMESPECIALTEMPVALUE'

db.test.update({'history.date': date},
           {$set: {
               'history.$.date' : temp
           }}, {multi:true})

db.test.update({'history.date': temp},
           {$pull: {
               'history.date' : temp
           }}, {multi:true})   

db.test.update({'history.date': temp},
           {$set: {
               'history.$.date' : date
           }}, {multi:true})

这是有效的,因为$只更新第一个匹配的子文档。使用pull我然后删除所有剩余的重复项。最后,我将剩余的临时值重置为其原始值。这对我来说效果很好,因为它是一次只有三个主观日期的操作。否则我可能会采用脚本方法。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.