Tahap $group mengumpulkan dokumen yang bernilai sama pada satu kunci menjadi satu kelompok, lalu merangkum setiap kelompok dengan akumulator (accumulator) seperti $sum dan $avg. Mekanismenya setara dengan klausa GROUP BY pada SQL, yaitu banyak baris masuk dan sedikit baris ringkasan keluar. Topik ini membahas penentuan kunci kelompok, pemakaian akumulator, pengelompokan seluruh koleksi, serta pengumpulan nilai per kelompok dengan $push. Data contohnya berupa koleksi nilai pada basis data sekolah yang memuat nilai akademik siswa.
Mengelompokkan dokumen dengan $group#
Tahap $group menerima satu dokumen spesifikasi yang wajib memuat kunci _id. Nilai _id tersebut adalah ekspresi kunci kelompok, yaitu rumus yang dievaluasi pada setiap dokumen masukan. Server membentuk satu kelompok untuk setiap nilai unik yang dihasilkan. Akumulator ditulis sebagai field lain pada spesifikasi yang sama, misalnya jumlah: { $sum: 1 }, dan dihitung terpisah untuk setiap kelompok. Keluaran $group berupa satu dokumen per kelompok, sehingga jumlah dokumennya menyusut menjadi banyaknya nilai kunci yang unik.
Jalankan mongosh, pilih basis data kerja, lalu isi koleksi nilai dengan 24 dokumen berikut. Setiap dokumen memuat satu nilai ujian untuk satu mata pelajaran milik satu siswa.
use sekolahswitched to db sekolahdb.nilai.insertMany([
{ nama: "Ali Rahman", kelas: "8A", mapel: "Matematika", nilai: 85 },
{ nama: "Ali Rahman", kelas: "8A", mapel: "Bahasa Indonesia", nilai: 72 },
{ nama: "Ali Rahman", kelas: "8A", mapel: "IPA", nilai: 88 },
{ nama: "Budi Santoso", kelas: "8A", mapel: "Matematika", nilai: 75 },
{ nama: "Budi Santoso", kelas: "8A", mapel: "Bahasa Indonesia", nilai: 85 },
{ nama: "Budi Santoso", kelas: "8A", mapel: "IPA", nilai: 70 },
{ nama: "Citra Melati", kelas: "8A", mapel: "Matematika", nilai: 90 },
{ nama: "Citra Melati", kelas: "8A", mapel: "Bahasa Indonesia", nilai: 88 },
{ nama: "Citra Melati", kelas: "8A", mapel: "IPA", nilai: 92 },
{ nama: "Dewi Lestari", kelas: "8B", mapel: "Matematika", nilai: 70 },
{ nama: "Dewi Lestari", kelas: "8B", mapel: "Bahasa Indonesia", nilai: 75 },
{ nama: "Dewi Lestari", kelas: "8B", mapel: "IPA", nilai: 76 },
{ nama: "Eka Pratama", kelas: "8B", mapel: "Matematika", nilai: 88 },
{ nama: "Eka Pratama", kelas: "8B", mapel: "Bahasa Indonesia", nilai: 70 },
{ nama: "Eka Pratama", kelas: "8B", mapel: "IPA", nilai: 84 },
{ nama: "Fitri Handayani", kelas: "8B", mapel: "Matematika", nilai: 95 },
{ nama: "Fitri Handayani", kelas: "8B", mapel: "Bahasa Indonesia", nilai: 90 },
{ nama: "Fitri Handayani", kelas: "8B", mapel: "IPA", nilai: 86 },
{ nama: "Gilang Saputra", kelas: "8C", mapel: "Matematika", nilai: 65 },
{ nama: "Gilang Saputra", kelas: "8C", mapel: "Bahasa Indonesia", nilai: 72 },
{ nama: "Gilang Saputra", kelas: "8C", mapel: "IPA", nilai: 74 },
{ nama: "Hana Puspita", kelas: "8C", mapel: "Matematika", nilai: 72 },
{ nama: "Hana Puspita", kelas: "8C", mapel: "Bahasa Indonesia", nilai: 72 },
{ nama: "Hana Puspita", kelas: "8C", mapel: "IPA", nilai: 86 }
]){
acknowledged: true,
insertedIds: {
'0': ObjectId('6ae893727626d65ee08de666'),
'1': ObjectId('6ae893727626d65ee08de667'),
'2': ObjectId('6ae893727626d65ee08de668'),
'3': ObjectId('6ae893727626d65ee08de669'),
'4': ObjectId('6ae893727626d65ee08de66a'),
'5': ObjectId('6ae893727626d65ee08de66b'),
'6': ObjectId('6ae893727626d65ee08de66c'),
'7': ObjectId('6ae893727626d65ee08de66d'),
'8': ObjectId('6ae893727626d65ee08de66e'),
'9': ObjectId('6ae893727626d65ee08de66f'),
'10': ObjectId('6ae893727626d65ee08de670'),
'11': ObjectId('6ae893727626d65ee08de671'),
'12': ObjectId('6ae893727626d65ee08de672'),
'13': ObjectId('6ae893727626d65ee08de673'),
'14': ObjectId('6ae893727626d65ee08de674'),
'15': ObjectId('6ae893727626d65ee08de675'),
'16': ObjectId('6ae893727626d65ee08de676'),
'17': ObjectId('6ae893727626d65ee08de677'),
'18': ObjectId('6ae893727626d65ee08de678'),
'19': ObjectId('6ae893727626d65ee08de679'),
'20': ObjectId('6ae893727626d65ee08de67a'),
'21': ObjectId('6ae893727626d65ee08de67b'),
'22': ObjectId('6ae893727626d65ee08de67c'),
'23': ObjectId('6ae893727626d65ee08de67d')
}
}Pipeline berikut menghitung rata-rata nilai per mata pelajaran dengan $avg, lalu mengurutkan hasilnya menurut _id.
db.nilai.aggregate([
{ $group: { _id: "$mapel", rataRata: { $avg: "$nilai" } } },
{ $sort: { _id: 1 } }
])[
{ _id: 'Bahasa Indonesia', rataRata: 78 },
{ _id: 'IPA', rataRata: 82 },
{ _id: 'Matematika', rataRata: 80 }
]Perintah ini setara dengan SELECT mapel, AVG(nilai) FROM nilai GROUP BY mapel pada SQL. Delapan dokumen pada setiap mata pelajaran diringkas menjadi satu dokumen ringkasan, sehingga seluruh koleksi menjadi tiga dokumen. Nilai _id: "$mapel" menyatakan bahwa kunci kelompoknya adalah field mapel dari dokumen masukan, dan tanda $ wajib ditulis agar yang dibaca adalah nilai field tersebut.
Akumulator untuk merangkum kelompok#
Satu $group boleh memuat beberapa akumulator sekaligus, dan masing-masing menghitung ringkasannya sendiri untuk setiap kelompok. Akumulator $sum menjumlahkan nilai, $avg menghitung rata-rata, sedangkan $min dan $max mengambil nilai terendah dan tertinggi. Kombinasi tersebut menghasilkan ringkasan statistik yang lazim dibutuhkan pada laporan.
db.nilai.aggregate([
{ $group: {
_id: "$mapel",
jumlahNilai: { $sum: 1 },
rataRata: { $avg: "$nilai" },
terendah: { $min: "$nilai" },
tertinggi: { $max: "$nilai" }
} },
{ $sort: { _id: 1 } }
])[
{
_id: 'Bahasa Indonesia',
jumlahNilai: 8,
rataRata: 78,
terendah: 70,
tertinggi: 90
},
{
_id: 'IPA',
jumlahNilai: 8,
rataRata: 82,
terendah: 70,
tertinggi: 92
},
{
_id: 'Matematika',
jumlahNilai: 8,
rataRata: 80,
terendah: 65,
tertinggi: 95
}
]Kunci kelompok tidak terbatas pada satu field, dan field lain seperti kelas dapat menjadi kunci pengganti. Pipeline berikut menghitung jumlah dokumen nilai per kelas dengan $sum bernilai 1, yang padanannya pada SQL adalah COUNT(*).
db.nilai.aggregate([
{ $group: { _id: "$kelas", jumlahNilai: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])[
{ _id: '8A', jumlahNilai: 9 },
{ _id: '8B', jumlahNilai: 9 },
{ _id: '8C', jumlahNilai: 6 }
]Kelas 8A dan 8B masing-masing memuat sembilan dokumen karena tiga siswanya diuji pada tiga mata pelajaran, sedangkan kelas 8C memuat enam dokumen dari dua siswa. Angka-angka tersebut mudah diverifikasi langsung dari data contohnya.
Mengelompokkan seluruh koleksi#
Nilai _id berupa null membuat seluruh dokumen masukan masuk ke satu kelompok tunggal. Bentuk ini dipakai untuk statistik yang mencakup seluruh koleksi tanpa pembagian kategori, misalnya total dan rata-rata keseluruhan. Nilai null dipilih sebagai penanda karena tidak mungkin bertabrakan dengan nilai field yang nyata pada kelompok biasa.
db.nilai.aggregate([
{ $group: {
_id: null,
jumlahNilai: { $sum: 1 },
totalNilai: { $sum: "$nilai" },
rataRataKeseluruhan: { $avg: "$nilai" }
} }
])[
{
_id: null,
jumlahNilai: 24,
totalNilai: 1920,
rataRataKeseluruhan: 80
}
]Keluarannya satu dokumen karena hanya ada satu kelompok. Total 1920 diperoleh dari penjumlahan seluruh 24 nilai, dan rata-rata keseluruhannya 80. Perhitungan semacam ini menggantikan perintah agregat tanpa GROUP BY pada SQL, misalnya SELECT AVG(nilai) FROM nilai.
Bentuk _id: null juga berlaku ketika ringkasannya memuat campuran jenis akumulator, sehingga total, rata-rata, nilai terendah, dan nilai tertinggi dapat dihitung dalam satu tahap yang sama. Apabila ringkasan keseluruhan dan ringkasan per kategori dibutuhkan bersamaan, masing-masing dihitung oleh $group tersendiri dalam dua pipeline yang terpisah. Setiap pipeline tetap sederhana, sedangkan hasilnya digabungkan pada lapisan aplikasi bila perlu.
Mengumpulkan nilai dengan $push#
Akumulator $push menambahkan satu nilai ke array kelompoknya untuk setiap dokumen yang masuk, sehingga isi satu kelompok dapat dikumpulkan menjadi satu array. Pipeline berikut mengumpulkan nilai ujian setiap siswa menjadi satu array daftarNilai.
db.nilai.aggregate([
{ $group: { _id: "$nama", daftarNilai: { $push: "$nilai" } } },
{ $sort: { _id: 1 } }
])[
{ _id: 'Ali Rahman', daftarNilai: [ 85, 72, 88 ] },
{ _id: 'Budi Santoso', daftarNilai: [ 75, 85, 70 ] },
{ _id: 'Citra Melati', daftarNilai: [ 90, 88, 92 ] },
{ _id: 'Dewi Lestari', daftarNilai: [ 70, 75, 76 ] },
{ _id: 'Eka Pratama', daftarNilai: [ 88, 70, 84 ] },
{ _id: 'Fitri Handayani', daftarNilai: [ 95, 90, 86 ] },
{ _id: 'Gilang Saputra', daftarNilai: [ 65, 72, 74 ] },
{ _id: 'Hana Puspita', daftarNilai: [ 72, 72, 86 ] }
]Setiap siswa kini diwakili satu dokumen yang arraynya memuat tiga nilai sesuai urutan dokumennya pada koleksi. Pola ini berguna untuk membentuk ulang data dari satu dokumen per nilai ujian menjadi satu dokumen per siswa yang memuat seluruh nilainya.
Akumulator serupa bernama $addToSet mengumpulkan nilai unik saja, sehingga nilai yang sama tidak ditulis dua kali pada arraynya. Pemilihan di antara keduanya bergantung pada kebutuhan, yaitu $push untuk deretan lengkap dan $addToSet untuk daftar kategori. Keduanya menerima satu ekspresi nilai per dokumen masukan sebagaimana akumulator lainnya.
Kesalahan umum#
Kesalahan yang paling umum pada $group adalah melupakan kunci _id pada spesifikasinya. Pipeline berikut hanya mendefinisikan akumulator tanpa _id, dan server menolaknya saat eksekusi.
db.nilai.aggregate([
{ $group: { jumlahNilai: { $sum: 1 } } }
])Uncaught
MongoServerError[Location15955]: a group specification must include an _idPesan error tersebut muncul karena _id adalah bagian wajib dari spesifikasi $group, sekalipun yang diinginkan hanyalah satu kelompok untuk seluruh koleksi. Perbaikannya adalah menuliskan _id: null untuk pengelompokan keseluruhan atau _id: "$<field>" untuk pengelompokan menurut field tertentu.
Kesalahan kedua adalah menuliskan nama field kunci tanpa tanda $, misalnya _id: "mapel". Server tidak membaca fieldnya, melainkan menganggap setiap dokumen memiliki nilai kunci berupa teks mapel, sehingga seluruh dokumen masuk ke satu kelompok tanpa pesan error apa pun. Hasil yang tampak berhasil tetapi salah semacam ini lebih sulit ditemukan daripada error yang ditolak server, sehingga bentuk keluaran _id perlu diperiksa setiap kali $group ditulis.
Ringkasan#
$groupmembentuk satu kelompok untuk setiap nilai unik pada_iddan merangkum tiap kelompok dengan akumulator.- Akumulator
$sum,$avg,$min, dan$maxmenghitung ringkasan numerik, sedangkan$pushmengumpulkan nilai kelompok menjadi array. _id: nullmenghasilkan satu kelompok untuk seluruh koleksi sebagai padanan agregat tanpaGROUP BYpada SQL.- Kunci
_idwajib ada pada spesifikasi$group, dan field path-nya ditulis dengan awalan$.
Referensi#
- $group — MongoDB Manual
— Dokumentasi tahap pengelompokan beserta aturan kunci
_id. - $sum — MongoDB Manual
— Dokumentasi akumulator penjumlahan pada
$group. - $avg — MongoDB Manual
— Dokumentasi akumulator rata-rata pada
$group. - $push — MongoDB Manual — Dokumentasi akumulator pengumpul nilai menjadi array.
- $addToSet — MongoDB Manual
— Dokumentasi akumulator pengumpul nilai unik pada
$group.