$addFields dan ekspresi

Menambahkan field terhitung dan logika kondisional ke dokumen pipeline dengan $addFields beserta ekspresinya.

Daftar isi

Tahap $addFields menambahkan field terhitung ke setiap dokumen pada pipeline tanpa menghapus field yang sudah ada. Field baru itu dihitung dari ekspresi (expression), yaitu rumus yang membaca field, menerapkan operator aritmetika, string, atau logika kondisional, lalu menghasilkan satu nilai. Topik ini membahas penambahan field aritmetika, logika kondisional dengan $cond dan $switch, serta ekspresi $ifNull dan $concat untuk data yang belum lengkap. Data contohnya berupa koleksi pegawai pada basis data pegawai yang memuat gaji pokok, tunjangan, dan tahun masuk.

Menambahkan field terhitung dengan $addFields#

Tahap $addFields menerima satu dokumen spesifikasi yang setiap fieldnya menamai field baru beserta ekspresi penghitungnya. Setiap dokumen keluaran memuat seluruh field masukan ditambah field-field baru tersebut, sehingga tahap ini tidak mengubah jumlah dokumen dan tidak membuang data asli. Field masukan dibaca melalui field path yang diawali tanda $, misalnya "$gajiPokok", dan operator seperti $add serta $subtract menghitung nilainya. Ekspresi yang sama juga dapat ditulis pada $project, tetapi $addFields menjadi pilihan bila field aslinya tetap dibutuhkan.

Jalankan mongosh, pilih basis data kerja, lalu isi koleksi pegawai dengan enam dokumen berikut.

mongosh
use pegawai
text Keluaran
switched to db pegawai
mongosh
db.pegawai.insertMany([
  { nama: "Ali Rahman", kodeDepartemen: "IT", gajiPokok: 8000000, tunjangan: 1500000, tahunMasuk: 2015 },
  { nama: "Budi Santoso", kodeDepartemen: "IT", gajiPokok: 7000000, tunjangan: 1000000, tahunMasuk: 2022 },
  { nama: "Citra Melati", kodeDepartemen: "SDM", gajiPokok: 6000000, tunjangan: 900000, tahunMasuk: 2023 },
  { nama: "Dewi Lestari", kodeDepartemen: "SDM", gajiPokok: 6500000, tunjangan: 800000, tahunMasuk: 2020 },
  { nama: "Eka Pratama", kodeDepartemen: "KEU", gajiPokok: 7500000, tunjangan: 1200000, tahunMasuk: 2018 },
  { nama: "Fitri Handayani", kodeDepartemen: "KEU", gajiPokok: 5000000, tunjangan: 700000, tahunMasuk: 2024 }
])
text Keluaran
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ae898f398143b39188de666'),
    '1': ObjectId('6ae898f398143b39188de667'),
    '2': ObjectId('6ae898f398143b39188de668'),
    '3': ObjectId('6ae898f398143b39188de669'),
    '4': ObjectId('6ae898f398143b39188de66a'),
    '5': ObjectId('6ae898f398143b39188de66b')
  }
}

Pipeline berikut menambahkan field totalGaji berupa penjumlahan gaji pokok dan tunjangan, serta field masaKerja berupa selisih tahun acuan dengan tahun masuk. Tahun acuan 2025 ditulis langsung pada ekspresi agar contohnya dapat direproduksi tanpa bergantung pada tanggal berjalan.

mongosh
db.pegawai.aggregate([
  { $addFields: {
      totalGaji: { $add: ["$gajiPokok", "$tunjangan"] },
      masaKerja: { $subtract: [2025, "$tahunMasuk"] }
  } },
  { $project: { _id: 0, nama: 1, totalGaji: 1, masaKerja: 1 } },
  { $sort: { nama: 1 } }
])
text Keluaran
[
  { nama: 'Ali Rahman', totalGaji: 9500000, masaKerja: 10 },
  { nama: 'Budi Santoso', totalGaji: 8000000, masaKerja: 3 },
  { nama: 'Citra Melati', totalGaji: 6900000, masaKerja: 2 },
  { nama: 'Dewi Lestari', totalGaji: 7300000, masaKerja: 5 },
  { nama: 'Eka Pratama', totalGaji: 8700000, masaKerja: 7 },
  { nama: 'Fitri Handayani', totalGaji: 5700000, masaKerja: 1 }
]

Keluarannya enam dokumen yang masing-masing memuat kedua field baru tanpa kehilangan field aslinya. Angka-angkanya mudah diverifikasi dari data tanaman, misalnya total gaji Ali Rahman berupa 8000000 ditambah 1500000 dan masa kerjanya berupa 2025 dikurangi 2015. Field yang baru ditambahkan langsung dapat dipakai oleh tahap-tahap berikutnya, misalnya $sort, $match, maupun $group, sehingga $addFields biasanya diletakkan sebelum tahap yang membutuhkannya.

Logika kondisional dengan $cond dan $switch#

Operator $cond memilih satu dari dua nilai menurut sebuah syarat, dan operator $switch memilih satu dari banyak cabang. Keduanya memungkinkan field terhitung mengikuti aturan klasifikasi, misalnya menandai kategori gaji atau jenjang masa kerja pada dokumen yang sama. Pipeline berikut merangkai empat tahap $addFields, yaitu dua field dasar, kategori gaji dengan $cond, dan jenjang kepegawaian dengan $switch.

mongosh
db.pegawai.aggregate([
  { $addFields: { totalGaji: { $add: ["$gajiPokok", "$tunjangan"] } } },
  { $addFields: { masaKerja: { $subtract: [2025, "$tahunMasuk"] } } },
  { $addFields: {
      kategori: { $cond: { if: { $gte: ["$totalGaji", 8000000] }, then: "tinggi", else: "standar" } }
  } },
  { $addFields: {
      jenjang: { $switch: {
          branches: [
            { case: { $gte: ["$masaKerja", 6] }, then: "senior" },
            { case: { $gte: ["$masaKerja", 3] }, then: "menengah" }
          ],
          default: "junior"
      } }
  } },
  { $project: { _id: 0, nama: 1, kategori: 1, jenjang: 1 } },
  { $sort: { nama: 1 } }
])
text Keluaran
[
  { nama: 'Ali Rahman', kategori: 'tinggi', jenjang: 'senior' },
  { nama: 'Budi Santoso', kategori: 'tinggi', jenjang: 'menengah' },
  { nama: 'Citra Melati', kategori: 'standar', jenjang: 'junior' },
  { nama: 'Dewi Lestari', kategori: 'standar', jenjang: 'menengah' },
  { nama: 'Eka Pratama', kategori: 'tinggi', jenjang: 'senior' },
  { nama: 'Fitri Handayani', kategori: 'standar', jenjang: 'junior' }
]

Bentuk $cond pada contoh ini memakai tiga kunci if, then, dan else agar mudah dibaca. Syaratnya memakai $gte terhadap totalGaji, dan nilai then beserta else boleh berupa teks, angka, maupun ekspresi lain. Operator $switch menilai cabang-cabangnya secara berurutan dari atas ke bawah, lalu memakai nilai default apabila tidak ada syarat yang terpenuhi. Masa kerja Budi Santoso bernilai tiga tahun sehingga masuk jenjang menengah, sedangkan masa kerja Citra Melati yang lebih pendek jatuh ke cabang default.

Syarat pada $cond maupun cabang $switch dapat memakai operator pembanding lain seperti $lt dan $lte, serta operator logika $and dan $or apabila syaratnya lebih dari satu. Hindari cabang yang saling tumpang tindih pada $switch, karena cabang pertama yang cocok langsung dipilih tanpa menilai cabang sesudahnya. Susun cabang dari syarat yang paling ketat agar urutan penilaiannya mudah diprediksi.

Ekspresi untuk data yang belum lengkap#

Data nyata sering memuat field yang tidak terisi, dan ekspresi perlu menanganinya agar hasil pipeline tetap terduga. Tambahkan satu pegawai berikut yang tidak memuat field tunjangan, lalu amati perlakuan $ifNull dan $concat terhadapnya.

mongosh
db.pegawai.insertOne({ nama: "Gilang Saputra", kodeDepartemen: "IT", gajiPokok: 6500000, tahunMasuk: 2021 })
text Keluaran
{
  acknowledged: true,
  insertedId: ObjectId('6ae898f398143b39188de66c')
}

Pipeline berikut membentuk field label dengan $concat dan menggantikan field tunjangan yang tidak ada dengan angka 0 melalui $ifNull. Field tunjangan pada contoh ini ditulis ulang dengan nama yang sama, yang menunjukkan bahwa $addFields dapat menggantikan field lama selain menambah yang baru.

mongosh
db.pegawai.aggregate([
  { $addFields: {
      label: { $concat: ["$nama", " (", "$kodeDepartemen", ")"] },
      tunjangan: { $ifNull: ["$tunjangan", 0] }
  } },
  { $project: { _id: 0, label: 1, tunjangan: 1 } },
  { $sort: { label: 1 } }
])
text Keluaran
[
  { tunjangan: 1500000, label: 'Ali Rahman (IT)' },
  { tunjangan: 1000000, label: 'Budi Santoso (IT)' },
  { tunjangan: 900000, label: 'Citra Melati (SDM)' },
  { tunjangan: 800000, label: 'Dewi Lestari (SDM)' },
  { tunjangan: 1200000, label: 'Eka Pratama (KEU)' },
  { tunjangan: 700000, label: 'Fitri Handayani (KEU)' },
  { label: 'Gilang Saputra (IT)', tunjangan: 0 }
]

Operator $ifNull mengembalikan argumen pertama apabila ada dan bukan null, sedangkan argumen keduanya dipakai sebagai pengganti apabila field tidak ada. Gilang Saputra tampil dengan tunjangan: 0 berkat perlakuan tersebut. Operator $concat menyambung teks-teks argumennya menjadi satu string, tetapi hasilnya null apabila ada argumen yang bernilai null atau tidak ada. Karena itu, field yang dirangkai sebaiknya dijamin keberadaannya terlebih dahulu.

Kesalahan umum#

Kesalahan pertama adalah menulis nama field tanpa tanda $ pada ekspresi. Pipeline berikut memakai teks gajiPokok sebagai operand alih-alih membaca fieldnya, sehingga operator $add menolak operand bertipe string saat eksekusi.

mongosh
db.pegawai.aggregate([
  { $addFields: { totalGaji: { $add: ["gajiPokok", "$tunjangan"] } } }
])
text Keluaran
Uncaught
MongoServerError[TypeMismatch]: Executor error during aggregate command on namespace: pegawai.pegawai :: caused by :: $add only supports numeric or date types, not string

Pesan error tersebut menyatakan bahwa $add hanya menerima tipe numerik atau tanggal. Perbaikannya adalah menuliskan "$gajiPokok" dengan tanda $, karena operand tanpa tanda $ selalu dianggap nilai konstan dan bukan field.

Kesalahan kedua adalah merujuk field lain yang ditambahkan pada tahap $addFields yang sama. Ekspresi dalam satu tahap tersebut dinilai terhadap dokumen masukan, sehingga field yang dinyatakan lebih dulu pada tahap yang sama belum terbaca.

mongosh
db.pegawai.aggregate([
  { $addFields: {
      totalGaji: { $add: ["$gajiPokok", "$tunjangan"] },
      duaKaliTotal: { $multiply: ["$totalGaji", 2] }
  } },
  { $project: { _id: 0, nama: 1, totalGaji: 1, duaKaliTotal: 1 } },
  { $limit: 2 }
])
text Keluaran
[
  { nama: 'Ali Rahman', totalGaji: 9500000, duaKaliTotal: null },
  { nama: 'Budi Santoso', totalGaji: 8000000, duaKaliTotal: null }
]

Field duaKaliTotal bernilai null tanpa pesan error karena $totalGaji belum ada pada dokumen masukan tahap itu. Perbaikannya adalah memecah perhitungan menjadi dua tahap $addFields berurutan, sebagaimana pipeline pada bagian logika kondisional, atau menulis ulang ekspresi pendahulunya di dalam ekspresi yang membutuhkannya.

Perhatikan pula bahwa kedua kesalahan di atas baru muncul saat eksekusi, bukan saat pipeline ditulis, sehingga pemeriksaan menyeluruh diperlukan sebelum pipeline dipasang pada aplikasi. Menjalankan pipeline yang sama dengan $limit kecil di akhir adalah cara cepat memeriksa bentuk keluaran setiap field baru. Kebiasaan tersebut menyingkap operand yang keliru maupun field yang belum terbaca dalam satu kali eksekusi.

Ringkasan#

  • $addFields menambahkan field terhitung dengan mempertahankan seluruh field masukan, dan field bernama sama ditimpa nilainya.
  • Operator $cond memilih satu dari dua nilai menurut syarat, sedangkan $switch menilai cabang berurutan beserta nilai default.
  • $ifNull memberi nilai pengganti bagi field yang tidak ada, dan $concat menyambung teks dengan hasil null bila ada argumen yang kosong.
  • Field yang ditambahkan pada satu tahap $addFields belum dapat dirujuk oleh field lain pada tahap yang sama.

Referensi#