Обновление вложенного массива внутри массива 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);
        }
    });

4 ответа

Решение

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

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

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

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

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" 
        }
    }
)

РЕДАКТИРОВАТЬ:

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

В настоящее время это представляется невозможным с использованием структуры агрегации. С этим связана нерешенная открытая проблема JIRA. Тем не менее, обходной путь возможен с 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 
    );
};

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

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

Начиная с версии 3.6, вы можете использовать $ [] в соединении с $ [] для обновления вложенных массивов.

Обновить вложенные массивы в сочетании с $ []

Фильтрованный позиционный оператор $ [] вместе со всем позиционным оператором $ [] может использоваться для обновления вложенных массивов.

https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/

Мы попытаемся найти индекс внешнего массива (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))
                }
            }
        }
    }
})

Другие вопросы по тегам