Полнотекстовые запросы: match, match_phrase, multi_match

Что делает запрос «полнотекстовым»

Ключевое отличие от точных запросов — анализ ввода. Когда вы пишете match: "Беспроводные наушники", Elasticsearch прогоняет строку запроса через тот же анализатор, что применялся при индексации поля. На выходе — не строка, а набор токенов: нормализованных, приведённых к нижнему регистру, возможно стеммингованных слов. Именно эти токены ищутся в инвертированном индексе.

Это принципиально отличает match от term: term не анализирует ввод и ищет значение буквально. Подробнее о разнице — в Точные совпадения и фильтры: term, range, exists. Как устроен сам анализатор — в Анатомия анализатора: char filters, токенизатор, token filters.

Три запроса этой статьи:

  • match — основной инструмент полнотекстового поиска
  • match_phrase — поиск с сохранением порядка слов
  • multi_matchmatch сразу по нескольким полям

match: основной запрос

Минимальная форма:

GET /articles/_search
{
  "query": {
    "match": {
      "title": "elasticsearch поиск"
    }
  }
}

ES анализирует строку "elasticsearch поиск", получает токены elasticsearch и поиск, ищет документы, где в поле title встречается хотя бы один из них. По умолчанию — оператор OR.

Путь от строки запроса до результатов:

flowchart LR A[Запрос пользователя] --> B[Анализатор поля] B --> C[Токены: elasticsearch, поиск] C --> D{operator} D -->|OR, по умолчанию| E[Хотя бы один токен] D -->|AND| F[Все токены] E --> G[Инвертированный индекс] F --> G G --> H[Документы + _score]
flowchart LR
    A[Запрос пользователя] --> B[Анализатор поля]
    B --> C[Токены: elasticsearch, поиск]
    C --> D{operator}
    D -->|OR, по умолчанию| E[Хотя бы один токен]
    D -->|AND| F[Все токены]
    E --> G[Инвертированный индекс]
    F --> G
    G --> H[Документы + _score]
Путь запроса match: от строки к токенам и результатам

Развёрнутый синтаксис, когда нужны параметры:

GET /articles/_search
{
  "query": {
    "match": {
      "title": {
        "query": "elasticsearch поиск",
        "operator": "and"
      }
    }
  }
}

#### operator: AND vs OR

operator: "or" (по умолчанию) — достаточно одного совпавшего токена. Широкий охват, но и больше нерелевантного шума.

operator: "and" — все токены должны присутствовать в поле. Строже и точнее, но легко пропустить релевантный документ, в котором не хватает одного слова.

Допустим, в индексе три статьи:

idtitle
1«Введение в Elasticsearch»
2«Полнотекстовый поиск в Elasticsearch»
3«Введение в полнотекстовый поиск»

Запрос "elasticsearch поиск" с operator: "or" вернёт все три (в каждой есть хотя бы одно слово). С operator: "and" — только документ 2, где присутствуют оба.

Check yourself
Запрос: `match: { "title": { "query": "elasticsearch поиск документов", "operator": "and" } }`. В индексе документ с заголовком «Поиск документов в базе данных». Совпадёт ли он?

#### minimum_should_match: тонкая настройка

Компромисс между or и and — параметр minimum_should_match:

GET /articles/_search
{
  "query": {
    "match": {
      "body": {
        "query": "быстрый полнотекстовый поиск документов",
        "minimum_should_match": "75%"
      }
    }
  }
}

Запрос содержит 4 токена. "75%" означает: минимум 3 из 4 должны совпасть. Значение задаётся числом (2), процентом ("75%") или комбинированно ("2<75%" — если токенов меньше 3, нужны все; если 3 и больше — 75%). Процентный формат удобен, когда длина пользовательского ввода непредсказуема.

match_phrase: порядок слов имеет значение

match не обращает внимания на порядок слов: «поиск Elasticsearch» и «Elasticsearch поиск» для него равнозначны. Когда порядок критичен — используйте match_phrase:

GET /articles/_search
{
  "query": {
    "match_phrase": {
      "body": "полнотекстовый поиск"
    }
  }
}

Запрос найдёт только документы, где токены полнотекстовый и поиск стоят рядом и именно в таком порядке. «Поиск полнотекстовый» — уже не совпадёт.

Check yourself
Запрос `match_phrase: { "body": "полнотекстовый поиск" }`. Какой из вариантов совпадёт: (а) «поиск полнотекстовый», (б) «эффективный полнотекстовый поиск», (в) «полнотекстовый быстрый поиск»?

#### slop: допуск на вставные слова

Параметр slop задаёт, сколько «шагов перестановки» допустимо между токенами:

GET /articles/_search
{
  "query": {
    "match_phrase": {
      "body": {
        "query": "elasticsearch поиск",
        "slop": 1
      }
    }
  }
}

При slop: 1 между словами может стоять одно лишнее слово — «elasticsearch быстрый поиск» совпадёт. По умолчанию slop: 0: слова вплотную, без вставок.

Практическое правило: slop: 0 для точных терминов и цитат; slop: 1–2 — когда ищете устойчивые сочетания в живом тексте, где могут встречаться вставные слова.

multi_match: один запрос по нескольким полям

В реальных индексах несколько текстовых полей: title, body, tags. multi_match позволяет искать по всем сразу:

GET /articles/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch полнотекстовый поиск",
      "fields": ["title", "body", "tags"]
    }
  }
}

По умолчанию режим best_fields: итоговый _score берётся у поля с наибольшим совпадением. Логика такая — если запрос точно попал в заголовок, именно он определяет релевантность документа.

#### Буст отдельных полей

Добавьте ^N к имени поля, чтобы усилить его вклад в _score:

GET /articles/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch поиск",
      "fields": ["title^3", "body", "tags^2"]
    }
  }
}

Совпадение в title ценится втрое дороже, чем в body; в tags — вдвое. Как именно вычисляется _score — разбирается в Релевантность и BM25: как считается _score, а управление бустом — в Бустинг полей и function_score: управление ранжированием.

#### Режимы type

typeЛогика scoreКогда использовать
best_fields (по умолчанию)Максимальный score среди полейПоля независимы, одно важнее других
most_fieldsСумма score всех полейПоля взаимодополняют (например, title + title.english)
cross_fieldsПоля как единый текст, токены могут быть в разных поляхДанные разбиты по полям: first_name + last_name
phrasematch_phrase по каждому полюПоиск точной фразы сразу в нескольких полях
phrase_prefixАвтодополнение фразыПоисковые подсказки «по мере ввода»

Пример cross_fields — поиск имени и фамилии, когда они лежат в разных полях:

GET /users/_search
{
  "query": {
    "multi_match": {
      "query": "Иван Петров",
      "fields": ["first_name", "last_name"],
      "type": "cross_fields",
      "operator": "and"
    }
  }
}

С best_fields запрос провалится: ни одно поле не содержит «Иван Петров» целиком. С cross_fields ES объединяет поля в один текст и ищет иван и петров по всем им вместе.

Check yourself
Вы ищете пользователей через `multi_match` по полям `["first_name", "last_name"]` с запросом «Мария Иванова» и `type: "best_fields"`. Почему результат может оказаться неожиданным?

Ловушка: match по keyword-полю

Если поле имеет тип keyword, анализатор к нему не применяется — ни при индексации, ни при match-запросе. Значение хранится как есть. Запрос match: { "status": "Active" } найдёт строку "Active", но не "active".

Для полей keyword используйте term, а не match. Проверить тип: GET /my_index/_mapping. О типах полей подробнее — в Типы полей: text, keyword, числа и даты.

Quick recall
Чем `match_phrase` отличается от `match`?
Quick recall
Запрос `match: { "title": "elasticsearch поиск" }` — какой оператор применяется по умолчанию?
Quick recall
Чем полнотекстовый запрос `match` отличается от точного `term`?

См. также