Di database relasional, kolom yang tidak diisi selalu ada — nilainya NULL. Di MongoDB, document yang tidak menyimpan sebuah field berbeda dari document yang menyimpan field bernilai null: keduanya adalah kondisi terpisah di dalam BSON. Sayangnya, query { field: null } memperlakukan keduanya sebagai sama, dan perilaku inilah yang paling sering membuat laporan menghitung data yang sebenarnya belum terisi. Chapter ini membedah ketiga alat pembeda — kesamaan null, $type, dan $exists — beserta pola query “field terisi” yang dipakai di hampir semua aplikasi produksi.
Peta materi Chapter ini:
{ field: null }yang menangkap dua kondisi sekaligus.$type: "null"dan$existssebagai pembeda.- Pola
$exists+$ne: nulluntuk “field terisi”.
Data Uji: Diskon yang Tidak Selalu Ada #
Field hargaDiskon sengaja dibuat dalam tiga keadaan: berisi angka, berisi null (belum ada diskon, sengaja ditandai), dan tidak disimpan sama sekali:
use latihan_query
db.buku.drop()Output yang Dihasilkan:
switched to db latihan_query
truedb.buku.insertMany([
{ judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, hargaDiskon: 15, kategori: "fiksi" },
{ judul: "Bumi", penulis: "Tere Liye", harga: 89000, hargaDiskon: null, kategori: "fiksi" },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, hargaDiskon: 20, kategori: "nonfiksi" },
{ judul: "Atomic Habits", penulis: "James Clear", harga: 115000, kategori: "nonfiksi" },
{ judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi" },
{ judul: "Kamus Bahasa Indonesia", penulis: "Badan Bahasa", harga: 55000, hargaDiskon: null, kategori: "referensi" }
])Output yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad391cc16d5d5c7440b7bf0'),
'1': ObjectId('6ad391cc16d5d5c7440b7bf1'),
'2': ObjectId('6ad391cc16d5d5c7440b7bf2'),
'3': ObjectId('6ad391cc16d5d5c7440b7bf3'),
'4': ObjectId('6ad391cc16d5d5c7440b7bf4'),
'5': ObjectId('6ad391cc16d5d5c7440b7bf5')
}
}Status hargaDiskon per buku: angka pada Laut Bercerita (15) dan Filosofi Teras (20), null pada Bumi dan Kamus Bahasa Indonesia, serta tidak ada pada Atomic Habits dan Rumah Kaca.
{ field: null } Menangkap Dua Kondisi #
Query pertama mencari buku yang “tidak sedang diskon”:
db.buku.find({ hargaDiskon: null }, { _id: 0, judul: 1 })Output yang Dihasilkan:
[
{ judul: 'Bumi' },
{ judul: 'Atomic Habits' },
{ judul: 'Rumah Kaca' },
{ judul: 'Kamus Bahasa Indonesia' }
]Empat buku kembali: dua yang bernilai null dan dua yang tidak memiliki field hargaDiskon. MongoDB memperlakukan field yang hilang seolah bernilai null saat mencocokkan kesamaan — perilaku yang dijanjikan sejak Chapter Comparison Operators (di sana $ne ikut menangkap document tanpa field; ini sisi kebalikannya). Untuk “belum diisi” memang itu yang kita mau; untuk “sengaja kosong” hasilnya tercampur.
Pembuktian ekstremnya: query field yang tidak pernah ada di document mana pun tetap mengembalikan seluruh collection:
db.buku.find({ kodeRak: null }, { _id: 0, judul: 1 })Output yang Dihasilkan:
[
{ judul: 'Laut Bercerita' },
{ judul: 'Bumi' },
{ judul: 'Filosofi Teras' },
{ judul: 'Atomic Habits' },
{ judul: 'Rumah Kaca' },
{ judul: 'Kamus Bahasa Indonesia' }
]Tidak ada satu pun buku yang menyimpan kodeRak, sehingga keenamnya dicocokkan. Kesamaan null bukan pembuktian bahwa field ada dan kosong — hanya bukti bahwa nilainya tidak berisi data.
Pembeda: $type dan $exists #
Dua operator memisahkan kondisi yang tercampur itu. $type: "null" hanya menerima nilai yang benar-benar bertipe BSON null:
db.buku.find({ hargaDiskon: { $type: "null" } }, { _id: 0, judul: 1 })Output yang Dihasilkan:
[ { judul: 'Bumi' }, { judul: 'Kamus Bahasa Indonesia' } ]Hanya Bumi dan Kamus Bahasa Indonesia — document yang menyimpan null secara eksplisit — yang lolos; field yang hilang tidak punya tipe apa pun sehingga tidak diuji. Sementara itu $exists menguji keberadaan field itu sendiri, terlepas dari isinya:
db.buku.find({ hargaDiskon: { $exists: true } }, { _id: 0, judul: 1, hargaDiskon: 1 })
db.buku.find({ hargaDiskon: { $exists: false } }, { _id: 0, judul: 1 })Output yang Dihasilkan:
[
{ judul: 'Laut Bercerita', hargaDiskon: 15 },
{ judul: 'Bumi', hargaDiskon: null },
{ judul: 'Filosofi Teras', hargaDiskon: 20 },
{ judul: 'Kamus Bahasa Indonesia', hargaDiskon: null }
]
[ { judul: 'Atomic Habits' }, { judul: 'Rumah Kaca' } ]Mari kita bedah pasangan hasilnya:
$exists: true: empat buku yang menyimpan fieldhargaDiskon— termasuk yang isinyanull, karena$existshanya melihat ada-tidaknya field.$exists: false: dua buku tanpa field tersebut;$existstidak peduli nilai, sehingganulltidak masuk daftar ini.
Pola “Field Terisi”: $exists + $ne null #
Kebutuhan paling umum di aplikasi: mencari document yang field-nya benar-benar berisi nilai. Syaratnya dua lapis — field ada, dan isinya bukan null:
db.buku.find({ hargaDiskon: { $exists: true, $ne: null } }, { _id: 0, judul: 1, hargaDiskon: 1 })Output yang Dihasilkan:
[
{ judul: 'Laut Bercerita', hargaDiskon: 15 },
{ judul: 'Filosofi Teras', hargaDiskon: 20 }
]Hanya dua buku berdiskon angka yang tersisa. Menariknya, hasil yang sama bisa dicapai tanpa $exists:
db.buku.find({ hargaDiskon: { $ne: null } }, { _id: 0, judul: 1, hargaDiskon: 1 })Output yang Dihasilkan:
[
{ judul: 'Laut Bercerita', hargaDiskon: 15 },
{ judul: 'Filosofi Teras', hargaDiskon: 20 }
]Alasannya mengikuti logika kesamaan null di atas: field yang hilang dianggap sama dengan null, sehingga $ne: null otomatis menolak document tanpa field — $exists: true menjadi redundan. Meski begitu, banyak basis kode tetap menulis keduanya eksplisit karena maksudnya terbaca jelas: “ada dan terisi”. Ringkasan ketiganya:
| Query | Menangkap |
|---|---|
{ field: null } | bernilai null dan field tidak ada |
{ field: { $type: "null" } } | hanya bernilai null |
{ field: { $exists: true } } | field ada (termasuk isinya null) |
{ field: { $exists: false } } | field tidak ada |
{ field: { $ne: null } } | field ada dan isinya bukan null |
Rangkuman #
null(nilai eksplisit) dan field yang hilang adalah dua kondisi berbeda di BSON, tetapi{ field: null }mencocokkan keduanya.$type: "null"hanya menangkap nilainullsungguhan;$existsmenguji ada-tidaknya field tanpa peduli isi.$ne: nullsetara “ada dan tidak null” — field yang hilang ikut tertolak; kombinasi eksplisit$exists: true+$ne: nullpopuler karena mudah dibaca.- Sparse index bisa berinteraksi dengan query null — tunda penggunaannya sampai pembahasan Section Indexing.