Как обновить несколько элементов массива в mongodb

У меня есть документ Mongo, который содержит массив элементов.

Я хотел бы сбросить атрибут .handled всех объектов в массиве, где .profile= XX.

Документ находится в следующей форме:

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

Итак, я попробовал следующее:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

Однако он обновляет только первый соответствующий элемент массива в каждом документе. (Это определенное поведение для $ - позиционный оператор.)

Как я могу обновить все соответствующие элементы массива?

Ответ 1

В настоящий момент невозможно использовать оператор позиционирования для обновления всех элементов в массиве. См. JIRA http://jira.mongodb.org/browse/SERVER-1243

Как работа вокруг, вы можете:

  • Обновление каждого элемента индивидуально (события .0.handled events.1.handled ...) или...
  • Прочитайте документ, внесите изменения вручную и сохраните его, заменив более старый (проверьте " Обновить, если Текущий ", если вы хотите обеспечить атомные обновления)

Ответ 2

Что для меня работало:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

Я думаю, что это яснее для новичков-монго и любого, кто знаком с JQuery и друзьями.

Ответ 3

С выпуском MongoDB 3.6 (и доступным в ветке разработки от MongoDB 3.5.12) вы можете обновлять несколько элементов массива за один запрос.

При этом используется отфильтрованный позиционный $[<identifier>] оператор синтаксиса обновления, представленный в этой версии:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

"arrayFilters" в соответствии с параметрами для .update() или даже Метод.updateOne(), .updateMany(), .findOneAndUpdate() или .bulkWrite() указывает условия для сопоставления с идентификатором, указанным в выражение об обновлении. Все элементы, соответствующие указанному условию, будут обновлены.

Отмечая, что "multi", как указано в контексте вопроса, использовалось в расчете на то, что это "обновит несколько элементов", но это не так и все еще не так. Это использование здесь применяется к "нескольким документам", как это всегда было, или теперь указывается как обязательная настройка .updateMany() в современных версиях API.

ПРИМЕЧАНИЕ Как ни странно, поскольку это указано в аргументе "options" для .update() и аналогичных методов, этот синтаксис обычно совместим со всеми последними версиями драйверов выпуска.

Однако это не относится к оболочке mongo, так как при реализации метода там ("по иронии судьбы для обратной совместимости") аргумент arrayFilters не распознается и удаляется внутренним методом, который анализирует параметры для доставки "обратная совместимость" с предыдущими версиями сервера MongoDB и "устаревший" синтаксис вызова API .update().

Поэтому, если вы хотите использовать команду в оболочке mongo или в других продуктах, основанных на оболочке (в частности, Robo 3T), вам нужна последняя версия из ветки разработки или производственной версии начиная с версии 3.6 или выше.

См. также positional all $[], который также обновляет "несколько элементов массива", но без применения к указанным условиям и применяется ко всем элементам в массиве, где это желаемое действие.

Также смотрите Обновление вложенного массива с помощью MongoDB, чтобы узнать, как эти новые позиционные операторы применяются к "вложенным" структурам массивов, где "массивы находятся в других массивах".

ВАЖНО - Обновленные установки из предыдущих версий "возможно" не включили функции MongoDB, что также может привести к сбою операторов. Вы должны убедиться, что процедура обновления завершена с указанием таких деталей, как обновления индекса, а затем запустить

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

Или более поздняя версия в зависимости от установленной версии. то есть "4.0" для версии 4 и далее в настоящее время. Это позволило использовать такие функции, как новые операторы позиционного обновления и другие. Вы также можете проверить с помощью:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

Чтобы вернуть текущую настройку

Ответ 4

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

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

Число циклов, выполняемых циклом, будет равным максимальному количеству временных подкадров с profile равным 10 и handled, не равным 0, в любом из документов вашей коллекции. Поэтому, если у вас есть 100 документов в вашей коллекции, и один из них имеет три поддокумента, которые соответствуют query, а во всех других документах меньше подходящих поддокументов, цикл будет выполняться три раза.

Этот метод позволяет избежать опасности скрещивания других данных, которые могут быть обновлены другим процессом, пока выполняется этот script. Он также минимизирует количество данных, передаваемых между клиентом и сервером.

Ответ 5

Это на самом деле относится к давней проблеме на http://jira.mongodb.org/browse/SERVER-1243, где на самом деле существует ряд проблем с ясным синтаксисом, который поддерживает "все случаи", когда сопоставления множественных массивов найденный. Фактически уже существуют методы, которые "помогают" в решении этой проблемы, такие как " Массовые операции", которые были реализованы после этой первоначальной публикации.

До сих пор невозможно обновить более одного сопоставленного элемента массива в одном операторе обновления, поэтому даже при "многократном" обновлении все, что вы когда-либо сможете обновить, - это только один согласованный элемент в массиве для каждого документа в этом отдельном документе. выражение.

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

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$setDifference": [
               { "$map": {
                   "input": "$events",
                   "as": "event",
                   "in": {
                       "$cond": [
                           { "$eq": [ "$$event.handled", 1 ] },
                           "$$el",
                           false
                       ]
                   }
               }},
               [false]
            ]
        }
    }}
]).forEach(function(doc) {
    doc.events.forEach(function(event) {
        bulk.find({ "_id": doc._id, "events.handled": 1  }).updateOne({
            "$set": { "events.$.handled": 0 }
        });
        count++;

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

if ( count % 1000 != 0 )
    bulk.execute();

Часть .aggregate() там будет работать, когда есть "уникальный" идентификатор для массива или все содержимое каждого элемента образует сам "уникальный" элемент. Это связано с тем, что оператор "set" в $setDifference используется для фильтрации любых false значений, возвращаемых операцией $map используемой для обработки массива на совпадения.

Если содержимое вашего массива не имеет уникальных элементов, вы можете попробовать альтернативный подход с $redact:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }
    }}
])

Ограничение состоит в том, что если "обработано" на самом деле поле, предназначенное для присутствия на других уровнях документа, то вы, вероятно, получите неожиданные результаты, но это хорошо, если это поле появляется только в одной позиции документа и является совпадением равенства.

Будущие выпуски (после 3.1 MongoDB) на момент написания будут иметь более простую операцию $filter:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$project": {
        "events": {
            "$filter": {
                "input": "$events",
                "as": "event",
                "cond": { "$eq": [ "$$event.handled", 1 ] }
            }
        }
    }}
])

И все выпуски, которые поддерживают .aggregate() могут использовать следующий подход с $unwind, но использование этого оператора делает его менее эффективным из-за расширения массива в конвейере:

db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "events": { "$push": "$events" }
    }}        
])

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

В более ранних версиях, вероятно, лучше всего просто использовать .find() для возврата курсора и отфильтровывать выполнение операторов по количеству .update() элемента массива для итераций .update():

db.collection.find({ "events.handled": 1 }).forEach(function(doc){ 
    doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
        db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
    });
});

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

Действительный подход для версий MongoDB 2.4 и 2.2 также может использовать .aggregate() чтобы найти это значение:

var result = db.collection.aggregate([
    { "$match": { "events.handled": 1 } },
    { "$unwind": "$events" },
    { "$match": { "events.handled": 1 } },
    { "$group": {
        "_id": "$_id",
        "count": { "$sum": 1 }
    }},
    { "$group": {
        "_id": null,
        "count": { "$max": "$count" }
    }}
]);

var max = result.result[0].count;

while ( max-- ) {
    db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}

В любом случае, есть некоторые вещи, которые вы не хотите делать в обновлении:

  1. Не обновляйте массив "одним выстрелом": если, по вашему мнению, было бы более эффективно обновить содержимое всего массива в коде, а затем просто $set весь массив в каждом документе. Это может показаться быстрее для обработки, но нет никакой гарантии, что содержимое массива не изменилось с момента его чтения и выполнения обновления. Хотя $set по-прежнему является атомарным оператором, он будет обновлять массив только тем, что, по его мнению, является правильными данными, и, следовательно, может перезаписывать любые изменения, происходящие между чтением и записью.

  2. Не вычисляйте значения индекса для обновления: если вы похожи на подход "один выстрел", вы просто решаете, что позиция 0 и позиция 2 (и т.д.) - это элементы для обновления и кодирования их с помощью и конечного выражения, например:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}
    

    Опять проблема здесь заключается в "предположении", что эти значения индекса, найденные при чтении документа, являются одинаковыми значениями индекса в массиве во время обновления. Если новые элементы добавляются в массив таким образом, что это меняет порядок, то эти позиции больше не действительны, а неправильные элементы фактически обновляются.

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

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

Ответ 6

Я удивлен, что это еще не было рассмотрено в монго. Общее монго не кажется отличным при работе с субмассивами. Вы не можете подсчитать субмассивы просто, например.

Я использовал первое решение Хавьера. Прочитайте массив в событиях, затем выполните цикл и постройте множество exp:

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

Это можно абстрагировать в функцию, используя обратный вызов для условного теста

Ответ 7

Я искал решение этой проблемы, используя новейший драйвер для С# 3.6, и здесь исправление, на котором я в конце концов остановился. Ключ здесь использует "$ []", который, согласно MongoDB, является новым с версии 3.6. См. Https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S [] для получения дополнительной информации.

Вот код:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

Для получения дополнительной информации см. Мой оригинальный пост здесь: Удалите элемент массива из ВСЕХ документов, используя драйвер MongoDB С#

Ответ 8

Я пробовал следующее и прекрасно работал.

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

//функция обратного вызова в случае nodejs

Ответ 9

Тема очень старая, но я пришел сюда, чтобы найти ответ, и поэтому нашел новое решение.

В MongoDB версии 3. 6+ теперь можно использовать позиционный оператор для обновления всех элементов в массиве. Смотрите официальную документацию здесь.

Следующий запрос будет работать на вопрос, заданный здесь. Я также проверил с драйвером Java-MongoDB, и он успешно работает.

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

Надеюсь, это поможет кому-то вроде меня.

Ответ 10

Собственно, команда save - это только экземпляр класса Document. У этого есть много методов и атрибутов. Таким образом, вы можете использовать функцию lean() для снижения рабочей нагрузки. См. Здесь. https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

Еще одна проблема с функцией сохранения, которая сделает данные конфликта одновременно с мультисохранением. Model.Update будет делать данные последовательно. Таким образом, для обновления нескольких элементов в массиве документа. Используйте свой знакомый язык программирования и попробуйте что-то вроде этого, я использую mongoose в этом:

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})

Ответ 11

Оператор $ [] выбирает все вложенные массивы. Вы можете обновить все элементы массива с помощью '$ []'

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

Ссылка

Ответ 12

Помните, что некоторые ответы в этой теме, предлагающие использовать $ [], НЕПРАВИЛЬНЫ.

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

Приведенный выше код обновит "handled" до 0 для всех элементов в массиве "events", независимо от его значения "profile". Запрос {"events.profile":10} предназначен только для фильтрации всего документа, а не документов в массиве. В этой ситуации необходимо использовать $[elem] с arrayFilters, чтобы указать состояние элементов массива, чтобы ответ нейла Ланна был правильным.

Ответ 13

Обновить поле массива в нескольких документах в dong монго.

Используйте $ pull или $ push с запросом update many для обновления элементов массива в mongoDb.

Notification.updateMany(
    { "_id": { $in: req.body.notificationIds } },
    {
        $pull: { "receiversId": req.body.userId }
    }, function (err) {
        if (err) {
            res.status(500).json({ "msg": err });
        } else {
            res.status(200).json({
                "msg": "Notification Deleted Successfully."
            });
        }
    });

Ответ 14

Я просто хотел добавить еще одно решение, которое сработало для меня, и довольно просто. Здесь это всего лишь массив тегов (строк), поэтому для обновления тега, называемого "test", "изменить", просто выполните следующее:

myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
    myDocuments.update(
        {_id: doc._id, tags: "test"}, 
        {$set:{'tags.$': "changed"}});
    });