Перейти к основному содержимому

Запросы Elasticsearch с данными Spring

· 5 мин. чтения

1. Введение

В предыдущей статье мы продемонстрировали, как настроить и использовать Spring Data Elasticsearch для проекта. В этой статье мы рассмотрим несколько типов запросов, предлагаемых Elasticsearch, а также поговорим об анализаторах полей и их влиянии на результаты поиска.

2. Анализаторы

Все сохраненные строковые поля по умолчанию обрабатываются анализатором. Анализатор состоит из одного токенизатора и нескольких токен-фильтров, и обычно ему предшествует один или несколько символьных фильтров.

Анализатор по умолчанию разбивает строку по общепринятым разделителям слов (таким как пробелы или знаки препинания) и помещает каждый токен в нижний регистр. Он также игнорирует общеупотребительные английские слова.

Elasticsearch также можно настроить так, чтобы поле считалось проанализированным и не проанализированным одновременно.

Например, предположим, что в классе Article мы сохраняем поле title как стандартное анализируемое поле. Это же поле с суффиксом дословно будет храниться как неанализируемое поле:

@MultiField(
mainField = @Field(type = Text, fielddata = true),
otherFields = {
@InnerField(suffix = "verbatim", type = Keyword)
}
)
private String title;

Здесь мы применяем аннотацию @MultiField , чтобы сообщить Spring Data, что мы хотели бы, чтобы это поле было проиндексировано несколькими способами. Основное поле будет использовать название title и будет анализироваться в соответствии с описанными выше правилами.

Но мы также предоставляем вторую аннотацию, @InnerField , которая описывает дополнительную индексацию поля заголовка . Мы используем FieldType.keyword , чтобы указать, что мы не хотим использовать анализатор при выполнении дополнительной индексации поля, и что это значение должно храниться с использованием вложенного поля с суффиксом verbatim .

2.1. Анализируемые поля

Давайте посмотрим на пример. Допустим, в наш индекс добавлена статья с заголовком «Spring Data Elasticsearch». Анализатор по умолчанию разбивает строку на пробелы и создает токены нижнего регистра: « пружина », « данные» и « эластичный поиск ».

Теперь мы можем использовать любую комбинацию этих терминов для сопоставления с документом:

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "elasticsearch data"))
.build();

2.2. Неанализируемые поля

Неанализируемое поле не токенизируется, поэтому может быть сопоставлено только в целом при использовании запросов соответствия или терминов:

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch"))
.build();

Используя запрос соответствия, мы можем искать только по полному заголовку, который также чувствителен к регистру.

3. Соответствие запросу

Запрос соответствия принимает текст, числа и даты.

Существует три типа запроса «совпадение»:

  • логический
  • фраза и
  • фраза_префикс

В этом разделе мы рассмотрим логический запрос соответствия.

3.1. Сопоставление с логическими операторами

boolean — тип запроса на соответствие по умолчанию; вы можете указать, какой логический оператор использовать ( или по умолчанию):

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title","Search engines").operator(Operator.AND))
.build();
SearchHits<Article> articles = elasticsearchTemplate()
.search(searchQuery, Article.class, IndexCoordinates.of("blog"));

Этот запрос вернет статью с заголовком «Поисковые системы», указав два термина из заголовка с оператором и . Но что произойдет, если мы будем искать с оператором по умолчанию ( или ), когда только одно из условий соответствует?

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "Engines Solutions"))
.build();
SearchHits<Article> articles = elasticsearchTemplate()
.search(searchQuery, Article.class, IndexCoordinates.of("blog"));
assertEquals(1, articles.getTotalHits());
assertEquals("Search engines", articles.getSearchHit(0).getContent().getTitle());

Статья « Поисковые системы » по-прежнему будет соответствовать, но у нее будет более низкий балл, поскольку не все термины совпали.

Сумма баллов каждого совпадающего термина составляет общую оценку каждого полученного документа.

Возможны ситуации, когда документ, содержащий введенный в запросе редкий термин, будет иметь более высокий ранг, чем документ, содержащий несколько общих терминов.

3.2. Нечеткость

Когда пользователь делает опечатку в слове, его по-прежнему можно сопоставить с поиском, указав параметр нечеткости , который допускает неточное совпадение.

Для строковых полей нечеткость означает расстояние редактирования: количество односимвольных изменений, которые необходимо внести в одну строку, чтобы она стала такой же, как другая строка.

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "spring date elasticsearch")
.operator(Operator.AND)
.fuzziness(Fuzziness.ONE)
.prefixLength(3))
.build();

Параметр prefix_length используется для повышения производительности. В этом случае мы требуем, чтобы первые три символа точно совпадали, что уменьшает количество возможных комбинаций.

5. Поиск по фразе

Поиск фазы более строгий, хотя вы можете управлять им с помощью параметра slop . Этот параметр сообщает фразовому запросу, насколько далеко друг от друга могут находиться термины, при этом документ считается совпадающим.

Другими словами, он представляет собой количество раз, которое вам нужно переместить термин, чтобы запрос и документ соответствовали:

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1))
.build();

Здесь запрос будет соответствовать документу с заголовком « Spring Data Elasticsearch », потому что мы установили наклон равным единице.

6. Запрос на множественное совпадение

Если вы хотите выполнить поиск в нескольких полях, вы можете использовать QueryBuilders#multiMatchQuery() , где вы указываете все поля для соответствия:

NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQuery("tutorial")
.field("title")
.field("tags")
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS))
.build();

Здесь мы ищем совпадение в полях заголовка и тегов .

Обратите внимание, что здесь мы используем стратегию подсчета очков «лучшие поля». В качестве оценки документа будет использоваться максимальный балл среди полей.

7. Агрегации

В нашем классе Article мы также определили поле тегов , которое не анализируется. Мы могли бы легко создать облако тегов, используя агрегацию.

Помните, что, поскольку поле не анализируется, теги не будут токенизированы:

TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags")
.field("tags")
.order(Terms.Order.count(false));
SearchSourceBuilder builder = new SearchSourceBuilder().aggregation(aggregation);

SearchRequest searchRequest =
new SearchRequest().indices("blog").types("article").source(builder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

Map<String, Aggregation> results = response.getAggregations().asMap();
StringTerms topTags = (StringTerms) results.get("top_tags");

List<String> keys = topTags.getBuckets()
.stream()
.map(b -> b.getKeyAsString())
.collect(toList());
assertEquals(asList("elasticsearch", "spring data", "search engines", "tutorial"), keys);

8. Резюме

В этой статье мы обсудили разницу между анализируемыми и неанализируемыми полями и то, как это различие влияет на поиск.

Мы также узнали о нескольких типах запросов, предоставляемых Elasticsearch, таких как запрос на соответствие, запрос на соответствие фразе, запрос на полнотекстовый поиск и логический запрос.

Elasticsearch предоставляет множество других типов запросов, таких как гео-запросы, скриптовые запросы и составные запросы. Вы можете прочитать о них в документации Elasticsearch и изучить Spring Data Elasticsearch API, чтобы использовать эти запросы в своем коде.

Вы можете найти проект, содержащий примеры, использованные в этой статье, в репозитории GitHub .