Field array ada di mana-mana di katalog buku: tag untuk klasifikasi, ulasan berisi document pembeli. $unwind memecah array menjadi document terpisah, tetapi tidak semua kebutuhan cocok dengan pendekatan itu — kadang kita cukup menghitung, menyaring, atau mengubah array di tempatnya tanpa menggandakan document. Operator array di aggregation mengerjakan semuanya sebagai expression: dibaca, difilter, dipetakan, dirangkum, lalu diurutkan. Chapter ini mengolah tag dan ulasan dari katalog toko buku.
Peta materi Chapter ini:
- Operator pembaca:
$size,$arrayElemAt,$slice,$first/$last,$in. - Operator pengubah:
$filter,$map,$reduce,$concatArrays,$sortArray. - Jebakan
$sizepada field yang hilang dan penangkalnya.
Peta Operator #
| Operator | Peran |
|---|---|
$size | jumlah element |
$arrayElemAt | element pada index tertentu (negatif dihitung dari belakang) |
$slice | potongan n element awal, atau -n element akhir |
$first / $last | element pertama / terakhir |
$in | uji keanggotaan: apakah sebuah nilai ada di array |
$filter | salinan array yang lolos syarat |
$map | salinan array dengan tiap element ditransformasi |
$reduce | rangkum seluruh array menjadi satu nilai |
$concatArrays | gabungan beberapa array |
$sortArray | salinan array dalam urutan pilihan (MongoDB 5.2+) |
Siapkan data uji standar Section ini:
use latihan_agregasi2
db.dropDatabase()
db.buku.insertMany([
{ judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "fiksi", terjual: 120, tanggal_terbit: ISODate("2017-02-01T00:00:00Z"), tag: ["fiksi", "sastra", "penghargaan"], ulasan: [ { pengulas: "Budi", bintang: 5 }, { pengulas: "Siti", bintang: 4 } ] },
{ judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "fiksi", terjual: 250, tanggal_terbit: ISODate("2014-09-02T00:00:00Z"), tag: ["fiksi", "remaja", "fantasi"], ulasan: [ { pengulas: "Dewi", bintang: 5 }, { pengulas: "Budi", bintang: 4 }, { pengulas: "Siti", bintang: 5 } ] },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "nonfiksi", terjual: 180, tanggal_terbit: ISODate("2018-07-01T00:00:00Z"), tag: ["filsafat", "pengembangan diri"], ulasan: [ { pengulas: "Siti", bintang: 5 } ] },
{ judul: "Atomic Habits", penulis: "James Clear", harga: 115000, kategori: "nonfiksi", terjual: 310, tanggal_terbit: ISODate("2018-10-16T00:00:00Z"), tag: ["pengembangan diri", "terjemahan"], ulasan: [ { pengulas: "Budi", bintang: 5 }, { pengulas: "Dewi", bintang: 4 } ] },
{ judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi", terjual: 120, tanggal_terbit: ISODate("2016-06-15T00:00:00Z"), tag: ["fiksi", "remaja"], ulasan: [ { pengulas: "Dewi", bintang: 4 } ] },
{ judul: "Bumi Manusia", penulis: "Pramoedya A. Toer", harga: 105000, kategori: "sastra", terjual: 150, tanggal_terbit: ISODate("1980-05-25T00:00:00Z"), tag: ["sastra", "sejarah"], ulasan: [ { pengulas: "Budi", bintang: 5 }, { pengulas: "Siti", bintang: 5 }, { pengulas: "Dewi", bintang: 4 } ] },
{ judul: "Hujan Bulan Juni", penulis: "Sapardi Djoko Damono", harga: 65000, kategori: "puisi", terjual: 75, tanggal_terbit: ISODate("1994-06-10T00:00:00Z"), tag: ["puisi", "sastra"] },
{ judul: "Ensiklopedia Sains Populer", penulis: "Tim Sains", harga: 250000, kategori: "referensi", terjual: 40, tanggal_terbit: ISODate("2020-03-05T00:00:00Z"), tag: ["referensi", "sains"], ulasan: [ { pengulas: "Dewi", bintang: 4 } ] }
])Output yang Dihasilkan:
switched to db latihan_agregasi2
{ ok: 1, dropped: 'latihan_agregasi2' }
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3a653b8deb34e44fd68c0'),
'1': ObjectId('6ad3a653b8deb34e44fd68c1'),
'2': ObjectId('6ad3a653b8deb34e44fd68c2'),
'3': ObjectId('6ad3a653b8deb34e44fd68c3'),
'4': ObjectId('6ad3a653b8deb34e44fd68c4'),
'5': ObjectId('6ad3a653b8deb34e44fd68c5'),
'6': ObjectId('6ad3a653b8deb34e44fd68c6'),
'7': ObjectId('6ad3a653b8deb34e44fd68c7')
}
}Membaca Array: $size, $arrayElemAt, $slice, $first, $in #
Lima operator ini membaca array tanpa mengubahnya — semuanya dipakai sekaligus pada buku Bumi:
db.buku.aggregate([
{ $match: { judul: "Bumi" } },
{ $project: { _id: 0, judul: 1, tag: 1,
jumlahTag: { $size: "$tag" },
tagPertama: { $arrayElemAt: ["$tag", 0] },
tagTerakhir: { $arrayElemAt: ["$tag", -1] },
duaTagAwal: { $slice: ["$tag", 2] },
ulasanPertama: { $first: "$ulasan" },
ulasanTerakhir: { $last: "$ulasan" },
bertandaSastra: { $in: ["sastra", "$tag"] } } }
])Output yang Dihasilkan:
[
{
judul: 'Bumi',
tag: [ 'fiksi', 'remaja', 'fantasi' ],
jumlahTag: 3,
tagPertama: 'fiksi',
tagTerakhir: 'fantasi',
duaTagAwal: [ 'fiksi', 'remaja' ],
ulasanPertama: { pengulas: 'Dewi', bintang: 5 },
ulasanTerakhir: { pengulas: 'Siti', bintang: 5 },
bertandaSastra: false
}
]Bedah hasilnya: $size menghitung tiga tag; $arrayElemAt membaca index 0 (fiksi) dan -1 (fantasi) — index negatif dihitung dari belakang; $slice mengambil dua element awal; $first/$last mengambil ulasan paling awal dan paling akhir tanpa berhitung index. Terakhir, $in menguji keanggotaan: tag Bumi tidak memuat "sastra" sehingga hasilnya false.
Menyaring dan Memetakan: $filter dan $map #
Dua operator ini adalah pasangan kerja utama. $filter menyimpan element yang lolos syarat cond, $map menulis ulang tiap element:
db.buku.aggregate([
{ $match: { judul: "Bumi Manusia" } },
{ $project: { _id: 0, judul: 1,
ulasanPositif: { $filter: { input: "$ulasan", as: "u", cond: { $gte: ["$$u.bintang", 5] } } },
daftarPengulas: { $map: { input: "$ulasan", in: "$$this.pengulas" } } } }
])Output yang Dihasilkan:
[
{
judul: 'Bumi Manusia',
ulasanPositif: [ { pengulas: 'Budi', bintang: 5 }, { pengulas: 'Siti', bintang: 5 } ],
daftarPengulas: [ 'Budi', 'Siti', 'Dewi' ]
}
]Sintaks keduanya sejalan: input adalah array sumber, lalu cond/in berisi expression per element. Nama variable per element ditentukan as ("$$u"); tanpa as, nama bawaannya $$this — itulah yang dipakai $map di atas. Hasilnya: hanya ulasan bintang lima yang lolos filter, dan array document ulasan berubah menjadi array nama pengulas.
Merangkum: $reduce dan $concatArrays #
$reduce melipat array menjadi satu nilai dengan initialValue dan expression in yang menggabungkan $$value (hasil sementara) dengan $$this (element berjalan). $concatArrays menyambung beberapa array:
db.buku.aggregate([
{ $match: { judul: "Bumi Manusia" } },
{ $project: { _id: 0, judul: 1, tag: 1,
totalBintang: { $reduce: { input: "$ulasan", initialValue: 0, in: { $add: ["$$value", "$$this.bintang"] } } },
semuaLabel: { $concatArrays: ["$tag", ["$kategori"]] } } }
])Output yang Dihasilkan:
[
{
judul: 'Bumi Manusia',
tag: [ 'sastra', 'sejarah' ],
totalBintang: 14,
semuaLabel: [ 'sastra', 'sejarah', 'sastra' ]
}
]Tiga ulasan bintang 5, 5, dan 4 dirangkum $reduce menjadi totalBintang: 14 — pola yang sama bisa mengalikan, menggabungkan string, atau membangun document. Sementara itu semuaLabel menyambung array tag dengan array satu element ["$kategori"], menghasilkan daftar label gabungan untuk pencarian.
Mengurutkan: $sortArray #
Urutan array di database tidak berarti; $sortArray menghasilkan salinan terurut tanpa mengubah data asli:
db.buku.aggregate([
{ $match: { judul: "Bumi" } },
{ $project: { _id: 0, judul: 1, ulasan: 1,
ulasanTerurut: { $sortArray: { input: "$ulasan", sortBy: { bintang: 1 } } } } }
])Output yang Dihasilkan:
[
{
judul: 'Bumi',
ulasan: [
{ pengulas: 'Dewi', bintang: 5 },
{ pengulas: 'Budi', bintang: 4 },
{ pengulas: 'Siti', bintang: 5 }
],
ulasanTerurut: [
{ pengulas: 'Budi', bintang: 4 },
{ pengulas: 'Dewi', bintang: 5 },
{ pengulas: 'Siti', bintang: 5 }
]
}
]Field ulasan asli tetap berurut [5, 4, 5] sesuai penyimpanan, sedangkan ulasanTerurut menyusun ulasan dari bintang terendah ke tertinggi. sortBy menerima satu atau beberapa field dengan arah 1/-1, sama seperti $sort.
Jebakan $size pada Field Hilang #
Hujan Bulan Juni tidak memiliki field ulasan sama sekali. Operator array menuntut input bertipe array — field hilang ditolak:
db.buku.aggregate([ { $match: { judul: "Hujan Bulan Juni" } }, { $project: { jumlahUlasan: { $size: "$ulasan" } } } ])
db.buku.aggregate([ { $match: { judul: "Hujan Bulan Juni" } }, { $project: { _id: 0, judul: 1, jumlahUlasan: { $size: { $ifNull: ["$ulasan", []] } } } } ])Output yang Dihasilkan:
Uncaught
MongoServerError[Location17124]: Executor error during aggregate command on namespace: latihan_agregasi2.buku :: caused by :: The argument to $size must be an array, but was of type: missing
[ { judul: 'Hujan Bulan Juni', jumlahUlasan: 0 } ]Baris pertama gagal dengan error The argument to $size must be an array, but was of type: missing. Baris kedua menunjukkan penangkalnya — pola $ifNull: ["$ulasan", []] dari Chapter Conditional Expressions menyediakan array kosong sebagai cadangan, sehingga jumlahUlasan terhitung 0. Pola ini berlaku untuk semua operator array: data yang tidak selalu terisi wajib diberi cadangan.
Rangkuman #
$size,$arrayElemAt,$slice,$first/$last,$inmembaca array; index negatif menghitung dari belakang.$filtermenyaring dengancond,$mapmenulis ulang tiap element; variable element diatur lewatasatau$$thisbawaan.$reducemelipat array menjadi satu nilai dari$$valuedan$$this,$concatArraysmenyambung array,$sortArraymenghasilkan salinan terurut.- Field hilang ditolak semua operator array — lindungi dengan
$ifNull: ["$field", []].
Angka dan array sudah beres. Chapter berikutnya menyentuh tipe yang paling dilihat pengguna akhir: string — merangkai judul, membersihkan spasi, membangun slug, hingga pencarian pola dengan $regexMatch.