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:

js
use latihan_agregasi
db.dropDatabase()

Output yang Dihasilkan:

text
switched to db latihan_agregasi
{ ok: 1, dropped: 'latihan_agregasi' }
js
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:

text
{
  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:

js
db.buku.aggregate([
  { $group: {
      _id: null,
      totalStok: { $sum: "$stok" },
      rataHarga: { $avg: "$harga" },
      hargaTermurah: { $min: "$harga" },
      hargaTermahal: { $max: "$harga" }
  } }
])

Output yang Dihasilkan:

text
[
  {
    _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:

js
db.buku.aggregate([ { $group: { _id: "$kategori", totalStok: { $sum: "$stok" }, jumlahBuku: { $sum: 1 } } } ])

Output yang Dihasilkan:

text
[
  { _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:

js
db.buku.aggregate([
  { $group: { _id: { kategori: "$kategori", tahun: "$tahun_terbit" }, totalStok: { $sum: "$stok" } } },
  { $sort: { _id: 1 } }
])

Output yang Dihasilkan:

text
[
  { _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 #

AkumulatorFungsi
{ $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:

js
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:

text
[
  {
    _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:

js
db.buku.aggregate([ { $group: { _id: "$kategori", jumlah: { $count: {} } } } ])

Output yang Dihasilkan:

text
[
  { _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:

js
db.buku.aggregate([ { $group: { _id: "$kategori", daftarBuku: { $push: { judul: "$judul", stok: "$stok" } } } } ])

Output yang Dihasilkan:

text
[
  {
    _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: null untuk 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.
  • $push mencatat duplikat dan bisa merakit sub-document; $addToSet menyimpan nilai unik.
  • $first/$last hanya bermakna setelah $sort; urutan keluaran $group sendiri tidak dijamin — tutup dengan $sort.
  • { $count: {} } setara { $sum: 1 } untuk pola hitung per kelompok.