Kabar baiknya: kita tidak perlu mempelajari bahasa query baru. Filter MongoDB sudah berbentuk document, dan document di Python adalah dict — sehingga {"harga": {"$gt": 100000}} yang kita tulis di mongosh bisa disalin mentah-mentah ke PyMongo. Yang perlu dikuasai hanyalah pola terjemahannya: operator menjadi dict bersarang, urutan pengurutan menjadi argumen method, dan projection menjadi dict kedua pada find(). Chapter ini melatih pola itu memakai katalog toko buku yang sudah dilengkapi field bersarang dan array.

Filter sebagai Dictionary #

Satu berkas berisi seluruh pola filter yang paling sering dipakai. Fungsi kecil judul() di dalamnya meringkas hasil setiap query menjadi list judul supaya output mudah dibandingkan:

python
import os

from pymongo import ASCENDING, DESCENDING, MongoClient

URI = os.environ.get(
    "MONGODB_URI", "mongodb://127.0.0.1:27017/?directConnection=true"
)

client = MongoClient(URI, serverSelectionTimeoutMS=5000)
client.drop_database("latihan_query_py")
db = client["latihan_query_py"]

db.buku.insert_many([
    {"judul": "Laut Bercerita", "penulis": "Leila S. Chudori", "harga": 98000,
     "kategori": "fiksi", "tag": ["sastra", "pengarang-wanita"],
     "penerbit": {"nama": "KPG", "kota": "Jakarta"}},
    {"judul": "Bumi", "penulis": "Tere Liye", "harga": 89000,
     "kategori": "fiksi", "tag": ["remaja"],
     "penerbit": {"nama": "Gramedia", "kota": "Jakarta"}},
    {"judul": "Sapiens", "penulis": "Yuval Noah Harari", "harga": 135000,
     "kategori": "nonfiksi", "tag": ["sejarah", "bestseller"],
     "penerbit": {"nama": "KPG", "kota": "Jakarta"}},
    {"judul": "Rumah Kaca", "penulis": "Pramoedya A. Toer", "harga": 110000,
     "kategori": "fiksi", "tag": ["sastra", "sejarah"],
     "penerbit": {"nama": "Hasta Mitra", "kota": "Yogyakarta"}},
    {"judul": "Kamus Besar Bahasa Indonesia", "penulis": "Badan Bahasa",
     "harga": 75000, "kategori": "referensi", "tag": ["referensi"],
     "penerbit": {"nama": "Balai Pustaka", "kota": "Jakarta"}},
])

def judul(cursor):
    return [d["judul"] for d in cursor]

print("kategori == fiksi  :", judul(db.buku.find({"kategori": "fiksi"})))
print("harga > 100000     :", judul(db.buku.find({"harga": {"$gt": 100000}})))
print("kategori $in       :", judul(db.buku.find(
    {"kategori": {"$in": ["nonfiksi", "referensi"]}})))
print("array 'sastra'     :", judul(db.buku.find({"tag": "sastra"})))
print("penerbit Yogyakarta:", judul(db.buku.find({"penerbit.kota": "Yogyakarta"})))
print("penulis awalan 'T' :", judul(db.buku.find({"penulis": {"$regex": "^T"}})))

print("projection judul + harga:")
for d in db.buku.find({"kategori": "fiksi"}, {"judul": 1, "harga": 1, "_id": 0}):
    print("   ", d)

print("termahal 2         :", judul(
    db.buku.find({}, {"judul": 1, "_id": 0}).sort("harga", DESCENDING).limit(2)))
print("halaman 2          :", judul(
    db.buku.find({}, {"judul": 1, "_id": 0}).sort("harga", ASCENDING).skip(2).limit(2)))
print("sort ganda         :", judul(
    db.buku.find({}, {"judul": 1, "_id": 0})
    .sort([("kategori", ASCENDING), ("harga", DESCENDING)])))

client.drop_database("latihan_query_py")
client.close()

Output yang Dihasilkan:

text
kategori == fiksi  : ['Laut Bercerita', 'Bumi', 'Rumah Kaca']
harga > 100000     : ['Sapiens', 'Rumah Kaca']
kategori $in       : ['Sapiens', 'Kamus Besar Bahasa Indonesia']
array 'sastra'     : ['Laut Bercerita', 'Rumah Kaca']
penerbit Yogyakarta: ['Rumah Kaca']
penulis awalan 'T' : ['Bumi']
projection judul + harga:
    {'judul': 'Laut Bercerita', 'harga': 98000}
    {'judul': 'Bumi', 'harga': 89000}
    {'judul': 'Rumah Kaca', 'harga': 110000}
termahal 2         : ['Sapiens', 'Rumah Kaca']
halaman 2          : ['Laut Bercerita', 'Rumah Kaca']
sort ganda         : ['Rumah Kaca', 'Laut Bercerita', 'Bumi', 'Sapiens', 'Kamus Besar Bahasa Indonesia']

Mari kita bedah pola filternya satu per satu:

  1. Kesamaan langsung — {"kategori": "fiksi"} tanpa operator berarti $eq: field harus persis sama nilainya.
  2. Operator perbandingan — {"harga": {"$gt": 100000}}; dict kedua berisi operator dan nilai. Dua field berbeda bisa digabung dalam satu filter dict dan keduanya di-AND-kan.
  3. $in — daftar nilai yang diterima; padanan banyak $eq sekaligus, jauh lebih ringkas.
  4. Field array — filter {"tag": "sastra"} mencocokkan document yang memuat elemen tersebut di array-nya; $elemMatch tersedia untuk syarat multi-elemen yang lebih ketat.
  5. Dot notation — "penerbit.kota" menembus document bersarang; tanpa tanda titik, filter akan mencari field penerbit.kota yang tidak ada.
  6. $regex — pola regex PCRE; pencocokan peka huruf besar-kecil kecuali diberi opsi {"$options": "i"}.

Operator lain yang sering dipakai mengikuti bentuk yang sama:

OperatorArti
$gt, $gte, $lt, $ltelebih dari / sama dengan, kurang dari / sama dengan
$netidak sama dengan
$in, $nintermasuk / tidak termasuk daftar nilai
$existsfield ada (atau tidak) di document
$regexpencocokan pola teks

Projection #

Argumen kedua find() memilih field apa saja yang dikirim balik ke aplikasi. {"judul": 1, "harga": 1} berarti inclusion — hanya dua field itu yang diunduh, dan _id dikecualikan eksplisit karena field ini selalu ikut secara bawaan. Balikannya, {"ringkasan": 0} berarti exclusion — semua field dikirim kecuali yang disebut.

Projection bukan sekadar kerapian output. Pada collection dengan document besar — ringkasan buku sepanjang paragraf, sampul base64 — memangkas field yang tidak dipakai memperkecil data yang lewat jaringan dan memori, dan membantu index menutup query sepenuhnya (topik covered query di Section indexing).

sort, limit, skip #

Di mongosh kita menulis .sort().limit().skip() berantai di belakang find(); PyMongo memakai bentuk yang sama, dengan dua catatan. Pertama, arah urutan ditulis sebagai konstanta ASCENDING (nilai 1) atau DESCENDING (nilai -1) yang diimpor dari pymongo — angka mentah 1/-1 juga sah. Kedua, urutan pemanggilan bebas: server selalu menerapkan sort → skip → limit secara logis, apa pun urutan penulisan di kode kita.

Untuk pengurutan multi-field, kunci dikirim sebagai list pasangan: .sort([("kategori", ASCENDING), ("harga", DESCENDING)]) — hasil output kita memperlihatkan efeknya, buku dikelompokkan per kategori terlebih dahulu, lalu diurutkan harga dari yang termahal di dalam tiap kelompok. Kombinasi skip + limit adalah pagination paling sederhana, tetapi skip membaca dan membuang document yang dilewati — semakin dalam halamannya, semakin berat kerjanya. Chapter Cursor dan Pagination membahas alternatif yang jauh lebih efisien.

Rangkuman #

  • Filter, operator, dan projection memakai dict Python — semantiknya identik dengan yang kita tulis di mongosh.
  • Field bersarang diakses lewat dot notation ("penerbit.kota"); field array dicocokkan berdasarkan keanggotaan elemennya.
  • Projection adalah argumen kedua find(); inclusion dan exclusion tidak boleh bercampur, kecuali _id.
  • sort / skip / limit dirantai di belakang find(); arah urutan memakai ASCENDING/DESCENDING; multi-field memakai list pasangan.
  • Urutan pemanggilan sort/skip/limit bebas — server menerapkannya secara tetap.

Satu tingkat di atas query biasa menunggu: aggregation pipeline yang tersusun dari list of dict — Chapter berikutnya.