EElasticsearch Handbook

ORTA

Query DSL

Elasticsearch'ün sorgu dili JSON tabanlıdır. İki ana kategori vardır: Query context (relevance score hesaplar) ve Filter context (yes/no, cache'lenir, daha hızlı).

Kod örneği tercihiBu sayfadaki istemci örneklerini birlikte değiştirir.
Query Context (Scoring) match multi_match match_phrase query_string bool (must/should) function_score _score hesaplanır, cache'lenmez Full-text arama, relevance ranking Filter Context (No Score) term / terms range exists bool (filter/must_not) ids prefix / wildcard _score = 0, cache'lenir Exact match, filtering, daha hızlı

Karar Rehberi

DurumÖneriÖrnek veya gerekçe
match Uygun: Full-text arama Ürün arama kutusu
term Uygun: Keyword exact match Status filtre, enum
bool Uygun: Karmaşık kombinasyonlar Filtre + arama + boost
range Uygun: Sayısal/tarih aralık Fiyat: 100-500 TL
match_phrase Uygun: Sıralı kelime eşleşmesi "kırmızı spor ayakkabı"
multi_match Uygun: Birden fazla field name + description arama
function_score Uygun: Custom ranking Popülerlik boost

Bool Query Yapısı

# Kompleks bool query: e-ticaret ürün arama
curl -X GET "http://localhost:9200/products/_search" -H "Content-Type: application/json" -d'
{
  "query": {
    "bool": {
      "must": [
        { "multi_match": {
            "query": "spor ayakkabı",
            "fields": ["name^3", "description", "tags^2"],
            "type": "best_fields",
            "fuzziness": "AUTO"
        }}
      ],
      "filter": [
        { "term": { "is_active": true } },
        { "range": { "price": { "gte": 500, "lte": 5000 } } },
        { "terms": { "category": ["spor-ayakkabi", "sneaker"] } }
      ],
      "should": [
        { "term": { "brand": { "value": "nike", "boost": 2.0 } } },
        { "range": { "stock": { "gte": 10 } } }
      ],
      "must_not": [
        { "term": { "status": "discontinued" } }
      ],
      "minimum_should_match": 1
    }
  },
  "highlight": {
    "fields": { "name": {}, "description": {} }
  },
  "sort": [
    { "_score": "desc" },
    { "price": "asc" }
  ],
  "from": 0,
  "size": 20,
  "_source": ["name", "price", "category", "brand"]
}'
public async Task<SearchResult<Product>> SearchProductsAsync(ProductSearchRequest request)
{
    var response = await _client.SearchAsync<Product>(s => s
        .Index("products")
        .From(request.Page * request.PageSize)
        .Size(request.PageSize)
        .Query(q => q
            .Bool(b =>
            {
                b.Must(mu => mu
                    .MultiMatch(mm => mm
                        .Query(request.Query)
                        .Fields(new[] { "name^3", "description", "tags^2" })
                        .Type(TextQueryType.BestFields)
                        .Fuzziness(new Fuzziness("AUTO"))));

                b.Filter(
                    f => f.Term(t => t.Field(p => p.IsActive).Value(true)),
                    f => f.Range(r => r.NumberRange(nr => nr
                        .Field(p => p.Price)
                        .Gte(request.MinPrice)
                        .Lte(request.MaxPrice))),
                    f => f.Terms(t => t
                        .Field(p => p.Category)
                        .Terms(new TermsQueryField(
                            request.Categories.Select(c => FieldValue.String(c)).ToArray())))
                );

                b.Should(
                    sh => sh.Term(t => t
                        .Field(p => p.Brand)
                        .Value(request.PreferredBrand)
                        .Boost(2.0f)),
                    sh => sh.Range(r => r.NumberRange(nr => nr
                        .Field(p => p.Stock).Gte(10)))
                );

                b.MustNot(mn => mn
                    .Term(t => t.Field("status").Value("discontinued")));

                b.MinimumShouldMatch(1);
                return b;
            }))
        .Highlight(h => h
            .Fields(f => f
                .Add("name", new HighlightField())
                .Add("description", new HighlightField())))
        .Sort(so => so
            .Score(ScoreSortOrder.Desc)
            .Field(p => p.Price, SortOrder.Asc))
        .SourceIncludes(new[] { "name", "price", "category", "brand" }));

    return new SearchResult<Product>(
        response.Documents.ToList(),
        response.Total,
        response.Took);
}

Örnek: E-ticaret arama kutusunda kullanıcı "nike kırmızı 42" yazar. Bool query: must=multi_match("nike kırmızı 42"), filter=[is_active:true, stock>0], should=[brand:nike boost:3]. Filter cache'lenir, must scoring yapar. Sonuç: hızlı + alakalı.

term vs match: term query text field'larda ÇALIŞMAZ çünkü text field'lar analyzed'dır (lowercase vb.). Keyword field'larda term, text field'larda match kullanın. Bu en yaygın ES hatasıdır.

Anti-Pattern: term query on text field

# BU ÇALIŞMAZ! "Nike Air Max" text field'da analyzed → "nike", "air", "max" token'larına dönüşür
# term query exact match arar → "Nike Air Max" bulunamaz
curl -X GET "http://localhost:9200/products/_search" -H "Content-Type: application/json" -d'
{
  "query": {
    "term": { "name": "Nike Air Max" }
  }
}'
# Sonuç: 0 hit (çünkü inverted index'te "Nike Air Max" yok, "nike" var)
# Text field için: match query (analyzed, tokenize edilir)
curl -X GET "http://localhost:9200/products/_search" -H "Content-Type: application/json" -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "Nike Air Max" } }
      ],
      "filter": [
        { "term": { "category": "spor-ayakkabi" } },
        { "term": { "is_active": true } }
      ]
    }
  }
}'
# match → text field (analyzed) ✅
# term → keyword field (not analyzed) ✅