Jak szukać części słowa za pomocą ElasticSearch


133

Niedawno zacząłem używać ElasticSearch i nie mogę zmusić go do wyszukania części słowa.

Przykład: Mam trzy dokumenty z mojej couchdb zindeksowane w ElasticSearch:

{
  "_id" : "1",
  "name" : "John Doeman",
  "function" : "Janitor"
}
{
  "_id" : "2",
  "name" : "Jane Doewoman",
  "function" : "Teacher"
}
{
  "_id" : "3",
  "name" : "Jimmy Jackal",
  "function" : "Student"
} 

Więc teraz chcę wyszukać wszystkie dokumenty zawierające „Doe”

curl http://localhost:9200/my_idx/my_type/_search?q=Doe

To nie zwraca żadnych trafień. Ale jeśli szukam

curl http://localhost:9200/my_idx/my_type/_search?q=Doeman

Zwraca jeden dokument (John Doeman).

Próbowałem ustawić różne analizatory i różne filtry jako właściwości mojego indeksu. Próbowałem również użyć pełnego zapytania (na przykład:

{
  "query": {
    "term": {
      "name": "Doe"
    }
  }
}

) Ale wydaje się, że nic nie działa.

Jak sprawić, by ElasticSearch wyszukał zarówno John Doeman, jak i Jane Doewoman, gdy wyszukuję „Doe”?

AKTUALIZACJA

Próbowałem użyć tokenizera i filtra nGram, jak zaproponował Igor, w ten sposób:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "bulk_size": "100",
    "bulk_timeout": "10ms",
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "my_ngram_tokenizer",
          "filter": [
            "my_ngram_filter"
          ]
        }
      },
      "filter": {
        "my_ngram_filter": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "nGram",
          "min_gram": 1,
          "max_gram": 1
        }
      }
    }
  }
}

Problem, który mam teraz, polega na tym, że każde zapytanie zwraca WSZYSTKIE dokumenty. Jakieś wskazówki? Dokumentacja ElasticSearch dotycząca korzystania z nGram nie jest świetna ...


10
nic dziwnego, ustawisz min / max ngram na 1, czyli 1 literę :)
Martin B.

Odpowiedzi:


86

Ja też używam nGram. Używam standardowego tokenizera i nGram tylko jako filtra. Oto moja konfiguracja:

{
  "index": {
    "index": "my_idx",
    "type": "my_type",
    "analysis": {
      "index_analyzer": {
        "my_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "mynGram"
          ]
        }
      },
      "search_analyzer": {
        "my_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "standard",
            "lowercase",
            "mynGram"
          ]
        }
      },
      "filter": {
        "mynGram": {
          "type": "nGram",
          "min_gram": 2,
          "max_gram": 50
        }
      }
    }
  }
}

Znajdźmy części słów do 50 liter. Dostosuj max_gram według potrzeb. Po niemiecku może być naprawdę duży, więc ustawiłem go na wysoką wartość.



Czy to jest to, co otrzymujesz z ustawień indeksu, czy to jest to, co wysyłasz do Elasticsearch, aby go skonfigurować?
Tomas Jansson

To test POST do skonfigurowania Elasticsearch.
roka

Nie jestem mocny z aktualnymi wersjami Elasticsearch, ale należy wspomnieć o tym w docs: elastic.co/guide/en/elasticsearch/reference/current/index.html
roka

1
@JimC Nie korzystałem z ElasticSearch od co najmniej 7 lat, więc nie znam aktualnych zmian w projekcie.
roka

64

Wyszukiwanie za pomocą wiodących i końcowych symboli wieloznacznych będzie bardzo powolne w przypadku dużego indeksu. Jeśli chcesz mieć możliwość wyszukiwania według prefiksu słowa, usuń wiodący symbol wieloznaczny. Jeśli naprawdę potrzebujesz znaleźć podciąg w środku słowa, lepiej byłoby użyć tokenizera ngram.


15
Igor ma rację. Przynajmniej usuń wiodące *. Przykład NGram
karmi

3
@karmi: Dzięki za pełny przykład! Być może chcesz dodać swój komentarz jako rzeczywistą odpowiedź, to właśnie sprawiło, że działa dla mnie i co chciałbym zaopiniować.
Fabian Steeg

55

Myślę, że nie ma potrzeby zmieniać żadnego mapowania. Spróbuj użyć query_string , jest doskonały. Wszystkie scenariusze będą działać z domyślnym standardowym analizatorem:

Posiadamy dane:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

Scenariusz 1:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Doe*"}
} }

Odpowiedź:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

Scenariusz 2:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*Jan*"}
} }

Odpowiedź:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}

Scenariusz 3:

{"query": {
    "query_string" : {"default_field" : "name", "query" : "*oh* *oe*"}
} }

Odpowiedź:

{"_id" : "1","name" : "John Doeman","function" : "Janitor"}
{"_id" : "2","name" : "Jane Doewoman","function" : "Teacher"}

EDYCJA - ta sama implementacja z elastycznym wyszukiwaniem danych sprężynowych https://stackoverflow.com/a/43579948/2357869

Jeszcze jedno wyjaśnienie, w jaki sposób query_string jest lepszy niż inne https://stackoverflow.com/a/43321606/2357869


3
myślę, że to jest najłatwiejsze
Esgi Dendyanri

Tak . Wdrożyłem w swoim projekcie.
Opster Elasticsearch Pro-Vijay

Jak uwzględnić wiele pól do wyszukiwania?
Shubham A.

spróbuj tego: - {"query": {"query_string": {"fields": ["content", "name"], "query": "this AND that"}}}
Opster Elasticsearch Pro-Vijay


14

bez zmiany mapowania indeksu możesz wykonać proste zapytanie przedrostkowe, które będzie wykonywać częściowe wyszukiwania, na które masz nadzieję

to znaczy.

{
  "query": { 
    "prefix" : { "name" : "Doe" }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-prefix-query.html


czy możesz wyszukiwać w wielu polach za pomocą zapytania prefiksu?
Emil

Dzięki, właśnie tego szukałem! Jakieś przemyślenia na temat wpływu na wydajność?
Vingtoft

6

Wypróbuj rozwiązanie za pomocą opisanego tutaj: Dokładne wyszukiwanie podłańcuchów w ElasticSearch

{
    "mappings": {
        "my_type": {
            "index_analyzer":"index_ngram",
            "search_analyzer":"search_ngram"
        }
    },
    "settings": {
        "analysis": {
            "filter": {
                "ngram_filter": {
                    "type": "ngram",
                    "min_gram": 3,
                    "max_gram": 8
                }
            },
            "analyzer": {
                "index_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": [ "ngram_filter", "lowercase" ]
                },
                "search_ngram": {
                    "type": "custom",
                    "tokenizer": "keyword",
                    "filter": "lowercase"
                }
            }
        }
    }
}

Aby rozwiązać problem użycia dysku i problem ze zbyt długim terminem wyszukiwania, używane są krótkie 8-znakowe ngramy (skonfigurowane z: "max_gram": 8 ). Aby wyszukać terminy zawierające więcej niż 8 znaków, zamień swoje wyszukiwanie na zapytanie logiczne ORAZ wyszukujące każdy odrębny 8-znakowy podciąg w tym ciągu. Na przykład, jeśli użytkownik szukał dużego podwórka (10-znakowy ciąg), wyszukiwanie wyglądałoby tak:

„arge ya AND arge yar AND rge yard .


3
martwy link, pls fix
DarkMukke

Od jakiegoś czasu szukałem czegoś takiego. Dziękuję Ci! Czy wiesz, jak skaluje się pamięć z min_grami max_gramwydaje się, że będzie to zależne liniowo od rozmiaru wartości pól i zakresu mini max. Jak niezadowolony jest z używania czegoś takiego?
Glen Thompson

Czy jest też jakiś powód, dla którego ngramjest to filtr ponad tokenizerem? czy nie mógłbyś po prostu mieć go jako tokenizera, a następnie zastosować filtr z małych liter ... index_ngram: { type: "custom", tokenizer: "ngram_tokenizer", filter: [ "lowercase" ] }Próbowałem i wydaje się, że daje te same wyniki przy użyciu interfejsu testowego analizatora
Glen Thompson

2

Jeśli chcesz zaimplementować funkcję autouzupełniania, sugestia ukończenia jest najbardziej zgrabnym rozwiązaniem. Następny wpis na blogu zawiera bardzo jasny opis, jak to działa.

Krótko mówiąc, jest to struktura danych w pamięci zwana FST, która zawiera ważne sugestie i jest zoptymalizowana pod kątem szybkiego pobierania i wykorzystania pamięci. Zasadniczo jest to tylko wykres. Na przykład, i FST zawierającego słowa hotel, marriot, mercure, muncheni munichbędzie wyglądać następująco:

wprowadź opis obrazu tutaj


2

możesz użyć wyrażenia regularnego.

{ "_id" : "1", "name" : "John Doeman" , "function" : "Janitor"}
{ "_id" : "2", "name" : "Jane Doewoman","function" : "Teacher"  }
{ "_id" : "3", "name" : "Jimmy Jackal" ,"function" : "Student"  } 

jeśli używasz tego zapytania:

{
  "query": {
    "regexp": {
      "name": "J.*"
    }
  }
}

podasz wszystkie dane, których nazwa zaczyna się na literę „J”. Zastanów się, czy chcesz otrzymać tylko pierwsze dwa rekordy, które kończą się na „man”, więc możesz użyć tego zapytania:

{
  "query": { 
    "regexp": {
      "name": ".*man"
    }
  }
}

a jeśli chcesz otrzymać wszystkie rekordy, które w ich imieniu istnieją „m”, możesz użyć tego zapytania:

{
  "query": { 
    "regexp": {
      "name": ".*m.*"
    }
  }
}

To działa dla mnie. Mam nadzieję, że moja odpowiedź będzie odpowiednia do rozwiązania twojego problemu.


1

Używanie wilcards (*) zapobiega obliczaniu wyniku


1
Czy mógłbyś dodać więcej szczegółów do swojej odpowiedzi? Podaj przykładowy kod lub odniesienie do dokumentacji opisującej, co to robi.
Cray

1

Używam tego i udało mi się

"query": {
        "query_string" : {
            "query" : "*test*",
            "fields" : ["field1","field2"],
            "analyze_wildcard" : true,
            "allow_leading_wildcard": true
        }
    }

-6

Nieważne.

Musiałem zajrzeć do dokumentacji Lucene. Wygląda na to, że mogę używać symboli wieloznacznych! :-)

curl http://localhost:9200/my_idx/my_type/_search?q=*Doe*

Zrób sztuczkę!


11
Zobacz odpowiedź @imotov. Używanie symboli wieloznacznych wcale nie będzie dobrze skalowane.
Mike Munroe

5
@Idx - zobacz, jak Twoja własna odpowiedź jest odrzucana. Głosy przeciwne reprezentują jakość i trafność odpowiedzi. Czy mógłbyś poświęcić chwilę, aby zaakceptować właściwą odpowiedź? Przynajmniej nowi użytkownicy byliby ci wdzięczni.
asyncwait

3
Wystarczająco dużo głosów przeciw. OP wyjaśnił, jaka jest teraz najlepsza odpowiedź. +1 za udostępnienie tego, co wydawało się być najlepszą odpowiedzią, zanim ktoś opublikuje lepszą.
Daniel
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.