Menghubungkan collection dengan $lookup, menjalankan beberapa sub-pipeline sekaligus dengan $facet, membuat histogram dengan $bucket, serta menghitung ranking, running totals, dan moving average memakai $setWindowFields ala window functions di SQL.

Di episode 9 kalian membangun fondasi aggregation pipeline. Tapi aplikasi nyata butuh lebih dari sekedar group dan sum: menggabungkan data lintas collection, menghitung peringkat sekaligus ringkasan dalam satu query, dan membuat histogram distribusi harga.
Episode 10 menjawabnya dengan stage-stage lanjutan: $lookup untuk join antar collection, $facet untuk banyak sub-pipeline paralel, $bucket untuk histogram, dan $setWindowFields untuk window functions ala SQL. Mari mulai.
$lookup adalah analog LEFT JOIN di SQL: ia mengambil field dari dokumen collection utama, mencocokkannya dengan field di collection lain, dan menempelkan hasilnya sebagai array ke dokumen utama.
db.orders.aggregate([
{ $lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
} }
])Bacanya: dari collection users, cocokkan userId di dokumen order dengan _id di users, lalu simpan hasilnya ke field user (selalu array, meski satu dokumen). Setiap order kini membawa data user lengkap — tanpa dua query terpisah. Karena as selalu array, rapikan dengan $unwind bila kalian butuh bentuk flat.
Basic lookup hanya bisa mencocokkan dengan kesetaraan sederhana. Untuk kondisi lebih kompleks — seperti menggabungkan berdasarkan dua field atau filter tambahan — gunakan pipeline syntax di let + pipeline:
db.orders.aggregate([
{ $lookup: {
from: "items",
let: { orderId: "$_id" },
pipeline: [
{ $match: {
$expr: { $eq: ["$orderId", "$$orderId"] },
qty: { $gte: 2 }
} }
],
as: "bulkItems"
} }
])Sintaks let mendefinisikan variabel lokal ($$orderId) yang dipakai di sub-pipeline — memberi kontrol penuh atas kondisi join.
$facet menjalankan beberapa sub-pipeline secara paralel dalam satu pass atas data — hasilnya dikumpulkan dalam satu dokumen. Ini revolusioner untuk halaman dashboard yang membutuhkan beberapa ringkasan berbeda dari data yang sama, tanpa query berulang:
db.orders.aggregate([
{ $match: { status: "paid" } },
{ $facet: {
"totalRevenue": [
{ $group: { _id: null, total: { $sum: "$total" } } }
],
"byCategory": [
{ $group: { _id: "$category", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
],
"recentOrders": [
{ $sort: { createdAt: -1 } },
{ $limit: 5 },
{ $project: { _id: 1, total: 1, status: 1 } }
]
} }
])Hasilnya satu dokumen dengan tiga key: totalRevenue, byCategory, dan recentOrders. Bayangkan berapa banyak query terpisah yang dihemat — dan konsistensinya sempurna karena semua facet membaca snapshot yang sama.
$bucket mengelompokkan data ke dalam rentang (bucket) yang kalian tentukan. Sempurna untuk membuat histogram harga:
db.products.aggregate([
{ $bucket: {
groupBy: "$price",
boundaries: [0, 100000, 250000, 500000, 1000000],
default: "lainnya",
output: {
count: { $sum: 1 },
totalValue: { $sum: "$price" }
}
} }
])Batas [0, 100000, 250000, 500000, 1000000] membentuk empat bucket: 0–100rb, 100rb–250rb, 250rb–500rb, 500rb–1jt. Produk di luar rentang masuk ke bucket default.
Sementara $bucketAuto membagi data ke jumlah bucket yang diinginkan dengan batas yang dihitung otomatis agar merata:
db.products.aggregate([
{ $bucketAuto: {
groupBy: "$price",
buckets: 5,
output: {
count: { $sum: 1 },
average: { $avg: "$price" }
}
} }
])MongoDB menentukan batas tiap bucket sendiri sehingga distribusinya merata — berguna saat rentang alami data belum diketahui.
$setWindowFields menghadirkan kemampuan window functions ala SQL (ROW_NUMBER, SUM ... OVER, moving average) ke MongoDB. Ia menghitung nilai berdasarkan window — jendela dokumen di sekitar dokumen saat ini — tanpa mengelompokkan seluruh data.
db.products.aggregate([
{ $setWindowFields: {
partitionBy: "$category",
sortBy: { soldCount: -1 },
output: {
rank: { $rank: {} },
denseRank: { $denseRank: {} }
}
} },
{ $match: { rank: { $lte: 3 } } },
{ $project: { name: 1, category: 1, soldCount: 1, rank: 1 } }
])partitionBy memecah data per kategori, sortBy menentukan urutan, lalu $rank memberi nomor urut. Beda $rank dan $denseRank: $rank melompat setelah nilai seri (1, 2, 2, 4), $denseRank tidak (1, 2, 2, 3). Hasilnya tiga produk terlaris per kategori.
Window functions juga bisa menjumlahkan kumulatif dan menghitung rata-rata bergerak — jantung analisis time-series:
db.sales.aggregate([
{ $sort: { date: 1 } },
{ $setWindowFields: {
sortBy: { date: 1 },
output: {
runningTotal: {
$sum: "$amount",
window: { documents: ["unbounded", "current"] }
},
movingAvg7: {
$avg: "$amount",
window: { documents: [-6, 0] }
}
}
} }
])runningTotal menjumlahkan dari dokumen pertama (unbounded) sampai saat ini (current); movingAvg7 merata-rata 7 hari terakhir untuk menghaluskan fluktuasi harian.Gabungan $facet dan $setWindowFields menghasilkan laporan level enterprise dalam satu query: satu facet untuk ringkasan agregat, satu lagi untuk detail dengan ranking dan running totals. Semua facet berbagi snapshot yang sama, jadi hasil dijamin konsisten.
Yang perlu diwaspadai: $lookup yang tidak di-index sangat mahal — ia menjadi nested loop di server. Pastikan field foreignField punya index, dan hindari $lookup raksasa di setiap request; untuk workload read-heavy, pertimbangkan snapshot/denormalisasi dari episode 7. Gunakan $lookup sebagai pelengkap, bukan tulang punggung desain.
Pada episode 10 ini kalian telah mendorong aggregation ke level profesional: $lookup basic dan correlated subquery untuk menggabungkan collection ala LEFT JOIN, $facet untuk menjalankan beberapa sub-pipeline paralel dalam satu query — seperti dashboard lengkap dalam satu panggilan, $bucket dan $bucketAuto untuk histogram dengan batas manual atau otomatis, serta $setWindowFields untuk window functions: ranking dengan $rank/$denseRank, running total, dan moving average.
Inti yang harus dibawa pulang:
$lookup menggabungkan collection; selalu pastikan foreignField ter-index.$lookup memberi kondisi join yang fleksibel.$facet menggabungkan banyak ringkasan dalam satu query konsisten.$bucket untuk histogram manual; $bucketAuto untuk distribusi otomatis.$setWindowFields memberi ranking, running totals, dan moving average.Di episode 11 berikutnya kita bicara cara menemukan data dengan kata kunci: Full-Text Search & Atlas Search. Kalian akan membuat text index dan memakai query $text dengan scoring relevansi, lalu mengenal MongoDB Atlas Search berbasis Lucene yang menghadirkan fuzzy search, autocomplete, dan faceted search. Sampai jumpa di episode 11!