$project sangat teliti: field yang tidak disebut hilang dari hasil. Pola itu cocok untuk report final, tetapi menyulitkan di tengah pipeline — kita ingin menambah satu field hasil hitungan tanpa harus menuliskan ulang seluruh daftar field yang ada. Tahap $addFields, bersama nama aliasnya $set, menjawab kebutuhan itu: menambah atau menimpa field sambil meneruskan seluruh field lain apa adanya. Chapter ini membedah kebiasaan keduanya, dari field sederhana sampai path bersarang.

Menambah Field Tanpa Membuang yang Lain #

Kita pakai data toko buku yang sama, lalu menambahkan dua field hitungan pada satu buku agar perubahannya mudah diamati:

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 }
}
js
db.buku.aggregate([
  { $match: { judul: "Bumi" } },
  { $addFields: {
      totalTerjual: { $sum: "$terjual.jumlah" },
      nilaiStok: { $multiply: ["$harga", "$stok"] }
  } }
])

Output yang Dihasilkan:

text
[
  {
    _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 }
    ],
    totalTerjual: 63,
    nilaiStok: 1780000
  }
]

Bandingkan bentuk hasilnya dengan $project: seluruh field asli masih utuh — dari judul sampai array terjual — dan dua field baru menempel di bagian akhir. Mari kita bedah ekspresinya:

  1. totalTerjual: { $sum: "$terjual.jumlah" }: path "$terjual.jumlah" menembus array dan mengumpulkan seluruh nilai jumlah menjadi satu array nilai, lalu $sum menjumlahkannya menjadi 63.
  2. nilaiStok: { $multiply: ["$harga", "$stok"] }: mengalikan harga dengan stok — nilai persediaan buku itu di rak.

Kombinasi $match + $addFields seperti ini adalah pola “hitung turunan lalu periksa” yang sangat sering dipakai: field hasil hitungan bisa langsung dipakai tahap berikutnya, misalnya $match kedua yang menyaring buku dengan nilaiStok di atas ambang tertentu.

$set: Nama Lain untuk $addFields #

Sejak MongoDB 4.2, $set tersedia sebagai alias $addFields — perilakunya identik sepenuhnya:

js
db.buku.aggregate([
  { $match: { judul: "Bumi" } },
  { $set: {
      totalTerjual: { $sum: "$terjual.jumlah" },
      nilaiStok: { $multiply: ["$harga", "$stok"] }
  } }
])

Output yang Dihasilkan:

text
[
  {
    _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 }
    ],
    totalTerjual: 63,
    nilaiStok: 1780000
  }
]

Hasilnya persis sama. Nama $set sengaja dihadirkan agar pipeline terasa konsisten dengan update operator $set pada updateOne()/updateMany() yang sudah kita kenal di Section Operasi CRUD — keduanya sama-sama berarti “set field ke nilai baru”. Pilih salah satu dan gunakan secara konsisten dalam satu codebase.

Path Bertitik Membuat Document Bersarang #

Sama seperti $project, nama field tujuan boleh berupa path bertitik — bedanya $addFields tidak membuang field lain saat membangun struktur baru:

js
db.buku.aggregate([
  { $match: { judul: "Bumi" } },
  { $addFields: {
      "ringkasan.totalTerjual": { $sum: "$terjual.jumlah" },
      "ringkasan.nilaiStok": { $multiply: ["$harga", "$stok"] }
  } }
])

Output yang Dihasilkan:

text
[
  {
    _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 }
    ],
    ringkasan: { totalTerjual: 63, nilaiStok: 1780000 }
  }
]

Dua path bertitik dikumpulkan menjadi sub-document ringkasan yang berisi keduanya. Semua field asli tetap berada di tempatnya. Pola ini pas untuk menyiapkan hasil akhir yang rapi tanpa merestrukturisasi document di tengah jalan.

Menimpa Nilai Field yang Sudah Ada #

Jika nama field tujuan sudah ada di document, nilai lamanya diganti dengan hasil ekspresi — tidak ada yang lain yang berubah:

js
db.buku.aggregate([
  { $match: { judul: "Bumi" } },
  { $set: { harga: { $multiply: ["$harga", 0.9] } } },
  { $project: { _id: 0, judul: 1, harga: 1 } }
])

Output yang Dihasilkan:

text
[ { judul: 'Bumi', harga: 80100 } ]

Field harga lama (89000) tertimpa hasil hitungan 80100, sementara judul dan seluruh field lain tetap mengalir. Kombinasi $project di akhir hanya memangkas tampilan agar perubahan ini terlihat jelas — tanpa $project tersebut, hasilnya tetap memuat semua field dengan harga yang sudah baru.

$project, $addFields, dan $set #

Aspek$project$addFields / $set
Field yang tidak disebutDibuangDiteruskan apa adanya
Field baru dari ekspresi✔✔
Menimpa field yang adaHanya bila disebut✔ — memang tujuannya
Cocok untukMerakit bentuk hasil akhirMenambah turunan di tengah pipeline

Keduanya bukan pesaing melainkan pasangan: $addFields/$set bekerja di tengah jalan untuk menghitung, $project menuntaskan bentuk di ujung jalan.

Rangkuman #

  • $addFields menambah atau menimpa field tanpa membuang field yang sudah ada — kebalikan dari sifat selektif $project.
  • $set adalah alias $addFields sejak MongoDB 4.2; hasil keduanya identik.
  • Ekspresi seperti { $sum: "$terjual.jumlah" } bisa menembus array bersarang untuk menghasilkan angka agregat per document.
  • Path bertitik membangun sub-document baru; nama field yang sudah ada akan tertimpa nilainya — hanya di aliran pipeline, bukan di collection.