Pipeline agregasi mengolah dokumen dari satu koleksi melalui rangkaian tahap yang saling bersambung. Topik ini membahas cara kerja pipeline, bentuk perintah aggregate, aliran dokumen antartahap, dan pengaruh urutan tahap terhadap hasil akhir. Data contohnya berupa koleksi siswa pada basis data sekolah, dan seluruh perintah diketik di dalam sesi mongosh.
Aliran dokumen pada pipeline agregasi#
Pipeline agregasi (aggregation pipeline) adalah kerangka pengolahan data yang menjalankan serangkaian tahap (stage) secara berurutan. Setiap tahap menerima aliran dokumen dari tahap sebelumnya, melakukan satu transformasi tertentu, lalu mengirimkan hasilnya ke tahap berikutnya. Dokumen keluaran dari tahap terakhir menjadi hasil akhir agregasi. Bentuk dokumen boleh berubah di sepanjang jalur tersebut, misalnya field yang disederhanakan, nilai baru yang dihitung, atau jumlah dokumen yang berkurang.
Setiap tahap ditulis sebagai sebuah dokumen yang kuncinya merupakan nama operator agregasi berawalan $, misalnya $match untuk penyaringan dan $group untuk pengelompokan. Seluruh tahap disusun dalam satu array yang dikirim ke metode aggregate pada koleksi yang dituju. Urutan elemen array itulah yang menentukan urutan eksekusi, sehingga dua pipeline yang memuat tahap sama tetapi urutannya berbeda dapat menghasilkan keluaran yang berbeda.
Dibandingkan find, kemampuan pipeline tidak berhenti pada penyaringan dokumen. Metode find mengembalikan dokumen apa adanya sesuai filternya, sedangkan pipeline dapat membentuk ulang struktur dokumen dan meringkas banyak dokumen menjadi sedikit dokumen ringkasan. Kemampuan transformasi inilah yang menjadikan agregasi alat utama untuk pelaporan dan statistik pada MongoDB.
Menjalankan agregasi satu tahap#
Pipeline paling sederhana hanya memuat satu tahap, dan tahap tersebut lazimnya $match yang menyaring dokumen dengan syarat query biasa. Jalankan mongosh, pilih basis data kerja, lalu isi koleksi siswa dengan delapan dokumen berikut.
use sekolahswitched to db sekolahdb.siswa.insertMany([
{ nama: "Ali Rahman", kelas: "8A", umur: 14, hobi: ["catur", "futsal"] },
{ nama: "Budi Santoso", kelas: "8A", umur: 13, hobi: ["futsal"] },
{ nama: "Citra Melati", kelas: "8A", umur: 14, hobi: ["seni tari", "catur"] },
{ nama: "Dewi Lestari", kelas: "8B", umur: 13, hobi: ["vokal"] },
{ nama: "Eka Pratama", kelas: "8B", umur: 14, hobi: ["futsal", "renang"] },
{ nama: "Fitri Handayani", kelas: "8B", umur: 15, hobi: ["seni tari"] },
{ nama: "Gilang Saputra", kelas: "8C", umur: 14, hobi: ["renang", "catur", "futsal"] },
{ nama: "Hana Puspita", kelas: "8C", umur: 13, hobi: ["vokal", "seni tari"] }
]){
acknowledged: true,
insertedIds: {
'0': ObjectId('6ae893741d8127236a8de666'),
'1': ObjectId('6ae893741d8127236a8de667'),
'2': ObjectId('6ae893741d8127236a8de668'),
'3': ObjectId('6ae893741d8127236a8de669'),
'4': ObjectId('6ae893741d8127236a8de66a'),
'5': ObjectId('6ae893741d8127236a8de66b'),
'6': ObjectId('6ae893741d8127236a8de66c'),
'7': ObjectId('6ae893741d8127236a8de66d')
}
}Keluaran penyisipan memuat pemetaan posisi elemen array ke ObjectId dokumen yang dibuat. Delapan ObjectId itu menandakan delapan dokumen telah tersimpan. Field hobi pada setiap dokumen berupa array dengan panjang yang berbeda, mulai dari satu hingga tiga elemen. Koleksi delapan dokumen inilah yang menjadi bahan seluruh pipeline pada topik ini.
Saring koleksi dengan satu tahap $match untuk mengambil siswa kelas 8A.
db.siswa.aggregate([
{ $match: { kelas: "8A" } }
])[
{
_id: ObjectId('6ae893741d8127236a8de666'),
nama: 'Ali Rahman',
kelas: '8A',
umur: 14,
hobi: [ 'catur', 'futsal' ]
},
{
_id: ObjectId('6ae893741d8127236a8de667'),
nama: 'Budi Santoso',
kelas: '8A',
umur: 13,
hobi: [ 'futsal' ]
},
{
_id: ObjectId('6ae893741d8127236a8de668'),
nama: 'Citra Melati',
kelas: '8A',
umur: 14,
hobi: [ 'seni tari', 'catur' ]
}
]Keluarannya berupa tiga dokumen yang sama dengan hasil db.siswa.find({ kelas: "8A" }). Satu tahap $match memang setara dengan penyaringan find, sehingga perbedaannya baru terasa ketika tahap lain disusun setelahnya. Hasil pemanggilan aggregate berupa kursor dokumen, yaitu penunjuk ke himpunan hasil query. mongosh mencetak isi kursor tersebut secara otomatis ke layar.
Meringkas dokumen dengan dua tahap#
Transformasi utama pipeline baru tampak ketika lebih dari satu tahap disusun. Pipeline berikut mengelompokkan siswa menurut kelasnya dengan $group, lalu mengurutkan kelompok hasilnya dengan $sort. Tahap $group menghitung jumlahSiswa melalui akumulator (accumulator) $sum, yaitu ekspresi yang menjumlahkan nilai dari setiap dokumen dalam kelompok. Nilai 1 berarti setiap dokumen menyumbang satu hitungan.
db.siswa.aggregate([
{ $group: { _id: "$kelas", jumlahSiswa: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])[
{ _id: '8A', jumlahSiswa: 3 },
{ _id: '8B', jumlahSiswa: 3 },
{ _id: '8C', jumlahSiswa: 2 }
]Perhatikan perubahan bentuknya. Delapan dokumen siswa yang awalnya memuat empat field telah berubah menjadi tiga dokumen ringkasan yang hanya memuat _id dan jumlahSiswa. Field _id pada keluaran $group menyimpan nilai kunci kelompoknya, sedangkan field-field asli seperti nama dan hobi tidak dibawa ke keluaran karena tidak diakumulasikan.
Pipeline ini setara dengan pernyataan SELECT kelas, COUNT(*) FROM siswa GROUP BY kelas pada SQL. Nilai _id pada keluaran mengambil peran kolom kelas, sedangkan jumlahSiswa mengambil peran kolom hitungannya. Persamaan bentuk semacam ini memudahkan pembaca yang sudah terbiasa dengan basis data relasional.
Urutan tahap menentukan hasil#
Dua pipeline yang tahapnya sama dapat menghasilkan keluaran yang berbeda apabila urutannya ditukar. Pipeline pertama menyaring siswa kelas 8C terlebih dahulu, baru mengambil satu dokumen dengan $limit. Pemanggilan .toArray() dipakai agar hasilnya tercetak sebagai array, sehingga kasus tanpa hasil juga terlihat jelas.
db.siswa.aggregate([
{ $match: { kelas: "8C" } },
{ $limit: 1 }
]).toArray()[
{
_id: ObjectId('6ae893741d8127236a8de66c'),
nama: 'Gilang Saputra',
kelas: '8C',
umur: 14,
hobi: [ 'renang', 'catur', 'futsal' ]
}
]Pipeline kedua memakai dua tahap yang sama dengan urutan terbalik.
db.siswa.aggregate([
{ $limit: 1 },
{ $match: { kelas: "8C" } }
]).toArray()[]Pipeline pertama mengembalikan satu dokumen karena koleksi masih memuat dua siswa kelas 8C ketika $limit bekerja. Pipeline kedua mengembalikan array kosong karena $limit di awal sudah menyisakan satu dokumen pertama koleksi, yaitu Ali Rahman dari kelas 8A. Syarat kelas 8C tidak menemukan apa pun pada dokumen yang tersisa. Contoh ini memperlihatkan bahwa setiap tahap hanya melihat dokumen keluaran tahap sebelumnya.
Kesalahan umum#
Kesalahan yang paling sering terjadi ketika pipeline baru ditulis adalah salah mengetik nama tahapnya. Nama tahap harus dieja persis seperti pada daftar operator agregasi, termasuk tanda $ di awalnya, karena server mengenali tahap dari kunci pada dokumen tahap tersebut. Pipeline berikut memuat $macth sebagai salah ketik dari $match, dan server menolak seluruh pipeline sebelum satu dokumen pun diproses.
db.siswa.aggregate([
{ $macth: { kelas: "8A" } }
])Uncaught
MongoServerError[Location40324]: Unrecognized pipeline stage name: '$macth'Pesan error Unrecognized pipeline stage name menyebut nama tahap yang tidak dikenal, sehingga tahap yang salah ketik mudah ditemukan. Perbaikannya adalah menuliskan ulang nama tahap sesuai ejaan yang benar. Kesalahan serupa juga terjadi apabila nama ekspresi agregasi dipakai sebagai nama tahap, misalnya $size yang memang bukan tahap pipeline. Karena itu, nama setiap tahap perlu diperiksa ulang sebelum pipeline dijalankan.
Kesalahan lain yang ringan adalah menjalankan aggregate pada koleksi yang salah karena basis data kerjanya belum dipindahkan. Perintah tetap berjalan tanpa pesan error dan mengembalikan hasil kosong, persis seperti koleksi yang memang tidak memuat dokumen yang cocok. Karena itu, nilai switched to db pada awal sesi dan nama koleksi pada setiap perintah perlu dipastikan sebelum kesimpulan tentang hasil kosong ditarik.
Ringkasan#
- Pipeline agregasi menjalankan tahap-tahap secara berurutan, dan setiap tahap mengubah aliran dokumen dari tahap sebelumnya sebelum meneruskannya.
- Tahap ditulis sebagai dokumen beroperator
$di dalam array yang dikirim kedb.<koleksi>.aggregate(). $matchmenyaring dokumen sepertifind, sedangkan$groupdan$sortmengubah bentuk serta jumlah dokumen.- Urutan tahap menentukan hasil karena setiap tahap hanya bekerja pada keluaran tahap sebelumnya.
Referensi#
- Aggregation Pipeline — MongoDB Manual — Dokumentasi konsep pipeline agregasi beserta aliran data antartahap.
- db.collection.aggregate() — MongoDB Manual — Dokumentasi metode agregasi beserta bentuk parameter tahapnya.
- $match — MongoDB Manual — Dokumentasi tahap penyaringan dokumen pada pipeline.