Це насправді пов'язане з давньою проблемою на веб-сайті 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
}}
Знову ж таки, проблема полягає в «презумпції», що ті значення індексу, знайдені під час читання документа, є тими самими значеннями індексу, що й у масиві на момент оновлення. Якщо нові елементи додаються до масиву таким чином, що змінює порядок, то ці позиції більше не дійсні, а неправильні елементи фактично оновлюються.
Отже, поки не буде визначений розумний синтаксис, який дозволяє обробляти кілька узгоджених елементів масиву в одному операторі оновлення, тоді основним підходом є або оновлення кожного зібраного елемента масиву в окремому операторі (в ідеалі масово), або по суті відпрацювання максимальних елементів масиву оновлювати або продовжувати оновлювати, поки не буде повернено більше змінених результатів. У будь-якому випадку вам слід "завжди" обробляти позиційні$
оновлення для відповідного елемента масиву, навіть якщо це лише оновлення одного елемента на оператор.
Масові операції насправді є "узагальненим" рішенням для обробки будь-яких операцій, які виявляються "декількома операціями", і оскільки для цього є більше додатків, ніж просто оновлення безглуздих елементів масиву з однаковим значенням, то воно, звичайно, було реалізовано Вже зараз це найкращий підхід до вирішення цієї проблеми.