MongoDB - 二级数组更新

时间:2015-10-19 11:03:09

标签: arrays mongodb mongodb-query

我有以下结构:

 document: {
    'data': {
        'field_1': 'blabla',
        'field_2': 'blabla++',
        'interesting_field': [
             {
                 'f_1': 'abc',
                 'this_is_interesting': [
                      {
                         'a1': 'text',
                         'a2': 'other_text'
                      },
                      {
                         'a1': 'text 2',
                         'a2': 'text 3'
                      }
                 ]
             },
             'etc': 'etc'
        ]
    },
    'somthing_boring': 'bla'
 }

我需要做的是更新所有' a2'来自' this_is_interesting'来自' interesting_field'字段(如果它们符合某个标准)。

要在1级数组中更新,我可以执行以下操作:

{
  '_id': new ObjectId('5621020d78946ac20a000021'), 
  'data.interesting_field' : {
    $elemMatch : {
        'f_1' : 'abc'
    }
  }
},
{$set: {'data.interesting_field.$.something_to_update': 'something'}}

但是......似乎我无法使用2级数组的匹配。任何想法如何做到这一点? (想到了mapReduce ......但是我觉得我不能在更新查询中使用它。)

3 个答案:

答案 0 :(得分:2)

正如您所指出的,通常使用位置运算符$执行此操作。

不幸的是,现在位置运算符只支持数组深度匹配。

对于您想要的行为,有一个JIRA票证:https://jira.mongodb.org/browse/SERVER-831

如果您希望能够更改this_is_interesting中的匹配元素,则必须更改数据结构 - 将其展平,或将其拆分为两个集合。

编辑:实际上,您可以使用MapReduce进行更新,如以下答案所述:https://stackoverflow.com/a/33172255/236660

但这真的非常低效。我建议您重新构建数据。

答案 1 :(得分:1)

好吧,假设您要更新集合中a2: 'other_text'的所有文档,并将a2值设置为new_other。使用文档的实际结构,您需要遍历集合中的每个文档,然后对interesting_field数组中的每个元素进行循环,然后对this_is_interesting数组中的每个元素检查{{1}的值是否为a2匹配给定的条件,在这种情况下,您使用所谓的"dot notation""bulk"操作更新相应的子文档,以实现最高效率。您必须动态构建查询才能使其生效。

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

db.collection.find({ "document.data.interesting_field.this_is_interesting.a2": 'other_text' }).forEach(function(doc) { 
    var interesting_field = doc.document.data.interesting_field; 
    var size = interesting_field.length; 
    for(var ind=0; ind < size; ind++) {    
        var this_is_interesting = doc.document.data.interesting_field[ind]['this_is_interesting'];     
        for(var ix=0; ix < this_is_interesting.length; ix++) {  
            if(this_is_interesting[ix]['a2'] === 'other_text') { 
                var updateDocument = {};
                updateDocument['document.data.interesting_field.' + ind + '.this_is_interesting.' + ix + '.a2'] = 'new_other';      
                bulkOp.find({'_id': doc._id}).update({
                    '$set': updateDocument 
                });             
                count++;         
            }     
        }       
        if(count % 200 === 0) { 
            // Execute per 200 operations and re-init       
            bulkOp.execute();        
            bulkOp = db.collection.initializeOrderedBulkOp();     
        } 
    } 
})

// Clean up queues

if(count > 0) { 
    bulkOp.execute(); 
}

但最好的办法是改变你的文件结构

答案 2 :(得分:0)

您可以使用positional filter with array filters,自MongoDB 3.6起可用。

例如,要将a2更新为a1 ==“文本2”的“某物”,可以使用:

db.yourCollection.update(
{
 '_id': new ObjectId('5621020d78946ac20a000021'), 
  'data.interesting_field' : {
    $elemMatch : {
        'f_1' : 'abc'
    }
  }
},
{$set: {'data.interesting_field.$.this_is_interesting.$[elem].a2': 'something'}},
{ arrayFilters: [ {'elem.a1': 'text 2' ] })