Pipeline adalah metafora yang tepat: document mengalir melewati serangkaian tahap pengolahan, dan setiap tahap mengubah aliran itu sebelum menyerahkannya ke tahap berikutnya. Chapter ini membedah anatomi aggregation pipeline — bentuk sintaksnya, cara menjalankannya di mongosh, dan mengapa urutan tahap menentukan makna hasil. Setelah ini, setiap Chapter di Section ini tinggal memperkenalkan satu tahap baru yang kita sambungkan ke pengetahuan yang sudah ada.

Document Mengalir Melewati Tahap #

Sebuah pipeline didefinisikan sebagai array berisi document; setiap document mewakili satu tahap (stage) yang bentuknya { $namaTahap: <operasi> }. MongoDB mengeksekusi tahap secara berurutan: seluruh document keluaran tahap pertama menjadi input tahap kedua, dan seterusnya, sampai tahap terakhir menghasilkan cursor untuk kita baca.

Diagram berikut memperlihatkan aliran sebuah pipeline yang akan kita jalankan sebentar lagi — perhatikan bagaimana jumlah document menyusut di tiap tahap:

Baca diagram ini dari kiri ke kanan: enam document masuk, tahap $match menyaring lima document, $group melipatnya menjadi dua hasil per kategori, dan $sort mengurutkan hasil tersebut. Bagian teks di dalam setiap kotak adalah operasinya; angka di baris berikutnya adalah jumlah document yang keluar dari tahap itu.

Menjalankan Pipeline di mongosh #

Kita pakai data toko buku yang sama dengan Chapter sebelumnya:

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 }
}

Berikut pipeline dari diagram di atas dalam bentuk aslinya:

js
db.buku.aggregate([
  { $match: { stok: { $lte: 20 } } },
  { $group: { _id: "$kategori", totalStok: { $sum: "$stok" }, rataHarga: { $avg: "$harga" } } },
  { $sort: { totalStok: -1 } }
])

Output yang Dihasilkan:

text
[
  { _id: 'Fiksi', totalStok: 38, rataHarga: 94000 },
  { _id: 'Nonfiksi', totalStok: 23, rataHarga: 102000 }
]

Mari kita bedah alurnya tahap per tahap:

  1. { $match: { stok: { $lte: 20 } } }: menyaring document dengan stok paling banyak 20. Kamus Bahasa Indonesia (stok 30) tersaring keluar, menyisakan lima document.
  2. { $group: ... }: melipat lima document menjadi dua — satu per nilai kategori. totalStok menjumlahkan stok anggota kelompok; rataHarga menghitung rata-rata harga.
  3. { $sort: { totalStok: -1 } }: mengurutkan dua hasil berdasarkan totalStok menurun.

Hasil akhirnya berupa cursor. mongosh secara otomatis mengiterasi cursor dan menampilkan hingga 20 document pertama — perilaku yang sama seperti find(). Kita juga bisa mengambil alih iterasi itu secara eksplisit:

js
const hasil = db.buku.aggregate([ { $group: { _id: "$kategori" } } ]).toArray()
hasil.length
db.buku.aggregate([ { $group: { _id: "$kategori" } } ]).itcount()

Output yang Dihasilkan:

text
3
3

Dua perintah terakhir memperlihatkan sisi cursor yang selama ini tersembunyi: .toArray() mengubah seluruh hasil menjadi array JavaScript yang bisa kita inspeksi (.length), sedangkan .itcount() menghabiskan iterator hanya untuk menghitung. Cursor ini juga yang memungkinkan aplikasi membaca hasil aggregation secara bertahap tanpa memuat semuanya ke memori.

Urutan Tahap Menentukan Makna #

Karena setiap tahap menerima keluaran tahap sebelumnya, menukar posisi dua tahap bisa mengubah hasil — bukan hanya performanya. Perhatikan dua pipeline berikut; keduanya “menyaring buku dengan harga minimal 90000”, tetapi hanya yang pertama yang berhasil:

js
db.buku.aggregate([
  { $match: { harga: { $gte: 90000 } } },
  { $project: { _id: 0, judul: 1, harga: 1 } }
])
db.buku.aggregate([
  { $project: { _id: 0, judul: 1, hargaAsli: "$harga" } },
  { $match: { harga: { $gte: 90000 } } }
]).toArray()

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita', harga: 98000 },
  {
    judul: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh',
    harga: 95000
  },
  { judul: 'Atomic Habits', harga: 115000 }
]
[]

Pipeline kedua mengembalikan array kosong. Penyebabnya: $project pada tahap pertama mengubah nama field harga menjadi hargaAsli, sehingga saat $match dijalankan, field harga sudah tidak ada di document yang mengalir. Aturan praktisnya: $match sedini mungkin, $project selambat mungkin — penyaringan awal memperkecil pekerjaan tahap-tahap setelahnya dan tetap bisa memakai index, seperti yang akan kita buktikan di Chapter $match.

find() vs aggregate() #

Untuk sekadar menyaring dan memilih field, kedua perintah ini menghasilkan keluaran yang identik:

js
db.buku.find({ kategori: "Fiksi" }, { _id: 0, judul: 1, harga: 1 })
db.buku.aggregate([
  { $match: { kategori: "Fiksi" } },
  { $project: { _id: 0, judul: 1, harga: 1 } }
])

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita', harga: 98000 },
  { judul: 'Bumi', harga: 89000 },
  {
    judul: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh',
    harga: 95000
  }
]
[
  { judul: 'Laut Bercerita', harga: 98000 },
  { judul: 'Bumi', harga: 89000 },
  {
    judul: 'Supernova: Ksatria, Puteri, dan Bintang Jatuh',
    harga: 95000
  }
]

Jadi kapan memakai yang mana? Gunakan find() ketika kebutuhannya persis seperti bentuknya — satu filter plus projection. Gunakan aggregate() begitu kebutuhan menyentuh wilayah yang tidak bisa dijangkau find():

Kebutuhanfind()aggregate()
Filter dengan query operator✔✔ ($match)
Projection sederhana✔✔ ($project)
Menghitung total/rata-rata/jumlah✖✔ ($group)
Mengubah bentuk document secara bebas✖✔ ($project, $addFields, $unwind)
Join antar-collection✖✔ ($lookup)

Rangkuman #

  • Pipeline adalah array tahap { $tahap: operasi } yang dieksekusi berurutan; keluaran satu tahap menjadi input tahap berikutnya.
  • Jumlah document bisa menyusut atau bertambah di sepanjang pipeline, tergantung tahap yang dilewati.
  • mongosh mengiterasi cursor hasil secara otomatis (maksimum 20 document); .toArray() dan .itcount() memberi kendali eksplisit.
  • Urutan tahap menentukan makna: $match setelah $project bekerja pada field yang sudah berganti nama.
  • find() cukup untuk filter plus projection; aggregate() membuka pengelompokan, transformasi bentuk, dan join.