← Kursa Dön
📄 Text · 30 min

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 skoru

should — 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 çıkar

Kural 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şmeli

minimum_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ĞİL

must_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:

  1. must: "kulaklık" araması (scoring)

  2. filter: Stokta olmalı VE (Sony VEYA Apple VEYA Samsung) — filter context

  3. 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örebilirsin

Named 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 var

boosting 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ırada

Pratik 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 skoru

tie_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 should

constant_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'lenir

Neden constant_score?

  1. Scoring gereksizse: Sadece filtreleme yapıyorsun, sıralama başka bir alana göre

  2. Performans: Scoring hesaplama maliyeti yok

  3. Ö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 bonus

function_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:

  1. Base query: "laptop" match → BM25 skoru

  2. Apple boost: Apple markaysa ×2

  3. Rating boost: Yüksek rating → daha yüksek skor (logaritmik)

  4. Price decay: 30K civarı ideal, uzaklaştıkça skor düşer (Gaussian)

Function Score Parametreleri

ParametreAçıklamaDeğerler
score_modeFonksiyonların birbirleriyle nasıl birleşeceğimultiply, sum, avg, first, max, min
boost_modeFonksiyon sonucu ile query skoru nasıl birleşecekmultiply, replace, sum, avg, max, min
max_boostFonksiyonlardan gelen boost'un üst limitiSayısal değer
min_scoreMinimum skor eşiği — altındakiler sonuçta çıkmazSayı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.5

Gerç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ı:

  1. dis_max: multi_match + match_phrase — en iyi eşleşen strateji kazanır

  2. bool filter: Stok, aktiflik, fiyat — cache'lenebilir filtreler

  3. bool should: Bestseller ve yüksek rating bonus

  4. 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_breaker ile diğerlerinin de katkı yapmasını sağla

  • constant_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!