Halaman daftar produk di toko buku online hanya menampilkan judul dan harga, tetapi find() secara default mengembalikan seluruh isi document — termasuk daftar tag, rating, dan rincian stok yang tidak dibutuhkan layar tersebut. Setiap byte yang tidak terpakai tetap dikirim lewat jaringan dan menambah waktu respons aplikasi. Projection adalah parameter kedua find() yang menentukan field mana saja yang boleh ikut dikirim ke klien. Chapter ini membahas dua mode projection, aturan pencampurannya, hingga projection pada nested document dan elemen array.

Peta materi Chapter ini:

  • Inclusion (memilih field yang tampil) vs exclusion (menyembunyikan field).
  • Larangan mencampur keduanya, dengan pengecualian _id.
  • Projection pada nested document dan elemen array ($slice, operator posisi $).

Inclusion: Memilih Field yang Tampil #

Projection ditulis sebagai parameter kedua find(): find(filter, projection). Object projection berisi nama field dengan nilai 1 (inclusion) atau 0 (exclusion). Siapkan katalog buku dengan field lengkap:

js
use latihan_query
db.buku.drop()

Output yang Dihasilkan:

text
switched to db latihan_query
true
js
db.buku.insertMany([
  { judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, stok: { gudang: 8, toko: 4 }, kategori: "fiksi", tag: ["fiksi", "sastra", "pengarang Indonesia"], rating: 4.7 },
  { judul: "Bumi", penulis: "Tere Liye", harga: 89000, stok: { gudang: 20, toko: 5 }, kategori: "fiksi", tag: ["fiksi", "remaja"], rating: 4.5 },
  { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, stok: { gudang: 15, toko: 3 }, kategori: "nonfiksi", tag: ["nonfiksi", "filsafat", "pengembangan diri", "stoa"], rating: 4.8 },
  { judul: "Atomic Habits", penulis: "James Clear", harga: 115000, stok: { gudang: 25, toko: 5 }, kategori: "nonfiksi", tag: ["nonfiksi", "pengembangan diri", "terjemahan"], rating: 4.6 },
  { judul: "Kamus Bahasa Indonesia", penulis: "Badan Bahasa", harga: 55000, stok: 4, kategori: "referensi", tag: ["referensi", "bahasa"], rating: 4.0 }
])

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad38f3db0bdf8685c21a6c9'),
    '1': ObjectId('6ad38f3db0bdf8685c21a6ca'),
    '2': ObjectId('6ad38f3db0bdf8685c21a6cb'),
    '3': ObjectId('6ad38f3db0bdf8685c21a6cc'),
    '4': ObjectId('6ad38f3db0bdf8685c21a6cd')
  }
}

Mode inclusion memakai nilai 1 pada field yang ingin ditampilkan:

js
db.buku.find({ kategori: "fiksi" }, { judul: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38f3db0bdf8685c21a6c9'),
    judul: 'Laut Bercerita',
    harga: 98000
  },
  {
    _id: ObjectId('6ad38f3db0bdf8685c21a6ca'),
    judul: 'Bumi',
    harga: 89000
  }
]

Mari kita bedah hasilnya:

  1. { judul: 1, harga: 1 }: hanya judul dan harga yang dikirim; penulis, stok, tag, rating, dan kategori tidak ikut.
  2. _id selalu tampil secara default: perhatikan bahwa _id tetap hadir meskipun tidak kita minta. MongoDB menganggap _id penting bagi hampir semua klien.
  3. Field yang tidak diminta tidak dievaluasi: server memotong document sebelum mengirimnya, sehingga payload per document mengecil.

_id bisa dikeluarkan secara eksplisit dengan _id: 0 — satu-satunya field yang boleh digabung dengan inclusion:

js
db.buku.find({ kategori: "fiksi" }, { judul: 1, harga: 1, _id: 0 })

Output yang Dihasilkan:

text
[ { judul: 'Laut Bercerita', harga: 98000 }, { judul: 'Bumi', harga: 89000 } ]

Hasilnya kini benar-benar hanya dua field yang kita minta. Bentuk judul: 1, harga: 1, _id: 0 adalah pola paling umum untuk kebutuhan tampilan daftar produk.

Exclusion dan Larangan Pencampuran #

Mode exclusion bekerja sebaliknya: seluruh field tampil kecuali yang diberi nilai 0. Mode ini cocok saat document besar tetapi hanya satu-dua field yang ingin disembunyikan, misalnya sinopsis yang panjang:

js
db.buku.find({ kategori: "fiksi" }, { penulis: 0, stok: 0, tag: 0, rating: 0, kategori: 0 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38f3db0bdf8685c21a6c9'),
    judul: 'Laut Bercerita',
    harga: 98000
  },
  {
    _id: ObjectId('6ad38f3db0bdf8685c21a6ca'),
    judul: 'Bumi',
    harga: 89000
  }
]

Yang tersisa hanya _id, judul, dan harga. Sekarang coba campur kedua mode dalam satu projection:

js
db.buku.find({ kategori: "fiksi" }, { judul: 1, penulis: 0 })

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[Location31254]: Cannot do exclusion on field penulis in inclusion projection

Server menolak query tersebut. Pesan Cannot do exclusion on field penulis in inclusion projection menegaskan aturannya: satu projection harus sepenuhnya inclusion atau sepenuhnya exclusion.

Projection pada Nested Document #

Field bersarang dijangkau dengan dot notation yang sama seperti pada query filter. Untuk mengambil hanya stok.gudang:

js
db.buku.find({}, { judul: 1, "stok.gudang": 1, _id: 0 })

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita', stok: { gudang: 8 } },
  { judul: 'Bumi', stok: { gudang: 20 } },
  { judul: 'Filosofi Teras', stok: { gudang: 15 } },
  { judul: 'Atomic Habits', stok: { gudang: 25 } },
  { judul: 'Kamus Bahasa Indonesia' }
]

Dua hal menarik dari hasil di atas:

  1. Nested projection memotong object: stok yang aslinya punya dua subfield hanya dikirim bersama gudang — toko dibuang.
  2. Document yang bentuknya tidak cocok tidak mengirim field tersebut: Kamus Bahasa Indonesia menyimpan stok sebagai angka biasa (stok: 4), bukan object, sehingga path stok.gudang tidak bisa dijangkau dan field stok hilang sepenuhnya dari hasilnya.

Projection pada Elemen Array #

Dua projection operator khusus bekerja pada field array. Yang pertama, $slice, memotong jumlah elemen yang dikirim:

js
db.buku.find({}, { judul: 1, tag: { $slice: 2 }, _id: 0 })

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita', tag: [ 'fiksi', 'sastra' ] },
  { judul: 'Bumi', tag: [ 'fiksi', 'remaja' ] },
  { judul: 'Filosofi Teras', tag: [ 'nonfiksi', 'filsafat' ] },
  { judul: 'Atomic Habits', tag: [ 'nonfiksi', 'pengembangan diri' ] },
  { judul: 'Kamus Bahasa Indonesia', tag: [ 'referensi', 'bahasa' ] }
]

Nilai negatif mengambil dari belakang — $slice: -1 hanya mengirim tag terakhir:

js
db.buku.find({}, { judul: 1, tag: { $slice: -1 }, _id: 0 })

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita', tag: [ 'pengarang Indonesia' ] },
  { judul: 'Bumi', tag: [ 'remaja' ] },
  { judul: 'Filosofi Teras', tag: [ 'stoa' ] },
  { judul: 'Atomic Habits', tag: [ 'terjemahan' ] },
  { judul: 'Kamus Bahasa Indonesia', tag: [ 'bahasa' ] }
]

Operator kedua, $ (operator posisi), mengirim hanya elemen array yang cocok dengan syarat query. Query-nya harus memfilter field array yang sama:

js
db.buku.find({ tag: "sastra" }, { judul: 1, "tag.$": 1, _id: 0 })

Output yang Dihasilkan:

text
[ { judul: 'Laut Bercerita', tag: [ 'sastra' ] } ]

Laut Bercerita memiliki tiga tag, tetapi yang terkirim hanya 'sastra' — elemen yang memenuhi filter { tag: "sastra" }.

Payload dan Performa #

Projection bukan sekadar kerapian tampilan. Pada collection dengan document besar, memilih beberapa field bisa menurunkan ukuran respons dari ratusan KB menjadi beberapa KB per document — perbedaan yang terasa saat jaringan lambat atau saat aplikasi menampilkan puluhan ribu baris. Server juga membaca lebih sedikit data dari disk untuk menyusun respons.

Kombinasi projection dengan index bahkan memungkinkan covered query — query yang dijawab sepenuhnya dari index tanpa menyentuh document aslinya. Efek payload ini kita ukur dengan angka nyata di Chapter Projection dan Ukuran Payload pada Section Performance dan Optimization.

Rangkuman #

  • Projection adalah parameter kedua find(); inclusion (field: 1) memilih field yang tampil, exclusion (field: 0) menyembunyikannya.
  • Kedua mode tidak boleh dicampur, kecuali untuk _id yang selalu bisa dikontrol dari mode mana pun.
  • Nested field dijangkau dengan dot notation ("stok.gudang": 1) dan terpotong mengikuti projection.
  • $slice memotong elemen array; operator posisi $ mengirim hanya elemen yang cocok dengan filter query.
  • Payload yang ramping berarti respons lebih cepat dan konsumsi memori klien lebih kecil.