Memperdalam kemampuan query dengan operator perbandingan, logika, elemen, dan array; menguasai sorting, limit, dan skip; serta membedah offset-based pagination versus cursor-based pagination untuk aplikasi berskala.

Di episode 3 dan 4 kalian sudah menulis query sederhana: filter berdasarkan kesetaraan ({ name: "Sneaker" }) dan update dengan operator. Tapi aplikasi dunia nyata jarang sesederhana itu. Pertanyaan seperti "tampilkan produk antara 100 ribu dan 500 ribu", "cari user yang aktif ATAU punya role mentor", atau "cari order yang semua statusnya paid" butuh operator yang jauh lebih kaya.
Episode 5 adalah gudang senjata query. Roadmap-nya: pertama operator perbandingan, kedua operator logika, ketiga operator elemen, keempat operator array, dan kelima sorting serta pagination. Akhir episode, kalian akan bisa menjawab hampir semua kebutuhan filter aplikasi dengan satu bahasa query yang ekspresif.
Operator perbandingan adalah yang paling dasar dan paling sering dipakai. Perhatikan bentuknya: field di sebelah kiri, operator di dalam object di sebelah kanan:
| Operator | Arti | Contoh |
|---|---|---|
$eq | Sama dengan | { price: { $eq: 150000 } } |
$ne | Tidak sama dengan | { role: { $ne: "admin" } } |
$gt | Lebih besar | { price: { $gt: 150000 } } |
$gte | Lebih besar atau sama | { stock: { $gte: 5 } } |
$lt | Lebih kecil | { age: { $lt: 30 } } |
$lte | Lebih kecil atau sama | { age: { $lte: 30 } } |
$in | Salah satu dari daftar | { category: { $in: ["elektronik", "fashion"] } } |
$nin | Bukan salah satu dari daftar | { category: { $nin: ["makanan"] } } |
db.products.find({
category: { $in: ["elektronik", "fashion"] },
price: { $gte: 100000, $lte: 1000000 }
})Perhatikan contoh kedua: field price menerima beberapa operator sekaligus dalam satu object — artinya semua kondisi harus terpenuhi. Ini jauh lebih ringkas daripada menulis dua filter terpisah.
Operator logika mengatur bagaimana kondisi digabungkan:
| Operator | Arti |
|---|---|
$and | Semua kondisi harus benar (default implisit saat menulis beberapa field) |
$or | Minimal satu kondisi benar |
$not | Membalik hasil kondisi |
$nor | Semua kondisi harus salah |
Contoh paling berguna adalah $or — mencari dokumen yang cocok dengan salah satu kondisi:
db.users.find({
$or: [
{ role: "mentor" },
{ isActive: true }
],
$and: [
{ age: { $gte: 18 } },
{ age: { $lte: 40 } }
]
})Info
Penting diketahui: saat kalian menulis beberapa field tanpa operator logika eksplisit, MongoDB menggabungkannya dengan $and secara implisit. Jadi { price: { $gt: 100 }, category: "elektronik" } sama artinya dengan menulis { $and: [{ price: { $gt: 100 } }, { category: "elektronik" }] }. Gunakan bentuk eksplisit saat butuh kejelasan atau ada kondisi yang memakai field yang sama.
Karena schema MongoDB fleksibel, tidak semua dokumen dalam satu collection memiliki field yang sama. Dua operator elemen membantu menangani situasi ini:
$exists — memeriksa apakah field ada di dokumen (tidak peduli nilainya apa).$type — memeriksa tipe BSON field.db.products.find({ specs: { $exists: true } })
db.products.find({ price: { $type: "decimal" } })Query pertama menemukan semua produk yang punya field specs. Query kedua menemukan produk yang harga tersimpan sebagai tipe decimal — berguna untuk menemukan data dengan tipe salah yang masuk di masa lalu.
Operasi array adalah area paling membedakan MongoDB dari RDBMS. Saat query terhadap field array, MongoDB memiliki perilaku default dan tiga operator khusus:
find({ tags: "mongodb" }) cocok jika array mengandung elemen tersebut.$all — array harus mengandung semua elemen yang disebutkan.$elemMatch — setidaknya satu elemen array memenuhi beberapa kondisi sekaligus.$size — panjang array sama dengan nilai yang diberikan.db.products.find({ tags: { $all: ["best-seller", "baru"] } })
db.orders.find({
items: {
$elemMatch: { product: "laptop", qty: { $gte: 2 } }
}
})
db.orders.find({ items: { $size: 3 } })Contoh $elemMatch paling penting: ia mencari order yang memiliki satu item bernama laptop sekaligus qty-nya minimal 2 dalam elemen yang sama di array. Tanpa $elemMatch, kondisi { "items.product": "laptop", "items.qty": { $gte: 2 } } bisa cocok dengan dua elemen berbeda — perilaku yang sering membingungkan.
Urutkan hasil dengan .sort(). Nilai 1 berarti ascending (kecil ke besar), -1 berarti descending (besar ke kecil):
db.products.find({}).sort({ price: 1 })
db.products.find({}).sort({ price: -1, createdAt: -1 }).limit(n) membatasi jumlah hasil; .skip(n) melompati n dokumen pertama:
db.products.find({})
.sort({ price: -1 })
.limit(10)
.skip(20)Query di atas mengambil halaman ketiga (dengan asumsi 10 item per halaman) dari produk termahal ke termurah. Inilah dasar offset-based pagination.
Pola di atas (skip + limit) adalah offset-based pagination — paling sederhana, cocok untuk halaman kecil. Tapi ia punya kelemahan mendasar: .skip() tetap memindai dokumen yang dilewati. Saat dataset membesar (misal skip 100.000), query melambat drastis. Belum lagi jika ada data baru masuk di tengah navigasi, hasil bisa "loncat" dan halaman berduplikasi.
Untuk dataset besar atau data yang terus berubah, gunakan cursor-based pagination: simpan nilai unik terakhir dari halaman sebelumnya, lalu filter berdasarkan nilai itu. Karena memakai index, performanya tetap cepat berapapun kedalaman halaman:
db.products.find({
$or: [
{ price: { $lt: 150000 } },
{ price: 150000, _id: { $gt: ObjectId("66xxxxxxxxxxxxxxxxxxxxxxxx") } }
]
}).sort({ price: -1, _id: -1 }).limit(10)Pola $or di atas mengambil 10 produk dengan harga kurang dari 150 ribu, atau harga tepat 150 ribu dengan _id lebih besar dari produk terakhir yang dilihat — komposisi (price, _id) yang terurut rapi menjamin tidak ada data terlewat atau duplikat.
Warning
Pagination besar (deep pagination) dengan skip pada collection jutaan dokumen adalah salah satu penyebab paling umum query lambat di produksi. Jika kalian melihat skip yang besar (ribuan ke atas) sering dipanggil, pertimbangkan berpindah ke cursor-based pagination — atau kombinasikan dengan filter temporal (misal "ambil order bulan ini") untuk mempersempit cakupan.
Pada episode 5 ini kalian telah memperluas kemampuan query secara signifikan: operator perbandingan $gt, $gte, $lt, $lte, $in, $nin, dan $ne; operator logika $and, $or, $not, dan $nor; operator elemen $exists dan $type; operator array $all, $elemMatch, dan $size; serta sorting, limit, skip, dan dua pendekatan pagination — offset-based dan cursor-based.
Inti yang harus dibawa pulang:
$and implisit; gunakan $or untuk alternatif.$elemMatch mencocokkan kondisi pada elemen array yang sama — hindari jebakan query array biasa.$exists dan $type sangat berguna di schema yang fleksibel..sort().limit().skip() adalah dasar pagination sederhana.Di episode 6 berikutnya kita naik level dari "bisa query" ke "merancang data dengan benar": Schema Design Patterns & Best Practices. Kalian akan memahami bahwa flexible schema bukan berarti tanpa desain, mempelajari prinsip data locality, serta mengenali anti-pattern yang harus dihindari seperti massive arrays dan excessive nesting. Sampai jumpa di episode 6!