Sebenarnya lebih cocok untuk mapReduce daripada kerangka agregasi, setidaknya dalam pemecahan masalah awal. Kerangka kerja agregasi tidak memiliki konsep nilai dokumen sebelumnya, atau nilai dokumen "dikelompokkan" sebelumnya, jadi inilah mengapa ia tidak dapat melakukan ini.
Di sisi lain, mapReduce memiliki "lingkup global" yang dapat dibagi antara tahapan dan dokumen saat diproses. Ini akan memberi Anda "total berjalan" untuk saldo saat ini di akhir hari yang Anda butuhkan.
db.collection.mapReduce(
function () {
var date = new Date(this.dateEntry.valueOf() -
( this.dateEntry.valueOf() % ( 1000 * 60 * 60 * 24 ) )
);
emit( date, this.amount );
},
function(key,values) {
return Array.sum( values );
},
{
"scope": { "total": 0 },
"finalize": function(key,value) {
total += value;
return total;
},
"out": { "inline": 1 }
}
)
Itu akan menjumlahkan berdasarkan pengelompokan tanggal dan kemudian di bagian "menyelesaikan" itu membuat jumlah kumulatif dari setiap hari.
"results" : [
{
"_id" : ISODate("2015-01-06T00:00:00Z"),
"value" : 50
},
{
"_id" : ISODate("2015-01-07T00:00:00Z"),
"value" : 150
},
{
"_id" : ISODate("2015-01-09T00:00:00Z"),
"value" : 179
}
],
Dalam jangka panjang, Anda sebaiknya memiliki koleksi terpisah dengan entri untuk setiap hari dan mengubah saldo menggunakan $inc
dalam pembaruan. Lakukan juga $inc
upsert di awal setiap hari untuk membuat dokumen baru yang meneruskan saldo dari hari sebelumnya:
// increase balance
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": amount } },
{ "upsert": true }
);
// decrease balance
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": -amount } },
{ "upsert": true }
);
// Each day
var lastDay = db.daily.findOne({ "dateEntry": lastDate });
db.daily(
{ "dateEntry": currentDate },
{ "$inc": { "balance": lastDay.balance } },
{ "upsert": true }
);
Bagaimana TIDAK melakukan ini
Meskipun benar bahwa sejak penulisan aslinya ada lebih banyak operator yang diperkenalkan ke kerangka agregasi, apa yang ditanyakan di sini masih tidak praktis yang harus dilakukan dalam pernyataan agregasi.
Aturan dasar yang sama berlaku bahwa kerangka kerja agregasi tidak bisa referensi nilai dari "dokumen" sebelumnya, juga tidak dapat menyimpan "variabel global". "Peretasan" ini dengan pemaksaan semua hasil ke dalam array:
db.collection.aggregate([
{ "$group": {
"_id": {
"y": { "$year": "$dateEntry" },
"m": { "$month": "$dateEntry" },
"d": { "$dayOfMonth": "$dateEntry" }
},
"amount": { "$sum": "$amount" }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" }
}},
{ "$addFields": {
"docs": {
"$map": {
"input": { "$range": [ 0, { "$size": "$docs" } ] },
"in": {
"$mergeObjects": [
{ "$arrayElemAt": [ "$docs", "$$this" ] },
{ "amount": {
"$sum": {
"$slice": [ "$docs.amount", 0, { "$add": [ "$$this", 1 ] } ]
}
}}
]
}
}
}
}},
{ "$unwind": "$docs" },
{ "$replaceRoot": { "newRoot": "$docs" } }
])
Itu bukan solusi berkinerja atau "aman" mengingat bahwa kumpulan hasil yang lebih besar menjalankan probabilitas yang sangat nyata untuk melanggar batas BSON 16MB. Sebagai "aturan emas" , apa pun yang mengusulkan untuk menempatkan SEMUA konten dalam larik satu dokumen:
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" }
}}
maka itu adalah kelemahan mendasar dan oleh karena itu bukan solusi .
Kesimpulan
Cara yang jauh lebih konklusif untuk menangani ini biasanya adalah pemrosesan pos pada kursor hasil yang sedang berjalan:
var globalAmount = 0;
db.collection.aggregate([
{ $group: {
"_id": {
y: { $year:"$dateEntry"},
m: { $month:"$dateEntry"},
d: { $dayOfMonth:"$dateEntry"}
},
amount: { "$sum": "$amount" }
}},
{ "$sort": { "_id": 1 } }
]).map(doc => {
globalAmount += doc.amount;
return Object.assign(doc, { amount: globalAmount });
})
Jadi secara umum selalu lebih baik untuk:
-
Gunakan iterasi kursor dan variabel pelacakan untuk total.
mapReduce
sample adalah contoh yang dibuat-buat dari proses yang disederhanakan di atas. -
Gunakan total pra-agregat. Mungkin bersamaan dengan iterasi kursor bergantung pada proses pra-agregasi Anda, apakah itu hanya total interval atau total lari "dilanjutkan".
Kerangka agregasi harus benar-benar digunakan untuk "mengagregasi" dan tidak lebih. Memaksakan pemaksaan pada data melalui proses seperti memanipulasi ke dalam array hanya untuk memproses sesuai keinginan Anda bukanlah tindakan yang bijaksana atau aman, dan yang terpenting kode manipulasi klien jauh lebih bersih dan efisien.
Biarkan database melakukan hal-hal yang mereka kuasai, karena "manipulasi" Anda jauh lebih baik ditangani dalam kode.