Inilah tahap yang menjadi alasan banyak orang mempelajari aggregation: $group melipat ratusan document menjadi beberapa baris jawaban — total penjualan per kategori, rata-rata harga per penerbit, jumlah buku per tag. Cara kerjanya sederhana secara konsep: document yang memiliki nilai kunci sama dikelompokkan, lalu setiap kelompok direduksi menjadi satu document hasil dengan bantuan akumulator. Bab ini membedah tiga bagian itu: kunci grup, akumulator, dan pola-pola yang paling sering dipakai.
Kunci Grup pada _id #
Setiap $group wajib punya field _id yang menentukan kunci pengelompokan. Ada tiga bentuk kunci, dan ketiganya menjawab tiga jenis pertanyaan berbeda. Kita mulai dari data yang sama:
use latihan_agregasi
db.dropDatabase()Output yang Dihasilkan:
switched to db latihan_agregasi
{ ok: 1, dropped: 'latihan_agregasi' }db.buku.insertMany([
{ _id: 1, judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "Fiksi", tag: ["sastra", "novel"], stok: 12, tahun_terbit: 2017, terjual: [ { bulan: "Januari", jumlah: 14 }, { bulan: "Februari", jumlah: 9 }, { bulan: "Maret", jumlah: 11 } ] },
{ _id: 2, judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "Fiksi", tag: ["novel", "fantasi"], stok: 20, tahun_terbit: 2014, terjual: [ { bulan: "Januari", jumlah: 21 }, { bulan: "Februari", jumlah: 17 }, { bulan: "Maret", jumlah: 25 } ] },
{ _id: 3, judul: "Supernova: Ksatria, Puteri, dan Bintang Jatuh", penulis: "Dee Lestari", harga: 95000, kategori: "Fiksi", tag: ["novel", "sains"], stok: 6, tahun_terbit: 2001, terjual: [ { bulan: "Januari", jumlah: 7 }, { bulan: "Februari", jumlah: 5 } ] },
{ _id: 4, judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "Nonfiksi", tag: ["filsafat", "pengembangan diri"], stok: 8, tahun_terbit: 2018, terjual: [ { bulan: "Januari", jumlah: 12 }, { bulan: "Februari", jumlah: 15 }, { bulan: "Maret", jumlah: 10 } ] },
{ _id: 5, judul: "Atomic Habits", penulis: "James Clear", harga: 115000, kategori: "Nonfiksi", tag: ["pengembangan diri", "kebiasaan"], stok: 15, tahun_terbit: 2018, terjual: [ { bulan: "Januari", jumlah: 18 }, { bulan: "Februari", jumlah: 22 }, { bulan: "Maret", jumlah: 16 } ] },
{ _id: 6, judul: "Kamus Bahasa Indonesia", penulis: "Badan Bahasa", harga: 55000, kategori: "Referensi", tag: ["referensi", "bahasa"], stok: 30, tahun_terbit: 2016, terjual: [] }
])Output yang Dihasilkan:
{
acknowledged: true,
insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
}Bentuk pertama, _id: null, memasukkan seluruh document ke dalam satu kelompok — dipakai saat kita hanya butuh angka agregat keseluruhan:
db.buku.aggregate([
{ $group: {
_id: null,
totalStok: { $sum: "$stok" },
rataHarga: { $avg: "$harga" },
hargaTermurah: { $min: "$harga" },
hargaTermahal: { $max: "$harga" }
} }
])Output yang Dihasilkan:
[
{
_id: null,
totalStok: 91,
rataHarga: 90166.66666666667,
hargaTermurah: 55000,
hargaTermahal: 115000
}
]Empat akumulator bekerja sekaligus di sini: $sum menjumlahkan, $avg merata-ratakan, $min dan $max mencari ujung rentang. Bentuk kedua, kunci satu field, memecah hasil menjadi satu baris per nilai kunci:
db.buku.aggregate([ { $group: { _id: "$kategori", totalStok: { $sum: "$stok" }, jumlahBuku: { $sum: 1 } } } ])Output yang Dihasilkan:
[
{ _id: 'Nonfiksi', totalStok: 23, jumlahBuku: 2 },
{ _id: 'Fiksi', totalStok: 38, jumlahBuku: 3 },
{ _id: 'Referensi', totalStok: 30, jumlahBuku: 1 }
]_id: "$kategori" membaca nilai kategori dari tiap document dan menjadikannya identitas kelompok. Pola { $sum: 1 } adalah cara klasik menghitung anggota — tiap document menyumbang angka 1. Bentuk ketiga, kunci komposit, menggabungkan beberapa field menjadi kunci gabungan:
db.buku.aggregate([
{ $group: { _id: { kategori: "$kategori", tahun: "$tahun_terbit" }, totalStok: { $sum: "$stok" } } },
{ $sort: { _id: 1 } }
])Output yang Dihasilkan:
[
{ _id: { kategori: 'Fiksi', tahun: 2001 }, totalStok: 6 },
{ _id: { kategori: 'Fiksi', tahun: 2014 }, totalStok: 20 },
{ _id: { kategori: 'Fiksi', tahun: 2017 }, totalStok: 12 },
{ _id: { kategori: 'Nonfiksi', tahun: 2018 }, totalStok: 23 },
{ _id: { kategori: 'Referensi', tahun: 2016 }, totalStok: 30 }
]Enam document terlipat menjadi lima kelompok: pasangan Fiksi+2017, Fiksi+2014, Fiksi+2001, Referensi+2016, dan satu kelompok Nonfiksi+2018 yang menampung dua buku sekaligus. Perhatikan juga $sort: { _id: 1 } setelahnya — urutan keluaran $group tidak dijamin oleh MongoDB, jadi tahap $sort membuat hasil deterministik dan mudah dibaca.
Akumulator yang Paling Sering Dipakai #
| Akumulator | Fungsi |
|---|---|
{ $sum: "$field" } | Menjumlahkan nilai (angka) atau menghitung ($sum: 1) |
{ $avg: "$field" } | Rata-rata nilai angka |
{ $min: "$field" } / { $max: "$field" } | Nilai terkecil / terbesar menurut urutan BSON |
{ $count: {} } | Menghitung anggota kelompok — setara { $sum: 1 } |
{ $push: "$field" } | Mengumpulkan nilai menjadi array (duplikat tetap tercatat) |
{ $addToSet: "$field" } | Mengumpulkan nilai unik menjadi array |
{ $first: "$field" } / { $last: "$field" } | Nilai dari document pertama / terakhir kelompok |
Empat akumulator terakhir paling mudah dipahami lewat satu contoh. Karena $first/$last membaca document yang datang lebih dulu dan lebih akhir, kita awali dengan $sort agar maknanya pasti:
db.buku.aggregate([
{ $sort: { tahun_terbit: 1 } },
{ $group: {
_id: "$tahun_terbit",
daftarKategori: { $push: "$kategori" },
setKategori: { $addToSet: "$kategori" },
pertama: { $first: "$judul" },
terakhir: { $last: "$judul" }
} },
{ $sort: { _id: 1 } }
])Output yang Dihasilkan:
[
{
_id: 2001,
daftarKategori: [ 'Fiksi' ],
setKategori: [ 'Fiksi' ],
pertama: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh',
terakhir: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh'
},
{
_id: 2014,
daftarKategori: [ 'Fiksi' ],
setKategori: [ 'Fiksi' ],
pertama: 'Bumi',
terakhir: 'Bumi'
},
{
_id: 2016,
daftarKategori: [ 'Referensi' ],
setKategori: [ 'Referensi' ],
pertama: 'Kamus Bahasa Indonesia',
terakhir: 'Kamus Bahasa Indonesia'
},
{
_id: 2017,
daftarKategori: [ 'Fiksi' ],
setKategori: [ 'Fiksi' ],
pertama: 'Laut Bercerita',
terakhir: 'Laut Bercerita'
},
{
_id: 2018,
daftarKategori: [ 'Nonfiksi', 'Nonfiksi' ],
setKategori: [ 'Nonfiksi' ],
pertama: 'Filosofi Teras',
terakhir: 'Atomic Habits'
}
]Kelompok tahun 2018 memperlihatkan seluruh perbedaannya sekaligus. Dua buku Nonfiksi terbit tahun itu, sehingga $push mencatat ['Nonfiksi', 'Nonfiksi'] — duplikat utuh — sementara $addToSet cukup ['Nonfiksi'] karena hanya menyimpan nilai unik. $first mengambil Filosofi Teras dan $last mengambil Atomic Habits, sesuai urutan yang ditetapkan $sort sebelum $group — tanpa $sort, document “pertama” dan “terakhir” tidak terdefinisi.
Pola Klasik: Menghitung per Kategori #
Pola yang paling sering muncul di dunia nyata adalah “hitung jumlah per kelompok” — berapa buku per kategori. Akumulator $count hadir khusus untuk itu:
db.buku.aggregate([ { $group: { _id: "$kategori", jumlah: { $count: {} } } } ])Output yang Dihasilkan:
[
{ _id: 'Referensi', jumlah: 1 },
{ _id: 'Fiksi', jumlah: 3 },
{ _id: 'Nonfiksi', jumlah: 2 }
]{ $count: {} } tidak menerima parameter apa pun dan fungsinya setara persis dengan { $sum: 1 } — pilih sesuai selera, hasilnya sama. Versi yang lebih kaya menyertakan daftar anggota berkat $push yang menerima bukan hanya path, tetapi juga document hasil rakitan:
db.buku.aggregate([ { $group: { _id: "$kategori", daftarBuku: { $push: { judul: "$judul", stok: "$stok" } } } } ])Output yang Dihasilkan:
[
{
_id: 'Nonfiksi',
daftarBuku: [
{ judul: 'Filosofi Teras', stok: 8 },
{ judul: 'Atomic Habits', stok: 15 }
]
},
{
_id: 'Fiksi',
daftarBuku: [
{ judul: 'Laut Bercerita', stok: 12 },
{ judul: 'Bumi', stok: 20 },
{ judul: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh', stok: 6 }
]
},
{
_id: 'Referensi',
daftarBuku: [ { judul: 'Kamus Bahasa Indonesia', stok: 30 } ]
}
]$push: { judul: "$judul", stok: "$stok" } merakit sub-document dari dua field sebelum menyimpannya ke array. Hasilnya: satu document per kategori berisi daftar isinya — bentuk yang siap ditampilkan langsung di antarmuka pengguna.
Rangkuman #
- Kunci grup ditentukan
_id:nulluntuk satu kelompok global,"$field"untuk satu field,{ a: "$x", b: "$y" }untuk kunci komposit. - Akumulator mereduksi kelompok:
$sum,$avg,$min,$max,$count,$push,$addToSet,$first,$last. $pushmencatat duplikat dan bisa merakit sub-document;$addToSetmenyimpan nilai unik.$first/$lasthanya bermakna setelah$sort; urutan keluaran$groupsendiri tidak dijamin — tutup dengan$sort.{ $count: {} }setara{ $sum: 1 }untuk pola hitung per kelompok.