Ini sebenarnya terkait dengan masalah lama di http://jira.mongodb.org/browse/SERVER-1243 di mana sebenarnya ada sejumlah tantangan untuk sintaks yang jelas yang mendukung "semua kasus" di mana pertandingan array mutiple berada ditemukan. Sebenarnya ada metode yang sudah ada yang "membantu" dalam solusi untuk masalah ini, seperti Operasi Massal yang telah dilaksanakan setelah posting asli ini.
Masih tidak mungkin untuk memperbarui lebih dari satu elemen array yang cocok dalam satu pernyataan pembaruan tunggal, sehingga bahkan dengan pembaruan "multi" semua yang dapat Anda perbarui hanyalah satu elemen matematika dalam array untuk setiap dokumen dalam satu dokumen itu dalam satu pernyataan.
Solusi terbaik yang mungkin saat ini adalah menemukan dan mengulang semua dokumen yang cocok dan memproses pembaruan Massal yang setidaknya akan memungkinkan banyak operasi dikirim dalam satu permintaan dengan respons tunggal. Secara opsional Anda dapat menggunakan .aggregate()
untuk mengurangi konten array yang dikembalikan dalam hasil pencarian hanya untuk mereka yang cocok dengan kondisi untuk pemilihan pembaruan:
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();
The .aggregate()
porsi akan bekerja ketika ada "unik" pengidentifikasi untuk array atau semua konten untuk setiap elemen membentuk "unik" elemen itu sendiri. Ini disebabkan oleh operator "set" yang $setDifference
digunakan untuk memfilter false
nilai yang dikembalikan dari $map
operasi yang digunakan untuk memproses array untuk pertandingan.
Jika konten array Anda tidak memiliki elemen unik, Anda dapat mencoba pendekatan alternatif dengan $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Di mana batasannya adalah bahwa jika "ditangani" sebenarnya suatu bidang dimaksudkan untuk hadir di tingkat dokumen lain, maka Anda kemungkinan akan mendapatkan hasil yang tidak terduga, tetapi baik-baik saja di mana bidang itu hanya muncul di satu posisi dokumen dan merupakan pencocokan kesetaraan.
Rilis di masa mendatang (pos 3.1 MongoDB) pada saat penulisan akan memiliki $filter
operasi yang lebih sederhana:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
Dan semua rilis yang mendukung .aggregate()
dapat menggunakan pendekatan berikut $unwind
, tetapi penggunaan operator itu menjadikannya pendekatan yang paling tidak efisien karena ekspansi array di dalam pipa:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
Dalam semua kasus di mana versi MongoDB mendukung "kursor" dari output agregat, maka ini hanya masalah memilih pendekatan dan mengulangi hasilnya dengan blok kode yang sama yang ditunjukkan untuk memproses pernyataan pembaruan Massal. Operasi Massal dan "kursor" dari output agregat diperkenalkan dalam versi yang sama (MongoDB 2.6) dan oleh karena itu biasanya bekerja bergandengan tangan untuk diproses.
Pada versi yang bahkan lebih awal maka mungkin yang terbaik adalah hanya menggunakan .find()
untuk mengembalikan kursor, dan menyaring eksekusi pernyataan hanya berapa kali elemen array cocok dengan .update()
iterasi:
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 }});
});
});
Jika Anda secara tegas bertekad untuk melakukan pembaruan "multi" atau menganggap itu pada akhirnya lebih efisien daripada memproses beberapa pembaruan untuk setiap dokumen yang cocok, maka Anda selalu dapat menentukan jumlah maksimum pencocokan array yang mungkin dan hanya menjalankan pembaruan "banyak" yang banyak kali, hingga pada dasarnya tidak ada lagi dokumen untuk diperbarui.
Pendekatan yang valid untuk versi MongoDB 2.4 dan 2.2 juga dapat digunakan .aggregate()
untuk menemukan nilai ini:
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 })
}
Apa pun masalahnya, ada hal-hal tertentu yang tidak ingin Anda lakukan dalam pembaruan:
Jangan "satu tembakan" memperbarui array: Di mana jika Anda berpikir mungkin lebih efisien untuk memperbarui seluruh konten array dalam kode dan kemudian hanya $set
seluruh array dalam setiap dokumen. Ini mungkin tampak lebih cepat untuk diproses, tetapi tidak ada jaminan bahwa konten array tidak berubah sejak dibaca dan pembaruan dilakukan. Meskipun $set
masih merupakan operator atom, itu hanya akan memperbarui array dengan apa yang "dianggapnya" adalah data yang benar, dan dengan demikian kemungkinan akan menimpa setiap perubahan yang terjadi antara baca dan tulis.
Jangan menghitung nilai indeks yang akan diperbarui: Jika mirip dengan pendekatan "satu tembakan", Anda hanya menentukan posisi 0
dan posisi itu 2
(dan seterusnya) adalah elemen untuk memperbarui dan kode ini dengan dan pada akhirnya pernyataan seperti:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Sekali lagi masalah di sini adalah "anggapan" bahwa nilai-nilai indeks yang ditemukan ketika dokumen dibaca adalah nilai indeks yang sama dalam array pada saat pembaruan. Jika item baru ditambahkan ke array dengan cara yang mengubah urutan maka posisi tersebut tidak lagi valid dan item yang salah sebenarnya diperbarui.
Jadi sampai ada sintaks yang masuk akal ditentukan untuk memungkinkan beberapa elemen array yang cocok untuk diproses dalam pernyataan pembaruan tunggal maka pendekatan dasar adalah baik memperbarui setiap elemen array yang cocok dalam pernyataan indvidual (idealnya dalam Massal) atau pada dasarnya bekerja di luar elemen array maksimum untuk memperbarui atau terus memperbarui hingga tidak ada lagi hasil yang dimodifikasi yang dikembalikan. Bagaimanapun, Anda harus "selalu" memproses pembaruan posisi$
pada elemen array yang cocok, bahkan jika itu hanya memperbarui satu elemen per pernyataan.
Operasi Massal sebenarnya adalah solusi "umum" untuk memproses setiap operasi yang berhasil menjadi "beberapa operasi", dan karena ada lebih banyak aplikasi untuk ini daripada sekadar memperbarui elemen array mutiple dengan nilai yang sama, maka tentu saja telah diimplementasikan sudah, dan saat ini merupakan pendekatan terbaik untuk menyelesaikan masalah ini.