Full-Text Queries — match, multi_match, match_phrase
match, multi_match, match_phrase
Google'da arama yaparken ne olur? "En iyi Java kitabı" yazarsın, Google bunu kelimelere ayırır, her kelimeyi milyarlarca sayfada arar ve en alakalı sonuçları döner. "En iyi" yazmana rağmen "Java Programlama — Kapsamlı Rehber" çıkar çünkü Google anlamsal yakınlığı değerlendirir. Elasticsearch'ün full-text query'leri de tam olarak bunu yapar.
Full-text query'ler, arama terimini analiz eder (tokenize + normalize), sonra inverted index'te eşleşen dökümanları bulur ve relevance scoring ile sıralar. Bu derste match, multi_match ve match_phrase sorgularını tüm detaylarıyla öğreneceğiz.
match Query — Temel Full-Text Arama
En sık kullanılan sorgu tipi. Verdiğin metni analiz eder, çıkan token'ları inverted index'te arar.
Basit match
GET /products/_search
{
"query": {
"match": {
"name": "kablosuz kulaklık"
}
}
}Perde arkasında:
"kablosuz kulaklık" → Analiz → ["kablosuz", "kulaklık"]
Inverted index'te "kablosuz" ve "kulaklık" terimlerini ara
Eşleşen dökümanları BM25 ile skorla
Varsayılan operatör OR → "kablosuz" VEYA "kulaklık" içeren tüm dökümanlar döner
operator Parametresi
// Varsayılan: OR — herhangi bir kelime eşleşse yeter
GET /products/_search
{
"query": {
"match": {
"name": {
"query": "kablosuz kulaklık",
"operator": "or" // Varsayılan
}
}
}
}
// "kablosuz mouse" da sonuçta çıkar (sadece "kablosuz" eşleşti)
// AND operatörü — tüm kelimeler eşleşmeli
GET /products/_search
{
"query": {
"match": {
"name": {
"query": "kablosuz kulaklık",
"operator": "and"
}
}
}
}
// Sadece HEM "kablosuz" HEM "kulaklık" içeren dökümanlarminimum_should_match
OR operatörü çok gevşek, AND çok katı. Ortayolu minimum_should_match ile bul:
// En az 2 kelime eşleşmeli (3 kelimelik sorguda)
GET /products/_search
{
"query": {
"match": {
"description": {
"query": "hafif dayanıklı kablosuz kulaklık",
"minimum_should_match": "75%"
}
}
}
}
// 4 kelimeden en az 3'ü (75%) eşleşmeli
// Sayısal değer de olabilir
{
"match": {
"description": {
"query": "hafif dayanıklı kablosuz kulaklık",
"minimum_should_match": 2 // En az 2 kelime
}
}
}fuzziness — Yazım Hatası Toleransı
Kullanıcılar yazım hatası yapar: "laptob", "kulalık", "elektonik". Fuzzy matching bunu tolere eder:
GET /products/_search
{
"query": {
"match": {
"name": {
"query": "laptob", // Yanlış yazım
"fuzziness": "AUTO" // Otomatik fuzzy
}
}
}
}
// "laptop" sonuçlarını bulur!fuzziness değerleri:
0— Exact match (fuzzy kapalı)1— 1 karakter farkı tolere et (ekleme, silme, değiştirme, yer değiştirme)2— 2 karakter farkı tolere etAUTO— Kelime uzunluğuna göre otomatik:
- 0-2 karakter → 0 (fuzzy yok) - 3-5 karakter → 1 edit distance - 6+ karakter → 2 edit distance
// AUTO en yaygın ve önerilen seçenek
{
"match": {
"name": {
"query": "elektronik", // "elektronik" → doğru
"fuzziness": "AUTO"
}
}
}
// prefix_length ile ilk N karakterin doğru olmasını zorunlu kıl
{
"match": {
"name": {
"query": "elektonik",
"fuzziness": "AUTO",
"prefix_length": 2 // İlk 2 karakter doğru olmalı: "el..."
}
}
}analyzer Parametresi
Sorgu analizinde kullanılacak analyzer'ı belirleyebilirsin:
// Varsayılan: index mapping'deki analyzer
{
"match": {
"name": {
"query": "LAPTOP",
"analyzer": "standard" // Explicit analyzer
}
}
}
// "LAPTOP" → standard analyzer → "laptop" → inverted index'te arazero_terms_query
Tüm kelimeler stop word ise ne olur?
// "ve ile bir" → Türkçe analyzer stop word'leri kaldırır → boş sorgu
{
"match": {
"description": {
"query": "ve ile bir",
"zero_terms_query": "all" // Tüm kelimeler stop word → match_all gibi davran
}
}
}
// zero_terms_query: "none" (varsayılan) → sonuç yok
// zero_terms_query: "all" → match_all gibi davranlenient — Tip Uyumsuzluğunu Tolere Et
// Numeric field'a text sorgusu gönderirsen normalde hata alırsın
// lenient: true ile hatayı tolere et
{
"match": {
"price": {
"query": "ucuz",
"lenient": true // Hata yerine sıfır sonuç dön
}
}
}multi_match Query — Birden Fazla Alanda Arama
Aynı sorguyu birden fazla alanda aramak için:
GET /products/_search
{
"query": {
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name", "description", "tags"]
}
}
}Field Boosting
// Başlıktaki eşleşme daha değerli
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name^3", "description^1", "tags^2"]
}
}
// name alanında eşleşen döküman 3 kat skor, tags 2 katmulti_match Tipleri
multi_match'in farklı skorlama stratejileri var:
1. best_fields (varsayılan)
En iyi eşleşen alanın skorunu kullanır. "Bu dökümanın EN ALAKALI alanı hangisi?"
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name", "description"],
"type": "best_fields" // Varsayılan
}
}
// name'de 5.0 skor, description'da 2.0 skor → Sonuç: 5.0
// En yüksek alan skoru kullanılır2. most_fields
Tüm alanların skorlarını toplar. "Bu döküman kaç alanda eşleşiyor?"
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name", "description", "tags"],
"type": "most_fields"
}
}
// name: 5.0, description: 2.0, tags: 1.5 → Sonuç: 8.5
// Daha fazla alanda eşleşen döküman daha yüksek skor alır3. cross_fields
Kelimeler farklı alanlara dağılmış olabilir. "Ahmet Yılmaz" aramasında "Ahmet" first_name'de, "Yılmaz" last_name'de olabilir.
{
"multi_match": {
"query": "Ahmet Yılmaz",
"fields": ["first_name", "last_name"],
"type": "cross_fields",
"operator": "and"
}
}
// "Ahmet" first_name'de VE "Yılmaz" last_name'de → Eşleşir!
// best_fields ile bu çalışmazdı — her kelime aynı alanda aranırdı4. phrase ve phrase_prefix
// phrase — match_phrase gibi davranır
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name", "description"],
"type": "phrase"
}
}
// phrase_prefix — autocomplete için
{
"multi_match": {
"query": "kablosuz kul",
"fields": ["name", "description"],
"type": "phrase_prefix"
}
}
// "kablosuz kul..." ile başlayan ifadeleri bulurHangi Tipi Kullanmalıyım?
| Tip | Kullanım | Örnek |
|---|---|---|
best_fields | Genel arama — en iyi alan kazanır | Ürün araması |
most_fields | Çok alanlı içerik — hepsinde eşleşme önemli | Makale araması |
cross_fields | Alanlar birleşik anlam taşıyor | İsim + soyisim |
phrase | Tam ifade araması — sıra önemli | Slogan, alıntı |
phrase_prefix | Autocomplete — yazmaya devam ederken | Arama çubuğu |
Wildcard Field Adları
// Tüm alanların hepsinde ara
{
"multi_match": {
"query": "laptop",
"fields": ["*"] // TÜM alanlar
}
}
// Belirli pattern'e uyan alanlarda
{
"multi_match": {
"query": "laptop",
"fields": ["*_name", "description*"]
}
}tie_breaker
best_fields'ta sadece en iyi alan kullanılır. Diğer alanların da bir miktar katkı sağlamasını istersen:
{
"multi_match": {
"query": "kablosuz kulaklık",
"fields": ["name^3", "description"],
"type": "best_fields",
"tie_breaker": 0.3
}
}
// Skor = en iyi alan skoru + 0.3 × (diğer alanların skorları toplamı)
// tie_breaker: 0.0 → sadece best field (varsayılan)
// tie_breaker: 1.0 → tüm alanların toplamı (most_fields gibi)
// tie_breaker: 0.3 → iyi bir orta yolmatch_phrase Query — Tam İfade Eşleşmesi
match query kelimeleri bağımsız arar. match_phrase ise kelimelerin sırasını ve bitişikliğini korur.
// match: "hızlı arama" → "hızlı" VEYA "arama"
// Sonuçlar: "Hızlı ve güvenilir arama motoru", "Arama optimizasyonu hızlı sonuç"
// match_phrase: "hızlı arama" → "hızlı" ve "arama" yan yana ve bu sırada
// Sonuçlar: "Hızlı arama motoru" ✅, "Arama hızlı yapılır" ❌ (sıra yanlış)GET /products/_search
{
"query": {
"match_phrase": {
"description": "kablosuz bluetooth kulaklık"
}
}
}
// "kablosuz bluetooth kulaklık" ifadesi tam olarak bu sırada geçen dökümanlarslop Parametresi — Kelimeler Arası Mesafe
Kelimelerin tam bitişik olması çok katı olabilir. slop ile aralarına kaç kelime girebileceğini belirle:
{
"match_phrase": {
"description": {
"query": "kablosuz kulaklık",
"slop": 1
}
}
}
// Eşleşir: "kablosuz kulaklık" (slop 0)
// Eşleşir: "kablosuz bluetooth kulaklık" (slop 1 — arada 1 kelime)
// Eşleşmez: "kablosuz aktif gürültü engelleyici kulaklık" (slop 3 gerekir)
// slop 2 ile daha esnek
{
"match_phrase": {
"description": {
"query": "kablosuz kulaklık",
"slop": 2
}
}
}
// "kablosuz aktif noise-cancelling kulaklık" → slop 2 ile eşleşirslop ile Kelime Sırası
slop > 0 olduğunda kelimeler yer değiştirebilir:
{
"match_phrase": {
"description": {
"query": "kulaklık kablosuz",
"slop": 2
}
}
}
// "kablosuz bluetooth kulaklık" → Eşleşir!
// Çünkü "kulaklık" ve "kablosuz" 2 pozisyon farkla yer değiştirebilir
// Ama doğru sıradaki eşleşme daha yüksek skor alırmatch_phrase_prefix — Autocomplete
// Kullanıcı yazmaya devam ederken
{
"match_phrase_prefix": {
"name": {
"query": "macbook p",
"max_expansions": 10
}
}
}
// "macbook p..." ile devam eden tüm ifadeler
// "MacBook Pro", "MacBook Pro 16", "MacBook Pro Max" — hepsi eşleşir
// max_expansions: Prefix'ten kaç farklı terime genişletilebileceği
// Düşük → daha hızlı, daha az sonuç
// Yüksek → daha yavaş, daha çok sonuç⚠️ Dikkat: match_phrase_prefix basit autocomplete için çalışır ama production'da completion suggester veya search-as-you-type field tipi önerilir. match_phrase_prefix son kelimeyi prefix olarak arar — bazı durumlarda yavaş olabilir.
query_string ve simple_query_string
query_string — Lucene Sorgu Söz Dizimi
İleri düzey kullanıcılar için güçlü ama tehlikeli:
GET /products/_search
{
"query": {
"query_string": {
"query": "(laptop OR tablet) AND brand:Apple AND price:[10000 TO 80000]",
"default_field": "name"
}
}
}query_string operatörleri:
AND,OR,NOT— boolean operatörler""— tam ifade (phrase)*— wildcard~— fuzzy (laptop~2)^— boost (laptop^3)[]— range ([10 TO 100])()— gruplama
// Karmaşık sorgu örnekleri
{
"query_string": {
"query": "name:(laptop OR \"gaming pc\") AND -brand:HP AND price:[5000 TO *]",
"default_operator": "AND"
}
}⚠️ DİKKAT: query_string, geçersiz söz diziminde hata fırlatır. Kullanıcı girdisini doğrudan query_string'e KESİNLİKLE gönderme — injection riski var.
simple_query_string — Güvenli Alternatif
query_string'in güvenli versiyonu. Geçersiz söz diziminde hata vermez, sessizce yoksayar:
GET /products/_search
{
"query": {
"simple_query_string": {
"query": "laptop + kablosuz | -HP",
"fields": ["name^3", "description"],
"default_operator": "and"
}
}
}simple_query_string operatörleri:
+→ AND|→ OR-→ NOT""→ Phrase*→ Wildcard (sonuna)~N→ Fuzzy~N(phrase sonunda) → Slop
{
"simple_query_string": {
"query": "\"kablosuz kulaklık\" + premium -ucuz",
"fields": ["name", "description", "tags"]
}
}
// "kablosuz kulaklık" ifadesi VE "premium" kelimesi olan
// AMA "ucuz" kelimesi olmayan dökümanlarKural: Kullanıcı girdisi → simple_query_string. Dahili/admin sorguları → query_string.
Gerçek Dünya: Arama Çubuğu Implementasyonu
Bir e-ticaret arama çubuğunun tam implementasyonu:
// Kullanıcı yazdı: "samsung kablosuz kulaklk" (yazım hatası var)
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "samsung kablosuz kulaklk",
"fields": [
"name^5",
"name.autocomplete^3",
"brand^4",
"description",
"category^2",
"tags^2"
],
"type": "best_fields",
"fuzziness": "AUTO",
"prefix_length": 2,
"tie_breaker": 0.3
}
}
],
"filter": [
{ "term": { "in_stock": true } },
{ "term": { "active": true } }
],
"should": [
{
"match_phrase": {
"name": {
"query": "samsung kablosuz kulaklık",
"slop": 2,
"boost": 3
}
}
}
]
}
},
"_source": ["name", "brand", "price", "rating", "thumbnail"],
"highlight": {
"fields": {
"name": {},
"description": { "fragment_size": 100 }
}
},
"size": 20
}Bu sorgudaki stratejiler:
multi_match best_fields — Genel arama, en iyi alan kazanır
fuzziness: AUTO — "kulaklk" → "kulaklık" bulunur
match_phrase should — Tam ifade eşleşmesine bonus skor
Field boosting — name > brand > category > description
tie_breaker: 0.3 — Birden fazla alanda eşleşen dökümanlara ek skor
Highlight — Eşleşen kelimeleri vurgula
Java ile Full-Text 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.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())
);
// 1. Basit match
var matchResponse = client.search(s -> s
.index("products")
.query(q -> q
.match(m -> m
.field("name")
.query("kablosuz kulaklık")
.operator(co.elastic.clients.elasticsearch._types.query_dsl.Operator.And)
.fuzziness("AUTO")
)
),
Map.class
);
// 2. multi_match
var multiMatchResponse = client.search(s -> s
.index("products")
.query(q -> q
.multiMatch(mm -> mm
.query("kablosuz kulaklık")
.fields("name^3", "description", "tags^2")
.type(co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType.BestFields)
.fuzziness("AUTO")
.tieBreaker(0.3)
)
),
Map.class
);
// 3. match_phrase
var phraseResponse = client.search(s -> s
.index("products")
.query(q -> q
.matchPhrase(mp -> mp
.field("description")
.query("kablosuz bluetooth kulaklık")
.slop(1)
)
),
Map.class
);
// 4. simple_query_string (kullanıcı girdisi için güvenli)
var simpleResponse = client.search(s -> s
.index("products")
.query(q -> q
.simpleQueryString(sqs -> sqs
.query("kablosuz + kulaklık -ucuz")
.fields("name^3", "description")
.defaultOperator(co.elastic.clients.elasticsearch._types.query_dsl.Operator.And)
)
),
Map.class
);
System.out.println("Match: " + matchResponse.hits().total().value());
System.out.println("Multi-match: " + multiMatchResponse.hits().total().value());
System.out.println("Phrase: " + phraseResponse.hits().total().value());
System.out.println("Simple QS: " + simpleResponse.hits().total().value());
for (Hit<Map> hit : multiMatchResponse.hits().hits()) {
System.out.printf("%.3f | %s%n", hit.score(), hit.source().get("name"));
}
restClient.close();
}
}Best Practices
✅ multi_match kullan — Tek field yerine birden fazla alanda ara
✅ Field boosting yap — Başlık > açıklama > tags
✅ fuzziness: AUTO kullan — Yazım hatalarını tolere et
✅ match_phrase ile phrase boost yap — Tam ifade eşleşmesine bonus skor
✅ Kullanıcı girdisi için simple_query_string kullan — query_string yerine, güvenli
✅ tie_breaker: 0.3 dene — Birden fazla alanda eşleşen dökümanlara ek skor
✅ Analyze API ile test et — Sorgunun nasıl analiz edildiğini kontrol et
Yaygın Hatalar
❌ "match query ile exact match yapmaya çalışıyorum"
match query metni analiz eder. "Apple" → "apple" olur, "MacBook Pro" → "macbook" + "pro" olur. Exact match istiyorsan term query (keyword field üzerinde) kullan.
❌ "query_string'e kullanıcı girdisini doğrudan gönderiyorum"
query_string geçersiz söz diziminde hata fırlatır. "query": "name:(" gibi bir girdi uygulamayı kırar. Kullanıcı girdisi → simple_query_string.
❌ "match_phrase çok katı, sonuç gelmiyor"
slop parametresi ekle. slop: 1 veya slop: 2 ile kelimeler arasına boşluk tanı. Veya match_phrase'i should'a koyup bonus scoring olarak kullan.
❌ "Tüm alanlarda wildcard (*) ile arıyorum"
"fields": ["*"] tüm alanları tarar — çok yavaş. İlgili alanları açıkça belirt.
❌ "fuzziness'ı 3-4 yapıyorum"
Yüksek fuzziness değeri alakasız sonuçlar getirir. "cat" ile "dog" 3 edit distance — tam kaos. AUTO kullan, 2'den fazla gerekli değil.
Özet
match query, en temel full-text arama — metni analiz eder, inverted index'te arar
operator (
or/and) ve minimum_should_match ile eşleşme katılığı ayarlanırfuzziness: AUTO yazım hatalarını tolere eder — production'da mutlaka kullan
multi_match birden fazla alanda arar —
best_fields,most_fields,cross_fieldstipleri varField boosting (
^) ile alanları önceliklendir — başlık > açıklamamatch_phrase kelimelerin sırasını ve bitişikliğini korur — slop ile esneklik ekle
simple_query_string kullanıcı girdisi için güvenli — query_string yerine kullan
Arama çubuğunda multi_match + fuzziness + match_phrase boost kombinasyonu etkili
Bir sonraki derste Term-Level Queries öğreneceğiz — term, range, exists, wildcard, regexp!
AI Asistan
Sorularını yanıtlamaya hazır