Как искать фразы с перестановкой слов в полнотекстовом поиске
Что проверяют
Понимаете ли вы, чем поиск «всех слов в любом порядке» отличается от поиска точной фразы и как это выражается в конкретном движке. Следом обычно спрашивают, как поднять выше документы, где слова стоят рядом.
Как отвечать
Сначала разведите три запроса: все слова где угодно в документе, точная фраза, слова рядом, но в любом порядке. Третий и есть «перестановка», и его дают операторы близости:
- SQL Server:
CONTAINS(Title, 'NEAR((быстрый, поиск), 3, FALSE)'). Второй аргумент задаёт максимальное расстояние в словах,FALSEразрешает любой порядок. Из EF Core это вызывается черезEF.Functions.Contains.FREETEXTпорядок тоже не учитывает, но и близость не контролирует; - PostgreSQL:
plainto_tsquery('russian', 'поиск быстрый')соединяет слова через&и порядок игнорирует, аphraseto_tsqueryстрого требует порядок через<->. Близость в любом порядке учитывают ранжированиемts_rank_cd. В .NET это доступно через Npgsql:EF.Functions.ToTsVector(...).Matches(...); - Elasticsearch:
match_phraseс параметромslop. Перестановка двух соседних слов стоит два шага, поэтому для «быстрый поиск» и «поиск быстрый» нуженslopне меньше 2. Для точного контроля естьspan_nearсin_order: false. Из C# это делается через клиентElastic.Clients.Elasticsearch, старый NEST уже не развивается.
Упомяните анализатор: без стемминга «поиск» и «поиска» разные токены, и никакой slop не поможет. Для русского нужен морфологический анализатор или хотя бы стеммер.
Где спотыкаются
Предлагают расстояние Левенштейна. Оно считает правки по символам и для переставленных слов даёт огромное значение, то есть решает задачу опечаток, а не порядка.
Пишут цепочку LIKE '%слово%' или перебор в памяти через HashSet. Это полный скан без индекса и без ранжирования, на миллионе строк такое не живёт.
Забывают про близость: запрос «все слова» находит документ, где «быстрый» в первом абзаце, а «поиск» в последнем, и такие результаты забивают выдачу.