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 $size pada field yang hilang dan penangkalnya.

Peta Operator #

OperatorPeran
$sizejumlah element
$arrayElemAtelement pada index tertentu (negatif dihitung dari belakang)
$slicepotongan n element awal, atau -n element akhir
$first / $lastelement pertama / terakhir
$inuji keanggotaan: apakah sebuah nilai ada di array
$filtersalinan array yang lolos syarat
$mapsalinan array dengan tiap element ditransformasi
$reducerangkum seluruh array menjadi satu nilai
$concatArraysgabungan beberapa array
$sortArraysalinan array dalam urutan pilihan (MongoDB 5.2+)

Siapkan data uji standar Section ini:

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

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

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

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

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

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

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

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

js
db.buku.aggregate([
  { $match: { judul: "Bumi" } },
  { $project: { _id: 0, judul: 1, ulasan: 1,
      ulasanTerurut: { $sortArray: { input: "$ulasan", sortBy: { bintang: 1 } } } } }
])

Output yang Dihasilkan:

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

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

text
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, $in membaca array; index negatif menghitung dari belakang.
  • $filter menyaring dengan cond, $map menulis ulang tiap element; variable element diatur lewat as atau $$this bawaan.
  • $reduce melipat array menjadi satu nilai dari $$value dan $$this, $concatArrays menyambung array, $sortArray menghasilkan 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.