Compound Queries — bool, boosting, dis_max
bool, boosting, dis_max
Bir dedektif düşün. Bir dava çözerken tek bir ipucuna bakarak karar vermez — birden fazla kanıtı birleştirir. "Parmak izi eşleşti VE motif var VE alibisi yok" dediğinde, her kanıt ayrı bir sorgu, hepsini birleştiren mekanizma ise compound query.
Compound query'ler, birden fazla sorguyu mantıksal operatörlerle birleştirerek karmaşık arama senaryolarını çözer. Bu derste bool, boosting ve dis_max sorgularını tam olarak öğreneceğiz.
bool Query — Her Şeyin Temeli
bool query, Elasticsearch'teki en sık kullanılan ve en güçlü compound query'dir. Dört bölümden oluşur:
GET /products/_search
{
"query": {
"bool": {
"must": [ ... ], // VE — eşleşmeli + scoring
"should": [ ... ], // VEYA — eşleşirse bonus skor
"must_not": [ ... ], // DEĞİL — eşleşmemeli (filter context)
"filter": [ ... ] // VE — eşleşmeli, scoring YOK (filter context)
}
}
}must — Zorunlu Eşleşme (AND + Scoring)
Her must koşulu eşleşmek zorunda. Her biri scoring'e katkı sağlar.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "laptop" } },
{ "match": { "description": "hafif" } }
]
}
}
}
// "laptop" VE "hafif" — ikisi de eşleşmeli
// Skor = name skoru + description skorushould — Opsiyonel Eşleşme (OR + Bonus Scoring)
should'un davranışı kontekste göre değişir:
Kural 1: must veya filter varken → should opsiyonel (bonus scoring)
{
"bool": {
"must": [
{ "match": { "name": "laptop" } }
],
"should": [
{ "term": { "brand.keyword": "Apple" } },
{ "term": { "tags": "premium" } }
]
}
}
// "laptop" ZORUNLu eşleşmeli
// Apple veya premium olursa BONUS skor — olmasa da sonuçta çıkarKural 2: Sadece should varken → minimum 1 should eşleşmeli (OR)
{
"bool": {
"should": [
{ "match": { "name": "laptop" } },
{ "match": { "name": "tablet" } },
{ "match": { "name": "telefon" } }
]
}
}
// "laptop" VEYA "tablet" VEYA "telefon" — en az biri eşleşmeliminimum_should_match
should'un kaçının eşleşmesini istediğini kontrol et:
{
"bool": {
"should": [
{ "term": { "tags": "premium" } },
{ "term": { "tags": "yeni" } },
{ "term": { "tags": "indirimli" } },
{ "term": { "tags": "trend" } }
],
"minimum_should_match": 2
}
}
// 4 koşuldan en az 2'si eşleşmeli
// Yüzdesel
"minimum_should_match": "75%"
// 4 koşulun 75%'i = 3 koşul eşleşmeli
// Negatif değer (sondan say)
"minimum_should_match": -1
// 4 - 1 = 3 koşul eşleşmeli (en fazla 1 eşleşmeyebilir)must_not — Hariç Tutma (NOT, Filter Context)
{
"bool": {
"must": [
{ "match": { "name": "laptop" } }
],
"must_not": [
{ "term": { "brand.keyword": "HP" } },
{ "term": { "status": "discontinued" } },
{ "range": { "price": { "gt": 100000 } } }
]
}
}
// laptop ara AMA HP marka DEĞİL VE discontinued DEĞİL VE 100K+ DEĞİLmust_not her zaman filter context'te çalışır — scoring yapmaz, cache'lenir.
filter — Zorunlu Eşleşme (AND, No Scoring)
{
"bool": {
"must": [
{ "match": { "name": "laptop" } }
],
"filter": [
{ "term": { "in_stock": true } },
{ "range": { "price": { "gte": 5000, "lte": 50000 } } },
{ "term": { "category.keyword": "Laptop" } }
]
}
}
// must → scoring yapılır (hangi laptop en alakalı?)
// filter → scoring yapılmaz (stokta mı? fiyat aralığında mı?)must vs filter — Ne Zaman Hangisi?
Kullanıcı neyi arattı? → must (scoring gerekli)
Sistem filtreleri neler? → filter (scoring gereksiz)
Örnekler:
- Arama çubuğundaki metin → must
- Kategori filtresi → filter
- Fiyat aralığı → filter
- Stok durumu → filter
- Rating filtresi → filter
- Marka filtresi → should (bonus) veya filter (zorunlu)İç İçe bool Query
bool query'ler iç içe kullanılabilir:
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "kulaklık" } }
],
"filter": [
{ "term": { "in_stock": true } },
{
"bool": {
"should": [
{ "term": { "brand.keyword": "Sony" } },
{ "term": { "brand.keyword": "Apple" } },
{ "term": { "brand.keyword": "Samsung" } }
],
"minimum_should_match": 1
}
}
],
"should": [
{
"bool": {
"must": [
{ "term": { "tags": "noise-cancelling" } },
{ "range": { "rating": { "gte": 4.5 } } }
]
}
}
]
}
}
}Bu sorgu:
must: "kulaklık" araması (scoring)
filter: Stokta olmalı VE (Sony VEYA Apple VEYA Samsung) — filter context
should: noise-cancelling VE rating 4.5+ ise bonus skor — opsiyonel
Named Queries — Sorgu Etiketleme
Hangi koşulların eşleştiğini görmek için sorguları isimlendirmek mümkün:
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": {
"query": "laptop",
"_name": "name_match"
}
}
}
],
"filter": [
{
"term": {
"in_stock": {
"value": true,
"_name": "stock_filter"
}
}
},
{
"range": {
"price": {
"gte": 5000,
"lte": 50000,
"_name": "price_filter"
}
}
}
],
"should": [
{
"term": {
"brand.keyword": {
"value": "Apple",
"_name": "apple_boost"
}
}
}
]
}
}
}
// Yanıtta her hit'in matched_queries alanı var:
// "matched_queries": ["name_match", "stock_filter", "price_filter", "apple_boost"]
// Hangi koşulların eşleştiğini görebilirsinNamed queries, debug ve A/B testing için çok faydalı — "neden bu döküman sonuçta?" sorusunu cevaplar.
boosting Query — Negatif Scoring
boosting query, pozitif eşleşmeleri skorlar ama belirli kriterlere uyan dökümanların skorunu düşürür (penalize eder). must_not ile tamamen hariç tutmak yerine, sonuçta tutup sıralamada aşağı itmek istersin.
GET /products/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"name": "laptop"
}
},
"negative": {
"term": {
"condition": "refurbished"
}
},
"negative_boost": 0.3
}
}
}
// Tüm laptoplar sonuçta → ama refurbished olanlar 0.3 çarpanıyla düşük skor alır
// Yeni laptoplar üstte, refurbished'lar altta — ama sonuçta varboosting vs must_not Farkı
// must_not → Tamamen hariç tut
{
"bool": {
"must": [ { "match": { "name": "laptop" } } ],
"must_not": [ { "term": { "condition": "refurbished" } } ]
}
}
// Refurbished laptoplar HİÇ görünmez
// boosting → Sonuçta tut ama aşağıda göster
{
"boosting": {
"positive": { "match": { "name": "laptop" } },
"negative": { "term": { "condition": "refurbished" } },
"negative_boost": 0.3
}
}
// Refurbished laptoplar sonuçta var ama düşük sıradaPratik Kullanım
// E-ticaret: Sponsorlu olmayan ürünleri geri çek ama göster
{
"boosting": {
"positive": {
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name^3", "description"]
}
},
"negative": {
"bool": {
"must_not": [
{ "term": { "sponsored": true } }
]
}
},
"negative_boost": 0.5
}
}
// Sponsorlu olmayanlar biraz daha aşağıda
// (Normalde sponsored=true olanlar boost edilir — burada tam tersi)dis_max Query — En İyi Eşleşen Alan
dis_max (Disjunction Max) birden fazla sorgu çalıştırır ve en yüksek skoru alan sorgunun skorunu kullanır. multi_match'in best_fields tipi aslında dis_max'ı kullanır.
GET /products/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "name": "kablosuz kulaklık" } },
{ "match": { "description": "kablosuz kulaklık" } },
{ "match": { "tags": "kablosuz kulaklık" } }
]
}
}
}
// Her döküman için 3 sorgu çalışır
// En yüksek skoru alan sorgunun skoru = dökümanın skorutie_breaker
Varsayılan olarak dis_max sadece en yüksek skoru kullanır. tie_breaker ile diğer sorguların da katkı yapmasını sağla:
{
"dis_max": {
"queries": [
{ "match": { "name": "kablosuz kulaklık" } },
{ "match": { "description": "kablosuz kulaklık" } }
],
"tie_breaker": 0.3
}
}
// Skor = max(name_score, description_score)
// + 0.3 × min(name_score, description_score)
// Örnek:
// Doc A: name=5.0, description=2.0 → Skor: 5.0 + 0.3×2.0 = 5.6
// Doc B: name=3.0, description=3.5 → Skor: 3.5 + 0.3×3.0 = 4.4
// Doc A kazanır (tek alanda güçlü eşleşme > iki alanda orta eşleşme)dis_max vs bool(should) Farkı
// dis_max → En iyi alan kazanır
// name: 5.0, description: 2.0 → Skor: 5.0 (veya 5.6 tie_breaker ile)
// bool should → Tüm skorlar toplanır
// name: 5.0, description: 2.0 → Skor: 7.0
// Hangisini seçmeli?
// Tek alanda güçlü eşleşme önemliyse → dis_max
// Birden fazla alanda eşleşme önemliyse → bool shouldconstant_score Query — Sabit Skor
Scoring'i tamamen devre dışı bırak veya sabit bir skor ver:
GET /products/_search
{
"query": {
"constant_score": {
"filter": {
"term": { "category.keyword": "Laptop" }
},
"boost": 1.0
}
}
}
// Tüm laptop'lar aynı skoru (1.0) alır
// Filter context'te çalışır — cache'lenirNeden constant_score?
Scoring gereksizse: Sadece filtreleme yapıyorsun, sıralama başka bir alana göre
Performans: Scoring hesaplama maliyeti yok
Öngörülebilirlik: Her eşleşen döküman aynı skoru alır
// Pratik kullanım: Filtreye sabit skor vermek
{
"bool": {
"must": [
{ "match": { "name": "laptop" } }
],
"should": [
{
"constant_score": {
"filter": { "term": { "featured": true } },
"boost": 10
}
}
]
}
}
// laptop araması + öne çıkan ürünlere +10 sabit bonusfunction_score Query — Gelişmiş Scoring
BM25'in ötesinde özel scoring logic'i tanımla:
GET /products/_search
{
"query": {
"function_score": {
"query": {
"match": { "name": "laptop" }
},
"functions": [
{
"filter": { "term": { "brand.keyword": "Apple" } },
"weight": 2
},
{
"field_value_factor": {
"field": "rating",
"modifier": "log1p",
"factor": 2
}
},
{
"gauss": {
"price": {
"origin": 30000,
"scale": 15000
}
}
}
],
"score_mode": "multiply",
"boost_mode": "multiply"
}
}
}Bu sorgu:
Base query: "laptop" match → BM25 skoru
Apple boost: Apple markaysa ×2
Rating boost: Yüksek rating → daha yüksek skor (logaritmik)
Price decay: 30K civarı ideal, uzaklaştıkça skor düşer (Gaussian)
Function Score Parametreleri
| Parametre | Açıklama | Değerler |
|---|---|---|
score_mode | Fonksiyonların birbirleriyle nasıl birleşeceği | multiply, sum, avg, first, max, min |
boost_mode | Fonksiyon sonucu ile query skoru nasıl birleşecek | multiply, replace, sum, avg, max, min |
max_boost | Fonksiyonlardan gelen boost'un üst limiti | Sayısal değer |
min_score | Minimum skor eşiği — altındakiler sonuçta çıkmaz | Sayısal değer |
Yaygın Function Tipleri
// 1. weight — Sabit çarpan
{ "filter": { "term": { "premium": true } }, "weight": 3 }
// 2. field_value_factor — Alan değeri ile çarpma
{
"field_value_factor": {
"field": "popularity",
"modifier": "log1p", // log(1 + x), ln, log2p, square, sqrt, reciprocal
"factor": 0.5,
"missing": 1 // Alan yoksa varsayılan değer
}
}
// 3. script_score — Özel hesaplama
{
"script_score": {
"script": {
"source": "Math.log(2 + doc['likes'].value) * params.factor",
"params": { "factor": 1.5 }
}
}
}
// 4. decay functions — Mesafe/zaman bazlı azalma
{
"gauss": { // gaussian, linear, exp
"created_at": {
"origin": "now",
"scale": "7d",
"offset": "1d",
"decay": 0.5
}
}
}
// Yeni ürünler daha yüksek skor — son 7 günde decay 0.5Gerçek Dünya: E-Ticaret Arama Algoritması
Tüm compound query'leri birleştiren kapsamlı bir e-ticaret arama:
GET /products/_search
{
"query": {
"function_score": {
"query": {
"bool": {
"must": [
{
"dis_max": {
"queries": [
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name^5", "brand^3"],
"type": "best_fields",
"fuzziness": "AUTO"
}
},
{
"match_phrase": {
"name": {
"query": "kablosuz kulaklık",
"boost": 3
}
}
}
],
"tie_breaker": 0.3
}
}
],
"filter": [
{ "term": { "in_stock": true } },
{ "term": { "active": true } },
{ "range": { "price": { "gte": 500, "lte": 15000 } } }
],
"should": [
{ "term": { "tags": { "value": "bestseller", "boost": 2 } } },
{ "range": { "rating": { "gte": 4.5, "boost": 1.5 } } }
],
"must_not": [
{ "term": { "status": "discontinued" } }
]
}
},
"functions": [
{
"field_value_factor": {
"field": "sales_count",
"modifier": "log1p",
"factor": 0.1,
"missing": 0
}
},
{
"gauss": {
"created_at": {
"origin": "now",
"scale": "30d",
"decay": 0.5
}
}
},
{
"filter": { "term": { "sponsored": true } },
"weight": 1.5
}
],
"score_mode": "multiply",
"boost_mode": "multiply"
}
},
"_source": ["name", "brand", "price", "rating", "thumbnail"],
"size": 20
}Bu sorgunun katmanları:
dis_max: multi_match + match_phrase — en iyi eşleşen strateji kazanır
bool filter: Stok, aktiflik, fiyat — cache'lenebilir filtreler
bool should: Bestseller ve yüksek rating bonus
function_score: Satış sayısı, yenilik ve sponsorluk boost'u
Java ile Compound Queries
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import java.util.Map;
class Main {
public static void main(String[] args) throws Exception {
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200)
).build();
ElasticsearchClient client = new ElasticsearchClient(
new RestClientTransport(restClient, new JacksonJsonpMapper())
);
// bool query
var response = client.search(s -> s
.index("products")
.query(q -> q
.bool(b -> b
.must(m -> m
.multiMatch(mm -> mm
.query("kablosuz kulaklık")
.fields("name^3", "description")
.fuzziness("AUTO")
)
)
.filter(f -> f
.term(t -> t.field("in_stock").value(true))
)
.filter(f -> f
.range(r -> r.field("price")
.gte(JsonData.of(500))
.lte(JsonData.of(15000))
)
)
.should(sh -> sh
.term(t -> t.field("tags").value("premium"))
)
.mustNot(mn -> mn
.term(t -> t.field("status").value("discontinued"))
)
)
)
.size(20),
Map.class
);
System.out.println("Total: " + response.hits().total().value());
for (Hit<Map> hit : response.hits().hits()) {
System.out.printf("%.3f | %s%n", hit.score(), hit.source().get("name"));
}
// boosting query
var boostResponse = client.search(s -> s
.index("products")
.query(q -> q
.boosting(b -> b
.positive(p -> p.match(m -> m.field("name").query("laptop")))
.negative(n -> n.term(t -> t.field("condition").value("refurbished")))
.negativeBoost(0.3)
)
),
Map.class
);
System.out.println("Boosting total: " + boostResponse.hits().total().value());
restClient.close();
}
}Best Practices
✅ bool query'de scoring gerektirmeyen koşulları filter'a koy — Performans farkı büyük
✅ dis_max + tie_breaker kullan — Birden fazla alanda arama yaparken doğal sıralama
✅ boosting query ile soft-exclude yap — must_not ile tamamen silmek yerine sıralamada geri at
✅ function_score ile business logic ekle — Popülerlik, yenilik, sponsorluk boost'u
✅ Named queries ile debug yap — Hangi koşullar eşleşti, kolayca gör
✅ İç içe bool query'leri okunabilir tut — Çok derin nesting'ten kaçın
Yaygın Hatalar
❌ "must + should birlikte kullanınca should OR gibi çalışmıyor"
must varken should opsiyonel olur — eşleşmese de sonuç gelir, sadece bonus skor verir. Gerçek OR istiyorsan sadece should kullan veya minimum_should_match: 1 ekle.
❌ "Tüm filtreleri must'a koyuyorum"
must'taki her koşul scoring hesaplamasına dahil olur. Exact match ve range gibi filtreler filter'a koy — cache'lenir, daha hızlı.
❌ "function_score çok karmaşık, kullanmıyorum"
Basit weight fonksiyonu bile çok işe yarar. Sponsored ürünlere ×1.5, yeni ürünlere ×1.2 vermek bile kullanıcı deneyimini iyileştirir.
❌ "dis_max ile bool should'ın farkını bilmiyorum"
dis_max: En iyi alan kazanır (tek alanda güçlü eşleşme ödüllendirilir). bool should: Tüm alanların skorları toplanır (çok alanda eşleşme ödüllendirilir).
❌ "negative_boost'u 0 yapıyorum"
negative_boost: 0 dökümanı 0 skorla döndürür — sonuçta hâlâ var ama anlamsız. Tamamen hariç tutmak istiyorsan must_not kullan. negative_boost 0.1-0.5 arası makul.
Özet
bool query dört bölümden oluşur: must (AND+scoring), should (OR+bonus), filter (AND, no scoring), must_not (NOT, no scoring)
should davranışı kontekste bağlıdır: must/filter varken opsiyonel, tek başınayken minimum 1 zorunlu
boosting query, eşleşen ama istenmeyen dökümanların skorunu düşürür — tamamen silmez
dis_max query, birden fazla sorgudan en yüksek skoru alır —
tie_breakerile diğerlerinin de katkı yapmasını sağlaconstant_score, filter context'te sabit skor verir — scoring gerekmediğinde kullan
function_score, BM25'in ötesinde popülerlik, yenilik, coğrafi yakınlık gibi business logic'le scoring özelleştirir
Named queries ile hangi koşulların eşleştiğini debug edebilirsin
Bu bölümü tamamladın! Artık Elasticsearch'te temel arama sorgularını yazabilirsin. Bir sonraki bölümde Mapping ve Analiz konusuna derinlemesine dalacağız — Custom Analyzer, Türkçe arama ve Synonym desteği!
AI Asistan
Sorularını yanıtlamaya hazır