2015-04-14 4 views
2

I имеют следующую структуру MongoDB документа:Обновление вложенного массива внутри массива MongoDB

[ 
    { 
     "_id": "04", 
     "name": "test service 4", 
     "id": "04", 
     "version": "0.0.1", 
     "title": "testing", 
     "description": "test", 
     "protocol": "test", 
     "operations": [ 
      { 
       "_id": "99", 
       "oName": "test op 52222222222", 
       "sid": "04", 
       "name": "test op 52222222222", 
       "oid": "99", 
       "description": "testing", 
       "returntype": "test", 
       "parameters": [ 
        { 
         "oName": "Param1", 
         "name": "Param1", 
         "pid": "011", 
         "type": "582", 
         "description": "testing", 
         "value": "" 
        }, 
        { 
         "oName": "Param2", 
         "name": "Param2", 
         "pid": "012", 
         "type": "58222", 
         "description": "testing", 
         "value": "" 
        } 
       ] 
      } 
     ] 
    } 
] 

Я был в состоянии использовать $ elemMatch для обновления полей операций, но когда я пытаюсь сделать то же самое (изменено) для параметров, похоже, не работает. Мне было интересно, какой другой подход я должен искать в попытке, чтобы иметь возможность успешно обновлять поля в определенном параметре, просматривая его pid.

Код обновления я в настоящее время и не работает, выглядит так:

var oid = req.params.operations; 
var pid = req.params.parameters; 

collection.update({"parameters":{"$elemMatch": {"pid": pid}}},{"$set": {"parameters.$.name":req.body.name, "parameters.$.description": req.body.description,"parameters.$.oName": req.body.oName,"parameters.$.type": req.body.type} }, function(err, result) { 
     if (err) { 
      console.log('Error updating service: ' + err); 
      res.send({'error':'An error has occurred'}); 
     } else { 
      // console.log('' + result + ' document(s) updated'); 
      res.send(result); 
     } 
    }); 
+2

MongoDB не поддерживает соответствие в более чем одним уровнем массив. Рассмотрите возможность изменения модели документа, чтобы каждый документ представлял собой операцию с информацией, общей для набора операций, дублированных в рабочих документах. – wdberkeley

ответ

5

Как @wdberkeley отметил в своем комментарии:

MongoDB не поддерживает соответствие более чем на один уровень массива. Рассмотрите возможность изменения модели документа таким образом, чтобы каждый документ представлял операцию с информацией, общей для набора операций, дублированных в рабочих документах.

Я согласен с выше и буду рекомендовать перепроектирование вашей схему, как MongoDB двигатель не поддерживает несколько позиционных операторов (см Multiple use of the positional $ operator to update nested arrays)

Однако, если вы знаете, индекс массива операций, на котором установлены параметры объекта необходимо обновить заранее, то запрос на обновление будет:

db.collection.update(
    { 
     "_id" : "04", 
     "operations.parameters.pid": "011" 
    }, 
    { 
     "$set": { 
      "operations.0.parameters.$.name": "foo", 
      "operations.0.parameters.$.description": "bar", 
      "operations.0.parameters.$.type": "foo" 
     } 
    } 
) 

EDIT:

Если вы хотите создать $set условия на лету то есть что-то, который поможет вам получить индексы для объектов, а затем изменить соответствующим образом, а затем рассмотреть вопрос об использовании MapReduce.

В настоящее время это представляется невозможным с использованием структуры агрегации. Существует неразрешенный открытый JIRA issue, связанный с ним. Однако обходной путь возможен с MapReduce. Основная идея MapReduce заключается в том, что он использует JavaScript в качестве языка запросов, но это, как правило, довольно медленнее, чем структура агрегации, и его нельзя использовать для анализа данных в режиме реального времени.

В вашей операции MapReduce вам необходимо определить пару шагов, т.е. шаг сопоставления (который отображает операцию в каждый документ в коллекции, и операция может либо ничего не делать, либо испускать какой-либо объект с ключами и прогнозируемыми значениями) и сокращение шага (который берет список испущенных значений и сводит его к одному элементу).

Для шага карты вы идеально хотели бы получить для каждого документа в коллекции, индекс для каждого поля массива operations и еще один ключ, который содержит ключи $set.

Ваш уменьшить шаг будет функция (которая не делает ничего) просто определяется как var reduce = function() {};

Заключительный шаг в вашей операции MapReduce затем создаст отдельные операции сбора данных, которые содержит излучаемый объект операции массива вместе с полем условия $set. Эта коллекция может периодически обновляться при запуске операции MapReduce в исходной коллекции. В целом, этот метод MapReduce будет выглядеть так:

var map = function(){ 
    for(var i = 0; i < this.operations.length; i++){ 
     emit( 
      { 
       "_id": this._id, 
       "index": i 
      }, 
      { 
       "index": i, 
       "operations": this.operations[i],    
       "update": { 
        "name": "operations." + i.toString() + ".parameters.$.name", 
        "description": "operations." + i.toString() + ".parameters.$.description", 
        "type": "operations." + i.toString() + ".parameters.$.type" 
       }      
      } 
     ); 
    } 
}; 

var reduce = function(){}; 

db.collection.mapReduce(
    map, 
    reduce, 
    { 
     "out": { 
      "replace": "operations" 
     } 
    } 
); 

Запрос коллекции operations вывода из эксплуатации MapReduce обычно даст вам результат:

db.operations.findOne() 

Выход:

{ 
    "_id" : { 
     "_id" : "03", 
     "index" : 0 
    }, 
    "value" : { 
     "index" : 0, 
     "operations" : { 
      "_id" : "96", 
      "oName" : "test op 52222222222", 
      "sid" : "04", 
      "name" : "test op 52222222222", 
      "oid" : "99", 
      "description" : "testing", 
      "returntype" : "test", 
      "parameters" : [ 
       { 
        "oName" : "Param1", 
        "name" : "foo", 
        "pid" : "011", 
        "type" : "foo", 
        "description" : "bar", 
        "value" : "" 
       }, 
       { 
        "oName" : "Param2", 
        "name" : "Param2", 
        "pid" : "012", 
        "type" : "58222", 
        "description" : "testing", 
        "value" : "" 
       } 
      ] 
     }, 
     "update" : { 
      "name" : "operations.0.parameters.$.name", 
      "description" : "operations.0.parameters.$.description", 
      "type" : "operations.0.parameters.$.type" 
     } 
    } 
} 

Затем вы можете использовать курсор из метода db.operations.find() для повторения и г и обновить свою коллекцию соответственно:

var oid = req.params.operations; 
var pid = req.params.parameters; 
var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid }); 

// Iterate through results and update using the update query object set dynamically by using the array-index syntax. 
while (cur.hasNext()) { 
    var doc = cur.next(); 
    var update = { "$set": {} }; 
    // set the update query object 
    update["$set"][doc.value.update.name] = req.body.name; 
    update["$set"][doc.value.update.description] = req.body.description; 
    update["$set"][doc.value.update.type] = req.body.type; 

    db.collection.update(
     { 
      "_id" : oid, 
      "operations.parameters.pid": pid 
     }, 
     update 
    ); 
}; 
+0

Это действительно работает, но есть ли способ создать условия $ set на лету? Что-то, что поможет мне получить индексы для объектов, а затем изменить соответственно? –

+0

Мне было интересно, если бы мне удалось реализовать что-то вроде того, что они предлагают здесь http://stackoverflow.com/questions/28756041/mongodb-updating-an-average-in-a-document-with-2 -nested-arrays –

+0

@SantiagoEsquivel Довольно возможно. Позже предоставит решение, но по существу использует MapReduce для получения проекции с индексами массива и использования этих значений индекса из этой проекции для обновления элементов массива «на лету». – chridam

1

Если данные, которые часто меняются, вы должны выровнять структуру и отдельные данные, которые меняет много от того, что не делает.

Если это данные, которые не меняются часто, а весь объект данных не массивный, просто измените объект на стороне клиента и обновите весь объект.

0

Мы попытаемся найти индекс внешнего массива (I) и внутреннего массива (J), а затем обновить

collection.findById(04) 
 
.then(result =>{ 
 
    for(let i = 0; i<result.operations.length; i++){ 
 
     if(result.operation[i]._id == "99"){ 
 
      let parameters = result.operations[i].parameters;`enter code here` 
 
      for(let j = 0; j<parameters.length; j++){ 
 
       if(parameters[j].pid == "011"){ 
 
        console.log("i", i); 
 
        console.log("j", j); 
 
        let data = {} 
 
        data["operations." + i + ".parameters." + j + ".oName"] = updateoName 
 
        data["operations." + i + ".parameters." + j + ".name"] = updatename 
 
        data["operations." + i + ".parameters." + j + ".pid"] = updatepid 
 
        data["operations." + i + ".parameters." + j + ".description"] = updatedescription 
 
        data["operations." + i + ".parameters." + j + ".value"] = updatevalue 
 
        console.log(data) 
 
        collection.update({ 
 
         "_id": "04" 
 
        },{ 
 
         $set: data 
 
        }) 
 
        .then(dbModel => res.json(dbModel)) 
 
       } 
 
      } 
 
     } 
 
    } 
 
})

Смежные вопросы