Полнотекстовый поиск – один из самых востребованных механизмов в современных информационных системах. Он позволяет находить документы по смыслу, а не только по точному совпадению строк. Однако при работе с ним часто возникает проблема: разделение слов (токенизация) может «разорвать» целую фразу запроса, из‑за чего поиск становится менее точным. В этой статье мы подробно разберём, почему это происходит, какие есть способы сохранить фразы запросов «intact» (неразделёнными) и как правильно реализовать их в популярных системах поиска.
Что такое полнотекстовый поиск?
Полнотекстовый поиск (Full‑Text Search, FTS) – это набор алгоритмов и индексов, позволяющих искать документы по содержимому текста. Основные задачи FTS:
- Разбить (токенизировать) текст на отдельные термы (слова, корни, n‑граммы).
- Преобразовать термы (стемминг, лемматизацию, удаление стоп‑слов).
- Построить обратный индекс, связывающий каждый терм с документами, где он встречается.
- Обрабатывать запросы пользователя, учитывая операторы (AND, OR, NOT, кавычки, proximity и др.).
Токенизация – первый и самый критичный шаг. Если при разбиении фразы запрос «красный автомобиль» превратится в два независимых терма «красный» и «автомобиль», система может вернуть документы, где эти слова находятся в разных частях текста, а не рядом.
Почему фразы «разбиваются»?
Разделение слов происходит по нескольким причинам:
- Стандартные анализаторы используют набор разделителей (пробел, пунктуация, цифры). Любой символ, не являющийся буквой, считается границей.
- Стемминг и лемматизация приводят слово к базовой форме, иногда изменяя его структуру.
- Удаление стоп‑слов (например, «и», «в», «на») может «разорвать» фразу, если стоп‑слово находится внутри.
- Нормализация (приведение к нижнему регистру, удаление диакритики) иногда приводит к потере информации о оригинальном порядке слов.
Если система не учитывает контекст расположения слов, запрос «новый iPhone 13» может быть интерпретирован как поиск любого из трёх слов, а не как конкретная фраза.
Как сохранить фразы «intact»?
Существует несколько проверенных подходов, позволяющих обеспечить поиск точных фраз без их разбиения:
Кавычки в запросе
Самый простой способ – требовать от пользователя обрамлять фразу в двойные кавычки. Большинство поисковых движков (MySQL, PostgreSQL, Elasticsearch, Solr) интерпретируют такой запрос как phrase query и ищут термы в точном порядке.
SELECT * FROM articles
WHERE MATCH(content) AGAINST('"красный автомобиль"' IN BOOLEAN MODE);
Специальные анализаторы (phrase‑aware analyzers)
В Elasticsearch и Solr можно создать custom analyzer, который сохраняет оригинальную фразу в виде отдельного токена (shingle). Пример:
{
"settings": {
"analysis": {
"filter": {
"shingle_filter": {
"type": "shingle",
"min_shingle_size": 2,
"max_shingle_size": 3,
"output_unigrams": false
}
},
"analyzer": {
"phrase_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "shingle_filter"]
}
}
}
}
}
Такой анализатор создаёт токены‑шинглы («красный автомобиль», «красный автомобиль 2022»), позволяя искать фразы без потери точности.
Индексация n‑грамм
Для языков с агглютинативной морфологией (например, русский) часто используют edge‑ngrams или ngram‑фильтры. Они позволяют находить части фраз, но при этом необходимо хранить оригинальный порядок слов в отдельном поле.
Поле «raw» (неанализируемое)
Создайте в индексе дополнительное поле, в котором хранится оригинальная строка без токенизации. По этому полю можно выполнять точный поиск с оператором keyword (Elasticsearch) или LIKE (SQL).
CREATE TABLE articles (
id INT PRIMARY KEY,
content TEXT,
content_raw TEXT GENERATED ALWAYS AS (content) STORED,
FULLTEXT(content)
);
Запрос:
SELECT * FROM articles
WHERE content_raw LIKE '%красный автомобиль%';
Операторы proximity (близости)
Если требуется гибкость (слова могут быть разделены небольшим числом слов), используют оператор NEAR или ~ в Elasticsearch:
GET /articles/_search
{
"query": {
"match_phrase": {
"content": {
"query": "красный автомобиль",
"slop": 2 // допускает до 2 промежуточных слов
}
}
}
}
Практические примеры в популярных СУБД и поисковых движках
MySQL
- Создайте FULLTEXT индекс.
- Для точных фраз используйте
IN BOOLEAN MODEс кавычками. - Если нужны шинглы, храните отдельное поле
content_phraseи индексируйте его какVARCHAR.
PostgreSQL (pg_trgm, tsvector)
Комбинация tsvector и pg_trgm позволяет искать как по токенам, так и по подстрокам;
CREATE EXTENSION pg_trgm;
ALTER TABLE articles ADD COLUMN content_tsv tsvector;
UPDATE articles SET content_tsv = to_tsvector('russian', content);
-- Поиск точной фразы
SELECT * FROM articles
WHERE content ILIKE '%красный автомобиль%';
Elasticsearch
Пример маппинга с двумя полями: content (анализируемый) и content_raw (keyword).
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "russian"
},
"content_raw": {
"type": "keyword"
}
} }
}
Запросы:
- Точная фраза:
match_phrase. - Точное совпадение без анализа:
termпоcontent_raw.
Apache Solr
В Solr используется field type text_general для токенизации и string для «raw»‑полей.
<field name="content" type="text_ru" indexed="true" stored="true"/>
<field name="content_raw" type="string" indexed="true" stored="false"/>
Запросы:
q="content:"красный автомобиль""
q="content_raw:"красный автомобиль""
Sphinx
В Sphinx можно задать phrase boundaries с помощью phrase_boundary в конфигурации.
index articles
{
source = src_articles
path = /var/data/sphinx/articles
morphology = stem_ru
phrase_boundary = 1
}
Лучшие практики
- Всегда храните оригинальный текст в отдельном неанализируемом поле – это спасёт, если понадобится точный поиск.
- Для пользовательских запросов поощряйте использование кавычек (можно добавить UI‑подсказку).
- Если требуется гибкость, используйте shingle‑анализаторы или proximity operators с небольшим
slop. - Не удаляйте стоп‑слова в полях, где важен порядок слов (например, в названиях книг).
- Тестируйте запросы на реальном наборе данных: сравните результаты
match,match_phraseиLIKE. - Для мультиязычных проектов используйте отдельные анализаторы для каждого языка, учитывающие особенности морфологии.
Сохранение фраз запросов «intact» в полнотекстовом поиске – задача, решаемая на нескольких уровнях: от правильного построения запросов (кавычки, proximity) до настройки индекса (shingle‑анализаторы, raw‑поля). Выбор конкретного подхода зависит от:
- Объёма данных и частоты запросов.
- Требуемой точности vs. гибкости поиска.
- Технологического стека (MySQL, PostgreSQL, Elasticsearch, Solr, Sphinx и др.).
Применяя описанные в статье методы, вы сможете обеспечить пользователям поиск, который будет находить именно те документы, где фразы находятся в нужном порядке, не «разрывая» их на отдельные слова. Это повышает релевантность, улучшает пользовательский опыт и снижает количество нерелевантных результатов.
Автор: ваш виртуальный помощник.