Как реализовать точное совпадение в фильтре с elasticsearch?
Я работаю над запросом на основе полей имени в Elasticsearch 2.4. Меня интересуют следующие поля:
- государство
- город
- колония
Если я отправлю этот запрос:
{"query":
{"bool" :
{"must" : [
{"match" : {"state" : {"query" : "michoacán de ocampo", "type" : "boolean"} } },
{"match" : {"colony" : {"query" : "zamora", "type" : "boolean"} } },
{"match" : {"city" : {"query" : "zamora", "type" : "boolean"} } }
],
"filter" : {"term" : {"state" : "michoacán"} }
}
} }
Полученные результаты
{
"_shards": {
"failed": 0,
"successful": 5,
"total": 5
},
"hits": {
"hits": [
{
"_id": "71807",
"_index": "my_place",
"_score": 8.708784,
"_source": {
"@timestamp": "2019-11-13T15:34:33.373Z",
"@version": "1",
"city": "Zamora",
"city_id": 828,
"colony": "Balcones de Zamora",
"id": 71807,
"state": "Michoacán de Ocampo",
"state_id": 16,
"type": "place",
"zipcode": "59624",
"zone_id": null
},
"_type": "place"
},
{
"_id": "71762",
"_index": "my_place",
"_score": 8.634264,
"_source": {
"@timestamp": "2019-11-13T15:34:33.112Z",
"@version": "1",
"city": "Zamora",
"city_id": 828,
"colony": "Zamora de Hidalgo Centro",
"id": 71762,
"state": "Michoacán de Ocampo",
"state_id": 16,
"type": "place",
"zipcode": "59600",
"zone_id": null
},
"_type": "place"
}
],
"max_score": 8.708784,
"total": 2
},
"timed_out": false,
"took": 5
}
Что нормально
Но если бы я отправил в фильтре полное название штата, вот так (обратите внимание на полное название "Michoacán de ocampo" в фильтре)
{"query":
{"bool" :
{"must" : [
{"match" : {"state" : {"query" : "michoacán de ocampo", "type" : "boolean"} } },
{"match" : {"colony" : {"query" : "zamora", "type" : "boolean"} } },
{"match" : {"city" : {"query" : "zamora", "type" : "boolean"} } }
],
"filter" : {"term" : {"state" : "Michoacán de Ocampo"} }
}
} }
Я получил такие результаты:
{
"_shards": {
"failed": 0,
"successful": 5,
"total": 5
},
"hits": {
"hits": [],
"max_score": null,
"total": 0
},
"timed_out": false,
"took": 6
}
Мне нужно отправить полное имя в фильтре, как я могу этого добиться или перенастроить индекс, чтобы получить те же результаты?
2 ответа
Обновление: как OP упоминал в комментарии, что он использует 2.4, я обновляю свое решение, чтобы включить решение, которое для него работает.
Решение ES 2.4
Создание индекса с необходимыми настройками и сопоставлениями
{
"settings": {
"analysis": {
"analyzer": {
"lckeyword": {
"filter": [
"lowercase"
],
"tokenizer": "keyword"
}
}
}
},
"mappings": {
"so": {
"properties": {
"state": {
"type": "string"
},
"city": {
"type": "string"
},
"colony": {
"type": "string"
},
"state_raw": {
"type": "string",
"analyzer": "lckeyword"
}
}
}
}
}
Поисковый запрос
{
"query": {
"filtered": {
"query": {
"bool": {
"should": [
{
"match": {
"state": {
"query": "michoacán de ocampo"
}
}
},
{
"match": {
"colony": {
"query": "zamora"
}
}
},
{
"match": {
"city": {
"query": "zamora"
}
}
}
]
}
},
"filter": {
"term": {
"state_raw": "michoacán de ocampo"
}
}
}
}
}
Здесь важно отметить создание настраиваемого анализатора (ключевое слово с фильтром в нижнем регистре), чтобы поле, для которого мы создаем фильтр, сохранялось как есть, но с маленькой буквой, поскольку это то, что вы передаете в своем запросе. Теперь вышеуказанный запрос возвращает вам оба ваших документа, это коллекция почтальона, в которой есть создание индекса, создание образцов документов и запрос, которые возвращают оба возвращенных документа.
Решение ES 7.X
Проблема в том, что вы определяете свой state
поле как text
поле, а затем в своем фильтре вы используете [term][1]
запрос, который не анализируется, как описано в официальном документе ES.
Возвращает документы, содержащие точный термин в указанном поле.
Hence it would try to find token `Michoacán de Ocampo` in inverted index which isn't present as state field is defined as text and generates 3 tokens `michoacán`, `de` and `ocampo` and ES works on token(search term) to token(inverted index) match. You can check these tokens with [analyze API][2] and can use [explain API][3] to see the tokens generated by ES when the query has results
Fix
---
Define `state` field as a [multi-field][4] and store it as it is(kwyword form) so that you can filter on it.
{
"mappings": {
"properties": {
"state": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
},
"city": {
"type": "text"
},
"colony": {
"type": "text"
}
}
}
}
Now below query would give you both results.
{
"query": {
"bool": {
"must": [
{
"match": {
"state": {
"query": "michoacán de ocampo"
}
}
},
{
"match": {
"colony": {
"query": "zamora"
}
}
},
{
"match": {
"city": {
"query": "zamora"
}
}
}
],
"filter": {
"term": {
"state.raw": "Michoacán de Ocampo" -->notice .raw to search on keyword field.
}
}
}
}
}
РЕДАКТИРОВАТЬ: - https://www.getpostman.com/collections/f4b9ed00d50e2f4bc7f4 - это ссылка на коллекцию почтальонов, если вы хотите быстро ее протестировать.
Я предполагаю, что отображение вашего state
поле является значением по умолчанию, т. е. state
текстовое поле с подполем ключевого слова (см. динамическое сопоставление полей).
Если это так, то фильтр вашего первого запроса "работает", потому что он соответствует одному из токенов, созданных анализаторами текста по умолчанию. Фактически, "Michoacán de Ocampo" преобразуется в эти три строчных токена: ["michoacán", "de", "ocampo" ].
По той же причине второй фильтр не может соответствовать, потому что вы сохраняете фразу "Michoacán de Ocampo" в регистре. Должен работать следующий запрос:
{
"query": {
"bool": {
"must": [
{
"match": {
"state": {
"query": "michoacán de ocampo"
}
}
},
{
"match": {
"colony": {
"query": "zamora"
}
}
},
{
"match": {
"city": {
"query": "zamora"
}
}
}
],
"filter": {
"term": {
"state.keyword": "Michoacán de Ocampo"
}
}
}
}
}