Sepertinya Anda memulai ini tetapi Anda tersesat di beberapa konsep lainnya. Ada beberapa kebenaran dasar saat bekerja dengan array dalam dokumen, tetapi mari kita mulai dari bagian terakhir yang Anda tinggalkan:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}}
])
Jadi itu hanya akan menggunakan $group
pipa untuk mengumpulkan dokumen Anda pada nilai yang berbeda dari bidang "status" dan kemudian juga menghasilkan bidang lain untuk "hitungan" yang tentu saja "menghitung" kemunculan kunci pengelompokan dengan memberikan nilai 1
ke $sum
operator untuk setiap dokumen yang ditemukan. Ini menempatkan Anda pada titik seperti yang Anda gambarkan:
{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }
Itulah tahap pertama dari ini dan cukup mudah untuk dipahami, tetapi sekarang Anda perlu tahu cara mendapatkan nilai dari sebuah array. Anda mungkin tergoda setelah Anda memahami "notasi titik" konsep dengan benar untuk melakukan sesuatu seperti ini:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
Tetapi apa yang akan Anda temukan adalah bahwa "total" sebenarnya adalah 0
untuk setiap hasil tersebut:
{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }
Mengapa? Nah, operasi agregasi MongoDB seperti ini sebenarnya tidak melintasi elemen array saat pengelompokan. Untuk melakukan itu, kerangka kerja agregasi memiliki konsep yang disebut $unwind
. Nama ini relatif cukup jelas. Array yang disematkan di MongoDB sangat mirip dengan memiliki asosiasi "satu-ke-banyak" antara sumber data yang ditautkan. Jadi apa $unwind
tidak persis seperti itu hasil "gabung", di mana "dokumen" yang dihasilkan didasarkan pada konten larik dan informasi duplikat untuk setiap induk.
Jadi untuk bertindak pada elemen array, Anda perlu menggunakan $unwind
pertama. Ini secara logis akan mengarahkan Anda ke kode seperti ini:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
Dan hasilnya:
{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }
Tapi itu kurang tepat bukan? Ingat apa yang baru saja Anda pelajari dari $unwind
dan bagaimana de-normalisasi bergabung dengan informasi induk? Jadi sekarang diduplikasi untuk setiap dokumen karena keduanya memiliki dua anggota array. Jadi, meskipun bidang "total" benar, "hitungan" dua kali lebih banyak dari yang seharusnya dalam setiap kasus.
Perlu sedikit lebih hati-hati, jadi alih-alih melakukan ini dalam satu $group
tahap, itu dilakukan dalam dua:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}}
])
Yang sekarang mendapatkan hasil dengan total yang benar di dalamnya:
{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }
Sekarang jumlahnya benar, tetapi masih belum persis seperti yang Anda minta. Saya pikir Anda harus berhenti di situ karena jenis hasil yang Anda harapkan benar-benar tidak cocok untuk hanya satu hasil dari agregasi saja. Anda mencari total untuk menjadi "di dalam" hasilnya. Itu benar-benar tidak termasuk di sana, tetapi pada data kecil tidak apa-apa:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}},
{ "$group": {
"_id": null,
"data": { "$push": { "count": "$count", "total": "$total" } },
"totalCost": { "$sum": "$total" }
}}
])
Dan bentuk hasil akhir:
{
"_id" : null,
"data" : [
{
"count" : 1,
"total" : 350
},
{
"count" : 2,
"total" : 700
}
],
"totalCost" : 1050
}
Tapi, "Jangan Lakukan Itu" . MongoDB memiliki batas dokumen pada respons 16MB, yang merupakan batasan spesifikasi BSON. Pada hasil kecil Anda dapat melakukan pembungkusan praktis semacam ini, tetapi dalam skema yang lebih besar dari hal-hal yang Anda inginkan hasilnya dalam bentuk sebelumnya dan baik kueri terpisah atau langsung dengan mengulangi seluruh hasil untuk mendapatkan total dari semua dokumen.
Anda tampaknya menggunakan versi MongoDB kurang dari 2.6, atau menyalin keluaran dari cangkang RoboMongo yang tidak mendukung fitur versi terbaru. Dari MongoDB 2.6 meskipun hasil agregasi bisa menjadi "kursor" daripada array BSON tunggal. Jadi, respons keseluruhan bisa jauh lebih besar dari 16 MB, tetapi hanya jika Anda tidak memadatkan satu dokumen sebagai hasilnya, seperti yang ditunjukkan pada contoh terakhir.
Ini akan benar terutama dalam kasus di mana Anda "memberi halaman" hasil, dengan 100 hingga 1000 baris hasil tetapi Anda hanya ingin "total" kembali dalam respons API ketika Anda hanya mengembalikan "halaman" dari 25 hasil di suatu saat.
Bagaimanapun, itu akan memberi Anda panduan yang masuk akal tentang cara mendapatkan jenis hasil yang Anda harapkan dari formulir dokumen umum Anda. Ingat $unwind
untuk memproses array, dan umumnya $group
beberapa kali untuk mendapatkan total pada tingkat pengelompokan yang berbeda dari pengelompokan dokumen dan koleksi Anda.