Fondasi Query DSL: URI search vs request body, perbedaan query context dan filter context, filter _source, pagination dari/size dan search_after, serta query dasar: match, term, match_phrase, multi_match, query_string, exists, dan range.

Kalian sudah menyimpan data dengan mapping yang benar. Sekarang tibalah bagian paling menarik: mencari. Di episode ini kita menguasai dasar-dasar Query DSL — bahasa query JSON Elasticsearch — yang akan kalian pakai di hampir semua pekerjaan sehari-hari. Episode 6 membahas cara mengirim pencarian (URI search vs request body), konsep penting query context vs filter context, filter _source, pagination, dan tujuh query dasar: match, term, match_phrase, multi_match, query_string, exists, dan range. Semua contoh bisa langsung dicoba di Dev Tools Kibana atau dengan curl.
Ada dua cara mengirim query. URI search — ringkas, semua parameter di URL:
GET /produk/_search?q=kaosGET /produk/_search?q=name:kaos&size=5&from=10Request body search — query DSL dikirim sebagai JSON di body, jauh lebih ekspresif dan menjadi cara utama di series ini:
{
"query": { "match": { "name": "kaos polos" } },
"size": 5
}URI search praktis untuk debugging cepat; request body adalah standar untuk aplikasi.
Ini konsep yang menentukan relevansi dan performa. Di query context, Elasticsearch menghitung skor relevansi (_score) berdasarkan seberapa baik dokumen cocok — dipakai untuk full-text search yang hasilnya perlu diurutkan berdasarkan kepentingan. Di filter context, dokumen hanya dinilai "cocok atau tidak" — tidak ada skor, tapi hasilnya di-cache sehingga sangat cepat dijalankan berulang.
{
"query": {
"bool": {
"must": { "match": { "name": "kaos" } },
"filter": { "range": { "price": { "lte": 100000 } } }
}
}
}Query match dihitung skornya; range hanya menjadi filter yang di-cache. Aturan praktis: gunakan query context untuk "seberapa mirip", filter context untuk "ya/tidak" — seperti kategori, harga, status.
Secara default respons membawa seluruh _source (dokumen asli). Untuk respons yang ringan, filter field yang dikembalikan:
{ "_source": ["name", "price"], "query": { "match_all": {} } }_source: false menonaktifkannya total. Ingat: filter _source hanya memengaruhi apa yang dikembalikan, bukan dokumen mana yang cocok.
from dan size adalah pagination standar — namun jangan digunakan untuk halaman dalam. Karena coordinating node harus mengumpulkan semua hasil sebelum di-sort, from: 10000 membuat query makin lambat dan dibatasi default index.max_result_window (10000). Untuk pagination dalam, pakai search_after lewat GET /produk/_search:
{
"size": 10,
"sort": [{ "price": "asc" }, { "_id": "asc" }],
"query": { "match_all": {} }
}Ambil nilai sort dari dokumen terakhir halaman pertama, lalu kirim sebagai search_after untuk halaman berikutnya:
{
"size": 10,
"sort": [{ "price": "asc" }, { "_id": "asc" }],
"search_after": [45000, "product-42"]
}search_after tanpa limit kedalaman — inilah cara yang benar untuk scroll data besar.
match adalah query full-text utama. Ia menganalisis teks yang dicari (dipecah jadi token), lalu mencocokkan dengan index — default-nya OR antar token:
{
"query": { "match": { "name": "kaos premium" } }
}term mencari nilai persis tanpa analisis — untuk field keyword. Ia tidak cocok dipakai pada field text (karena text sudah dipecah jadi token):
{
"query": { "term": { "category.keyword": "fashion" } }
}match_phrase mencari urutan kata yang persis:
{
"query": { "match_phrase": { "name": "kaos polos" } }
}Dokumen "kaos polos premium" cocok; "kaos premium polos" tidak (urutan beda).
multi_match mencari di beberapa field sekaligus — misalnya name, description, dan tags:
{
"query": { "multi_match": { "query": "kaos murah", "fields": ["name^3", "description", "tags^2"] } }
}Notasi ^3 memberi bobot: kecocokan di name dianggap tiga kali lebih relevan daripada di description.
query_string memakai sintaks Lucene penuh — mendukung +, -, wildcard, dan operator dalam satu string. Fleksibel tapi rawan error parsing dari input pengguna:
GET /produk/_search?q=name:kaos+AND+price:<100000exists menemukan dokumen yang memiliki field tertentu (termasuk yang bukan null) — berguna untuk mendeteksi data tidak lengkap:
{
"query": { "exists": { "field": "discount" } }
}range bekerja untuk numerik, date, dan IP:
{
"query": { "range": { "price": { "gte": 50000, "lte": 200000 } } }
}Operator yang tersedia: gt, gte, lt, lte, dan untuk date bisa memakai math seperti "now-30d".
Tip
Kebingungan paling umum pemula: term di field text tidak menghasilkan apa-apa. Ingat aturan emasnya — term untuk keyword, match untuk text. Jika ragu tipe fieldnya, cek dengan GET /index/_mapping/field/nama_field dan lihat tipe yang sesungguhnya.
Di episode 6 kalian menguasai dasar-dasar Query DSL: URI search vs request body, konsep query context (dengan skor) vs filter context (di-cache), filter _source, pagination from/size dan search_after untuk data dalam, serta tujuh query inti — match, term, match_phrase, multi_match, query_string, exists, dan range.
Inti yang harus dibawa pulang:
_score.term untuk keyword (exact), match untuk text (full-text).match_phrase menuntut urutan kata yang sama.search_after menggantikan from untuk pagination dalam.range melayani numerik, date, dan IP.Sekarang kalian bisa mencari, tapi pernahkah bertanya: bagaimana kata "kaos" bisa mencocokkan dokumen "KAOS" atau "kaos-kas"? Jawabannya ada di lapisan di bawah query. Di episode 7 kita bedah text analysis dan analyzers: karakter filter, tokenizer, token filter, analyzer bawaan, membuat custom analyzer, tes dengan _analyze API, normalizers, dan pola n-gram untuk autocomplete. Sampai jumpa!