Как реализовать точное совпадение в фильтре с 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
}

Мне нужно отправить полное имя в фильтре, как я могу этого добиться или перенастроить индекс, чтобы получить те же результаты?


person sirandy    schedule 27.01.2020    source источник
comment
Я поделился ссылкой на коллекцию почтальона, которую вы можете легко импортировать и легко протестировать, поскольку в ней есть образец сопоставления индекса, образец индексированного документа и измененный поисковый запрос.   -  person user156327    schedule 27.01.2020


Ответы (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 - это ссылка на коллекцию почтальонов, если вы хотите быстро ее протестировать.

person user156327    schedule 27.01.2020
comment
Я прекрасно понимаю ваш ответ, поскольку вы сказали, что определение состояния поля таково: "state": { "type": "string" }, (как видите, я использую очень старую версию ES). Какие могут быть последствия изменения этого определения индекса? - person sirandy; 28.01.2020
comment
@sirandy какую версию ES вы используете? даже в случае старой версии ES, такой как 1.X, вместо использования многополя, вы можете добавить еще одно поле для хранения ключевого слова, эквивалентного полю состояния, которое в нашем случае равно state.raw, а затем применить к нему фильтр. Кроме того, добавление нового поля не нарушает изменений, поэтому вам не нужно повторно индексировать данные, но старые документы не получат токены ключевых слов, поэтому позже, когда вы обновите этот документ, они будут иметь его и отображаться в вашем полученные результаты. Я делал это несколько раз, и это называлось инкрементными изменениями, дайте мне знать, если вам понадобится дополнительная информация. - person user156327; 28.01.2020
comment
Интересный подход, версия ES - 2.4. Я сделал это следующим образом: "state": {"type": "string", "fields": {"raw": {"type": "string"}}} Я ввел строку в тип, потому что ES 2.4 не поддерживает ключевое слово как тип, а затем я выполнил запрос таким образом: "filter": [ { "term": { "state.raw": "michoacán de ocampo" }} ]. Но безуспешно :( - person sirandy; 28.01.2020
comment
@sirandy, наконец, пришлось потратить так много времени, чтобы заставить его работать на вас :) ... пожалуйста, взгляните на мой обновленный ответ и коллекцию почтальонов - person user156327; 29.01.2020
comment
Ох! интересный подход! Сначала у меня были проблемы, потому что я забыл обновить свои документы новым полем state_raw. Насколько я понимаю, мне нужно создать это свойство для всех документов, а затем переиндексировать, верно? - person sirandy; 29.01.2020
comment
@sirandy, да, вы правы, вам нужно переиндексировать после того, как вы создадите это свойство, чтобы перенести изменения во все ваши документы и gald, наконец, это сработало для вас :-) - person user156327; 29.01.2020
comment
@sirandy идет дальше, пожалуйста, укажите версию ES, поскольку она создает нежелательную задержку, и мне пришлось потратить так много времени, чтобы проверить ее в более старых версиях, и даже в запросах были некоторые критические изменения :( .. обычно люди не тратят так много время, поэтому рекомендуется в первую очередь дать точную информацию, надеюсь, вы понимаете мою точку зрения :-) - person user156327; 29.01.2020
comment
Конечно, я отредактировал вопрос, а также теги, чтобы предоставить больше информации. Я очень ценю ваши усилия и благодарю за то, что вы вышли за рамки служебного долга. Возможно, вы уже это сделали, но использовали ли вы Docker для этой задачи? - person sirandy; 29.01.2020
comment
@sirandy, спасибо за редактирование вопроса, это сделало бы его более доступным для поиска и полезным для сообщества, нет, я не использовал докер для этой задачи. - person user156327; 29.01.2020

я предполагаю, что отображение вашего поля 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"
        }
      }
    }
  }
}
person glenacota    schedule 27.01.2020