Syarat nyata jarang berdiri sendiri. “Buku nonfiksi atau harga di atas seratus ribu”, “murah tapi stoknya masih banyak”, “bukan karya penulis ini” — kalimat-kalimat tersebut menggabungkan beberapa kondisi menjadi satu keputusan. MongoDB menyediakan empat logical operators untuk menyusun gabungan itu: $or, $nor, $and, dan $not. Chapter ini menguji keempatnya dan menyoroti satu kasus ketika $and bukan pilihan gaya melainkan keharusan — akibat aturan object JavaScript itu sendiri.

Peta materi Chapter ini:

  • $or dan $nor untuk alternatif syarat.
  • AND implisit versus $and eksplisit, termasuk kapan $and wajib.
  • $not untuk meniadakan satu operator.

$or: Cukup Satu Syarat Benar #

$or menerima array berisi beberapa filter; document cocok bila minimal satu filter di dalamnya terpenuhi. Siapkan katalog yang sama dengan Chapter sebelumnya — ingat bahwa Kamus Bahasa Indonesia sengaja tanpa field kategori dan rating:

js
use latihan_crud
db.buku.drop()

Output yang Dihasilkan:

text
switched to db latihan_crud
true
js
db.buku.insertMany([
  { judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, stok: 12, kategori: "fiksi", tag: ["fiksi", "sastra"], rating: 4.7 },
  { judul: "Bumi", penulis: "Tere Liye", harga: 89000, stok: 25, kategori: "fiksi", tag: ["fiksi", "remaja"], rating: 4.5 },
  { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, stok: 18, kategori: "nonfiksi", tag: ["filsafat", "pengembangan diri"], rating: 4.8 },
  { judul: "Atomic Habits", penulis: "James Clear", harga: 115000, stok: 30, kategori: "nonfiksi", tag: ["pengembangan diri", "terjemahan"], rating: 4.6 },
  { judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, stok: 9, kategori: "fiksi", tag: ["fiksi", "remaja"], rating: 4.3 },
  { judul: "Kamus Bahasa Indonesia", penulis: "Badan Bahasa", harga: 55000, stok: 4, tag: ["referensi", "bahasa"] }
])

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad38852820b6760a4ccb30f'),
    '1': ObjectId('6ad38852820b6760a4ccb310'),
    '2': ObjectId('6ad38852820b6760a4ccb311'),
    '3': ObjectId('6ad38852820b6760a4ccb312'),
    '4': ObjectId('6ad38852820b6760a4ccb313'),
    '5': ObjectId('6ad38852820b6760a4ccb314')
  }
}

Query pertama: buku nonfiksi, atau buku apa pun yang harganya di bawah 75000:

js
db.buku.find({
  $or: [
    { kategori: "nonfiksi" },
    { harga: { $lt: 75000 } }
  ]
}, { judul: 1, kategori: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38852820b6760a4ccb311'),
    judul: 'Filosofi Teras',
    harga: 89000,
    kategori: 'nonfiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb312'),
    judul: 'Atomic Habits',
    harga: 115000,
    kategori: 'nonfiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb313'),
    judul: 'Rumah Kaca',
    harga: 72000,
    kategori: 'fiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb314'),
    judul: 'Kamus Bahasa Indonesia',
    harga: 55000
  }
]

Empat document lolos lewat jalur yang berbeda-beda: Filosofi Teras dan Atomic Habits lewat kategori, Rumah Kaca dan Kamus Bahasa Indonesia lewat harga. Kamus Bahasa Indonesia sekali lagi lolos tanpa punya field kategori — tapi kali ini karena syarat keduanya, harga, yang terpenuhi.

$nor: Semua Syarat Harus Salah #

$nor adalah kebalikan persis $or: document cocok bila semua syarat di dalamnya salah. Query yang sama persis, operator diganti:

js
db.buku.find({
  $nor: [
    { kategori: "nonfiksi" },
    { harga: { $lt: 75000 } }
  ]
}, { judul: 1, kategori: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38852820b6760a4ccb30f'),
    judul: 'Laut Bercerita',
    harga: 98000,
    kategori: 'fiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb310'),
    judul: 'Bumi',
    harga: 89000,
    kategori: 'fiksi'
  }
]

Hanya dua buku fiksi yang harganya 75000 ke atas yang selamat: Laut Bercerita dan Bumi. $nor jarang jadi pilihan pertama — pembacaannya berat karena dua kali peniadaan — tetapi sulit digantikan saat pengecualian memang didefinisikan sebagai “bukan ini dan bukan itu”.

$and: Kapan Harus Eksplisit #

Filter dengan beberapa field — seperti { kategori: "nonfiksi", harga: 89000 } di Chapter Query Filters — sudah menghasilkan AND secara implisit. Bentuk eksplisit $and menerima array filter sebagaimana $or:

js
db.buku.find({
  $and: [
    { harga: { $ne: 89000 } },
    { harga: { $lt: 100000 } }
  ]
}, { judul: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38852820b6760a4ccb30f'),
    judul: 'Laut Bercerita',
    harga: 98000
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb313'),
    judul: 'Rumah Kaca',
    harga: 72000
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb314'),
    judul: 'Kamus Bahasa Indonesia',
    harga: 55000
  }
]

Contoh ini menarik karena kedua syaratnya menyasar field yang sama: harga. Rentang pada satu field memang bisa ditulis padat seperti { $gte: 89000, $lte: 98000 }, tetapi gabungan seperti “bukan 89000 dan di bawah 100000” mengharuskan field harga muncul dua kali — dan di sinilah batas AND implisit: satu object JavaScript hanya boleh memiliki satu kunci harga.

Batas itu menjadi masalah sesungguhnya ketika operator yang ingin diulang adalah $or itu sendiri. Misalnya kita mencari buku yang (nonfiksi atau di atas 100000) dan (stok di atas 20 atau rating minimal 4.8). Tanpa $and, kita tergoda menulis dua kunci $or dalam satu object:

js
db.buku.find({
  $or: [ { kategori: "nonfiksi" }, { harga: { $gt: 100000 } } ],
  $or: [ { stok: { $gt: 20 } }, { rating: { $gte: 4.8 } } ]
}, { judul: 1, kategori: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38852820b6760a4ccb310'),
    judul: 'Bumi',
    harga: 89000,
    kategori: 'fiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb311'),
    judul: 'Filosofi Teras',
    harga: 89000,
    kategori: 'nonfiksi'
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb312'),
    judul: 'Atomic Habits',
    harga: 115000,
    kategori: 'nonfiksi'
  }
]

Query berjalan tanpa galat — dan justru itulah masalahnya. Lihat baris pertama hasil: Bumi ikut masuk padahal field kategori-nya sendiri tercetak fiksi, dan harganya 89000 — bukti bahwa kelompok syarat pertama seolah-olah tidak pernah ada. Penyebabnya: kunci $or kedua menimpa yang pertama secara diam-diam, sehingga yang dieksekusi server hanya kelompok stok dan rating. Bentuk yang benar membungkus kedua kelompok dengan $and:

js
db.buku.find({
  $and: [
    { $or: [ { kategori: "nonfiksi" }, { harga: { $gt: 100000 } } ] },
    { $or: [ { stok: { $gt: 20 } }, { rating: { $gte: 4.8 } } ] }
  ]
}, { judul: 1 })

Output yang Dihasilkan:

text
[
  { _id: ObjectId('6ad38852820b6760a4ccb311'), judul: 'Filosofi Teras' },
  { _id: ObjectId('6ad38852820b6760a4ccb312'), judul: 'Atomic Habits' }
]

Hasil yang tersisa hanya Filosofi Teras dan Atomic Habits — keduanya memang lolos kelompok pertama (nonfiksi) sekaligus kelompok kedua.

$not: Meniadakan Satu Operator #

$not bekerja di level field: operator ini membungkus satu operator document lalu membalik hasilnya. Buku yang harganya tidak lebih dari 80000:

js
db.buku.find({ harga: { $not: { $gt: 80000 } } }, { judul: 1, harga: 1 })

Output yang Dihasilkan:

text
[
  {
    _id: ObjectId('6ad38852820b6760a4ccb313'),
    judul: 'Rumah Kaca',
    harga: 72000
  },
  {
    _id: ObjectId('6ad38852820b6760a4ccb314'),
    judul: 'Kamus Bahasa Indonesia',
    harga: 55000
  }
]

Secara hasil, { $not: { $gt: 80000 } } setara dengan { $lte: 80000 } — tetapi $not berguna saat operator yang perlu dibalik bukan perbandingan sederhana, misalnya $regex atau $mod. Dua aturan syntaksnya ketat. Pertama, argumen $not harus operator document atau regex; nilai polos ditolak:

js
db.buku.find({ harga: { $not: 89000 } })

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[BadValue]: $not argument must be a regex or an object

Kedua, $not mewarisi perilaku $ne terhadap field yang tidak ada: document tanpa field harga akan ikut lolos { harga: { $not: { $gt: 80000 } } }. Di katalog kita semua buku punya harga sehingga contoh tidak menunjukkan kasus itu, tetapi ingat pola ini saat bekerja dengan data yang belum seragam.

Sebagai penutup, bersihkan database latihan:

js
db.dropDatabase()

Output yang Dihasilkan:

text
{ ok: 1, dropped: 'latihan_crud' }

Rangkuman #

OperatorBentukDocument cocok bila
$orarray of filterminimal satu syarat benar
$norarray of filtersemua syarat salah
$andarray of filtersemua syarat benar
$notoperator documentoperator di dalamnya tidak terpenuhi
  • Beberapa field dalam satu filter sudah di-AND-kan implisit; $and eksplisit tidak diperlukan untuk itu.
  • $and wajib saat field atau operator yang sama harus muncul dua kali — terutama dua kelompok $or — karena kunci object JavaScript tidak boleh ganda.
  • $not membungkus operator document (atau regex), menolak nilai polos, dan menangkap document tanpa field tersebut.
  • $nor adalah negasi $or: semua syarat harus salah agar document lolos.

Syarat tunggal, perbandingan, dan logika majemuk sudah lengkap. Chapter berikutnya menutup bagian query dengan tiga operator khusus field array: $all, $elemMatch, dan $size.