Model data document sering menyimpan informasi berlapis: profil penulis di dalam data buku, alamat di dalam data pelanggan, dimensi di dalam data produk. Chapter Query Filters sudah memperlihatkan penjangkauan singkat lewat dot notation; chapter ini memperdalamnya dengan aturan yang wajib dipahami sebelum schema kita makin kompleks: penulisan nama field dengan spasi, perbedaan mencocokkan object secara utuh versus per field, dan penjangkauan posisi elemen array. Salah pilih pola di antaranya adalah sumber bug query yang paling sering muncul di schema bersarang.

Peta materi Chapter ini:

  • Dot notation pada beberapa tingkat kedalaman.
  • Kewajiban tanda kutip untuk nama field dengan spasi.
  • Exact match object (termasuk urutan field) vs query per field.
  • Penjangkauan posisi elemen array (tag.0).

Data Uji: Profil Penulis Bersarang #

Kali ini penulis bukan lagi string, melainkan object profil berisi nama, kota, dan tahun mulai menulis — nama terakhir sengaja memakai spasi:

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: { nama: "Leila S. Chudori", kota: "Jakarta", "tahun mulai menulis": 1989 }, harga: 98000, kategori: "fiksi", tag: ["fiksi", "sastra", "pengarang Indonesia"] },
  { judul: "Bumi", penulis: { nama: "Tere Liye", kota: "Bandung", "tahun mulai menulis": 2005 }, harga: 89000, kategori: "fiksi", tag: ["fiksi", "remaja"] },
  { judul: "Filosofi Teras", penulis: { nama: "Henry Manampiring", kota: "Jakarta", "tahun mulai menulis": 2018 }, harga: 89000, kategori: "nonfiksi", tag: ["nonfiksi", "filsafat", "pengembangan diri"] },
  { judul: "Atomic Habits", penulis: { nama: "James Clear", kota: "New York", "tahun mulai menulis": 2010 }, harga: 115000, kategori: "nonfiksi", tag: ["nonfiksi", "pengembangan diri", "terjemahan"] },
  { judul: "Kamus Bahasa Indonesia", penulis: { nama: "Badan Bahasa", kota: "Jakarta", "tahun mulai menulis": 2008 }, harga: 55000, kategori: "referensi", tag: ["referensi"] }
])

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad390750df1be6e57b76057'),
    '1': ObjectId('6ad390750df1be6e57b76058'),
    '2': ObjectId('6ad390750df1be6e57b76059'),
    '3': ObjectId('6ad390750df1be6e57b7605a'),
    '4': ObjectId('6ad390750df1be6e57b7605b')
  }
}

Dot Notation dan Nama Field dengan Spasi #

Path bertitik menembus object: "penulis.kota" berarti field kota di dalam object penulis. Semua buku yang penulisnya tinggal di Jakarta:

js
db.buku.find({ "penulis.kota": "Jakarta" }, { _id: 0, judul: 1 })

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita' },
  { judul: 'Filosofi Teras' },
  { judul: 'Kamus Bahasa Indonesia' }
]

Operator perbandingan bekerja seperti biasa pada path ini — tinggal memakai bentuk { field: { operator: nilai } } yang sudah kita kenal:

js
db.buku.find({ "penulis.tahun mulai menulis": 1989 }, { _id: 0, judul: 1 })

Output yang Dihasilkan:

text
[ { judul: 'Laut Bercerita' } ]

Perhatikan tanda kutip pada "penulis.tahun mulai menulis". Nama field yang mengandung spasi atau karakter khusus membuat path tersebut bukan identifier JavaScript yang sah, sehingga kunci object di filter wajib ditulis sebagai string. Tanpa kutip, mongosh menolak kode tersebut bahkan sebelum dikirim ke server karena kesalahan sintaks. Kebiasaan aman: tulis semua path bertitik sebagai string, seperti "penulis.kota".

Exact Match Object vs Query Per Field #

Cara kedua mencocokkan nested document adalah menyertakan object utuh sebagai nilai filter. Bumi disimpan dengan penulis: { nama: "Tere Liye", kota: "Bandung", "tahun mulai menulis": 2005 }:

js
db.buku.find({ penulis: { nama: "Tere Liye", kota: "Bandung", "tahun mulai menulis": 2005 } }, { _id: 0, judul: 1 })

Output yang Dihasilkan:

text
[ { judul: 'Bumi' } ]

Query ini cocok karena seluruh isi object sama. Sekarang uji dua variasi yang sering menjebak:

js
db.buku.find({ penulis: { kota: "Bandung", nama: "Tere Liye", "tahun mulai menulis": 2005 } }, { _id: 0, judul: 1 }).toArray()
db.buku.find({ penulis: { nama: "Tere Liye" } }, { _id: 0, judul: 1 }).toArray()

Output yang Dihasilkan:

text
[]
[]

Kedua query mengembalikan array kosong. Mari kita bedah alasannya:

  1. Baris pertama (urutan field dibalik): exact match object menuntut kesamaan isi dan urutan field. MongoDB membandingkan object sebagai urutan pasangan kunci-nilai BSON, sehingga { kota, nama, tahun } tidak sama dengan { nama, kota, tahun } yang tersimpan.
  2. Baris kedua (object parsial): exact match tidak mencocokkan sebagian. Object { nama: "Tere Liye" } hanya berisi satu field, sedangkan penulis di data punya tiga field — tidak sama, tidak cocok.

Inilah alasan dot notation menjadi pola utama untuk query nested document: "penulis.nama" fleksibel, tidak peduli field lain yang menyertainya, dan tidak terganggu urutan penulisan. Exact match object hanya cocok saat kita memang mencari document yang persis berisi object tersebut.

Posisi Elemen Array dengan Index #

Angka pada path bertitik menjangkau elemen array pada posisi tertentu: "tag.0" adalah elemen pertama, "tag.1" elemen kedua. Buku yang tag pertamanya fiksi:

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

Output yang Dihasilkan:

text
[
  {
    judul: 'Laut Bercerita',
    tag: [ 'fiksi', 'sastra', 'pengarang Indonesia' ]
  },
  { judul: 'Bumi', tag: [ 'fiksi', 'remaja' ] }
]

Filosofi Teras dan Atomic Habits tidak ikut meskipun array-nya memuat nonfiksi — elemen pertama mereka memang nonfiksi, bukan fiksi. Posisi bisa dikombinasikan seperti field biasa; buku yang tag pertamanya nonfiksi dan tag keduanya pengembangan diri:

js
db.buku.find({ "tag.0": "nonfiksi", "tag.1": "pengembangan diri" }, { _id: 0, judul: 1, tag: 1 })

Output yang Dihasilkan:

text
[
  {
    judul: 'Atomic Habits',
    tag: [ 'nonfiksi', 'pengembangan diri', 'terjemahan' ]
  }
]

Hanya Atomic Habits yang memenuhi kedua posisi sekaligus. Query posisi seperti ini berguna saat urutan elemen bermakna — misalnya array langkah atau daftar prioritas. Satu kekurangannya: pola ini rapuh terhadap perubahan urutan array, dan angka pada path tidak bisa dipakai untuk “elemen berapa pun” — untuk itu, query elemen tanpa posisi atau $elemMatch adalah alat yang tepat, dibahas di dua chapter berikutnya.

Rangkuman #

  • Dot notation ("penulis.kota") menjangkau field nested dan mendukung seluruh operator query.
  • Path dengan spasi atau karakter khusus wajib ditulis sebagai string; exact match object menuntut isi dan urutan field sama persis.
  • Object parsial tidak cocok untuk exact match — gunakan dot notation untuk pencocokan per field.
  • Angka pada path ("tag.0", "tag.1") menjangkau elemen array pada posisi tertentu dan bisa dikombinasikan.