У меня есть документ Mongo, который содержит массив элементов.
Я хотел бы reset атрибут .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)
однако он обновляет только первый сопоставленный элемент массива в каждом документе. (Это определенное поведение для $- позиционный оператор.)
Как я могу обновить элементы all с соответствующими элементами массива?
В настоящий момент невозможно использовать оператор позиционирования для обновления всех элементов в массиве. См. JIRA http://jira.mongodb.org/browse/SERVER-1243
Как работа вокруг, вы можете:
Что для меня работало:
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 и друзьями.
db.posts.find({ 'permalink':permalink }).forEach( function(doc) {...
и получаю Oops.. TypeError: Object # has no method 'forEach'
Это также может быть выполнено с помощью цикла 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. Он также минимизирует количество данных, передаваемых между клиентом и сервером.
С выпуском 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 и "устаревший".update()
API-запрос.Итак, если вы хотите использовать команду в оболочке
mongo
или других продуктах на основе оболочки (в частности, Robo 3T), вам понадобится последняя версия либо из ветки разработки, либо из производственной версии по версии 3.6 или выше.
См. также positional all $[]
, который также обновляет "несколько элементов массива", но не применяет к указанным условиям и относится к всем в массиве, где это искомое действие.
Также см. Обновление вложенного массива с помощью MongoDB, как эти новые позиционные операторы применяются к "вложенным" структурам массивов, где "массивы находятся внутри других массивов".
elem
?
Это действительно относится к долговременной проблеме в http://jira.mongodb.org/browse/SERVER-1243, где на самом деле существует ряд проблем для четкого синтаксиса который поддерживает "все случаи", где найдены совпадения с несколькими массивами. На самом деле существуют уже существующие методы "помощи" в решении этой проблемы, такие как Bulk Operations, которые были реализованы после этого исходного сообщения.
По-прежнему невозможно обновить более одного элемента сопоставленного массива в одном операторе обновления, поэтому даже при обновлении "multi" все, что вы когда-либо сможете обновить, - это всего лишь один элемент в массиве для каждого документа в этом единственном заявлении.
В настоящее время наилучшим решением является поиск и объединение всех согласованных документов и обработка массовых обновлений, которые по меньшей мере позволят отправлять многие операции в одном запросе с сингулярным ответом. Вы можете по желанию использовать .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 поддерживает "курсор" из совокупного вывода, это просто вопрос выбора подхода и повторения результатов с тем же блоком кода, который показан для обработки операторов Bulk update. Массовые операции и "курсоры" из совокупной производительности вводятся в той же версии (MongoDB 2.6) и поэтому обычно работают рука об руку для обработки.
В еще более ранних версиях, вероятно, лучше всего использовать .find()
для возврата курсора и отфильтровать выполнение операторов только столько раз, сколько элемент массива сопоставляется для итераций .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 }});
});
});
Если вы решительно настроены делать "несколько" обновлений или считаете, что это в конечном счете более эффективно, чем обработка нескольких обновлений для каждого согласованного документа, вы всегда можете определить максимальное количество возможных совпадений массива и просто выполнить "multi", обновите это много раз, пока в основном не будет больше документов для обновления.
Допустимый подход для версий 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 })
}
В любом случае есть определенные вещи, которые вы делаете не в обновлении:
Не обновляйте массив "одним выстрелом":. Если вы считаете, что более эффективно обновлять весь контент массива в коде, а затем просто $set
весь массив в каждый документ. Это может показаться более быстрым для обработки, но нет гарантии, что содержимое массива не изменилось с момента его чтения и обновления. Хотя $set
все еще является атомарным оператором, он только обновляет массив тем, что он "думает", является правильными данными и, следовательно, может переписать любые изменения, происходящие между чтением и записью.
Не вычисляйте значения индексов для обновления:. Там, где аналогично подходу "один снимок", вы просто определяете, что позиция 0
и позиция 2
(и т.д.) являются элементы для обновления и кодирования их с помощью и последующего оператора, например:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Снова проблема здесь - это "презумпция" того, что эти значения индекса, найденные при чтении документа, являются одинаковыми значениями индекса в массиве th во время обновления. Если новые элементы добавляются в массив таким образом, который изменяет порядок, то эти позиции больше недействительны, и на самом деле обновляются не те вещи.
Итак, до тех пор, пока не будет разумный синтаксис, определяемый для того, чтобы разрешить обработку нескольких согласованных элементов массива в едином операторе обновления, базовый подход состоит в том, чтобы либо обновить каждый элемент сопоставленного массива в виде indvidual (в идеале в Bulk), либо по существу разработать максимальные элементы массива для обновления или обновления, пока не будут возвращены никакие измененные результаты. Во всяком случае, вы должны "всегда" обрабатывать positional $
обновления в соответствующем элементе массива, даже если это только обновление одного элемента за утверждение.
Массовые операции на самом деле являются "обобщенным" решением для обработки любых операций, которые выходят за рамки "нескольких операций", и поскольку для этого существует больше приложений, чем просто обновление элементов нескольких элементов с одинаковым значением, то оно имеет курс уже реализован, и в настоящее время это лучший подход для решения этой проблемы.
Я удивлен, что это еще не было рассмотрено в монго. Общее монго не кажется отличным при работе с субмассивами. Вы не можете подсчитать субмассивы просто, например.
Я использовал первое решение Хавьера. Прочитайте массив в событиях, затем выполните цикл и постройте множество 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});
Это можно абстрагировать в функцию, используя обратный вызов для условного теста
Я пробовал следующее и прекрасно работал.
.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);
//функция обратного вызова в случае nodejs
Собственно, команда 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))
})
Я просто хотел добавить еще одно решение, которое сработало для меня, и довольно просто. Здесь это всего лишь массив тегов (строк), поэтому для обновления тега, называемого "test", "изменить", просто выполните следующее:
myDocuments.find({tags: "test" }, {fields: {_id: 1}}).forEach(function (doc) {
myDocuments.update(
{_id: doc._id, tags: "test"},
{$set:{'tags.$': "changed"}});
});