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