Это на самом деле относится к давней проблеме на 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"
}
}}
])
Если ограничение заключается в том, что если "handled" был вФактически, поле должно присутствовать на других уровнях документа, тогда вы, вероятно, получите неожиданные результаты, но хорошо, если это поле появляется только в одной позиции документа и соответствует равенству.
Будущие выпуски (после 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 поддерживает «курсор» из агрегата oно это просто вопрос выбора подхода и повторения результатов с тем же блоком кода, который показан для обработки операторов массового обновления.Массовые операции и «курсоры» из совокупного вывода представлены в одной и той же версии (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 }});
});
});
Если вы абсолютно уверены, что делаете «множественные» обновленияили вы считаете, что это в конечном итоге более эффективно, чем обработка нескольких обновлений для каждого сопоставленного документа, тогда вы всегда можете определить максимальное количество возможных совпадений массивов и просто выполнить многократное обновление несколько раз, пока в основном не останется документов для обновления..
Действительный подход для версий 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
}}
Опять проблема здесь заключается в «предположении», что эти значения индекса, найденные при чтении документа, являются такими же значениями индекса в массиве во время обновления. Если новые элементы добавляются в массив таким образом, чтобы изменить порядок, то эти позиции больше не действительны и неправильные элементы фактически обновляются.
Таким образом, до тех пор, пока не будет определен разумный синтаксис, позволяющий обрабатывать несколько совпадающих элементов массива в одном операторе обновления, тогда основной подход состоит в том, чтобы либо обновить каждый соответствующий элемент массива в отдельном операторе (в идеале в Bulk), либо по существу выполнить максимальное количество элементов массива для обновления или обновления до тех пор, пока не будут возвращены более измененные результаты. В любом случае, вы должны «всегда» обрабатывать позиционных $
обновлений соответствующего элемента массива, даже если это обновляет только один элемент на оператор.
Массовые операции на самом деле являются «обобщенным» решением для обработки любых операций, которые работают как «множественные операции», и, поскольку для этого существует больше приложений, чем просто обновление нескольких элементов массива с тем же значением, тогда оно имеет курс уже реализован, и в настоящее время это лучший подход для решения этой проблемы.