# Как сделать так, чтобы xt850 находил xt 850

Практическое руководство о том, как сделать так, чтобы запросы вроде xt850 находили xt 850: с понятным разбором bigram_delimiter, second_numeric, second_has_digit и воспроизводимыми примерами.

## Кратко

В Manticore, начиная с версии `23.0.0`, можно настроить поиск так, чтобы запрос `xt850` находил `xt 850`. Для этого используется [bigram\_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter) вместе с режимами [bigram\_index](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_index), которые умеют работать с цифрами.

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

## Что стоит учесть в примерах

В примерах ниже мы исходим из таких условий:

- RT-таблицы созданы по SQL-примерам без изменений, именно в показанном виде
- токенизация остается стандартной, если в конкретном примере явно не задано другое поведение
- в названиях моделей используются ASCII‑цифры, потому что `second_numeric` и `second_has_digit` рассчитаны на цифры из диапазона `0-9`

Все SQL-примеры и ожидаемые результаты в этой статье мы проверили перед публикацией на реальном сервере Manticore версии `23.0.0`, используя новые таблицы, созданные с нуля для каждого сценария.

## Проблема не только в `xt850`

Представьте, что у вас есть каталог с такими товарами:

- `xt 850 action camera`
- `iphone 5se battery case`
- `canon eos 80d body`
- `thinkpad x1 carbon`

А теперь представьте, что пользователи ищут так:

- `xt850`
- `iphone5se`
- `eos80d`
- `thinkpadx1`

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

Обычно поисковые системы решают такое несоответствие одним из четырёх способов:

- индексация префиксов или инфиксов
- добавление пользовательских правил нормализации
- дублирование контента в альтернативные нормализованные поля
- индексация соседних пар токенов и при необходимости хранение склеенных вариантов

Новые возможности Manticore для биграмм позволяют сделать четвёртый вариант проще и понятнее, не дублируя поля.

## Начнём с простого: почему `xt850` не срабатывает по умолчанию

Вот как эта проблема выглядит в самом простом сценарии:

```sql
DROP TABLE IF EXISTS bi_default_demo;

CREATE TABLE bi_default_demo(title text);

INSERT INTO bi_default_demo VALUES
  (1,'xt 850 action camera');

SELECT id, title FROM bi_default_demo WHERE MATCH('xt850');
```

Ожидаемый результат:

```sql
Empty set
```

Почему это не работает?

Причина в том, что при индексации документ превращается в два отдельных токена: `xt` и `850`. Запрос же приходит одним токеном — `xt850`.

По умолчанию Manticore не предполагает, что:

- `xt850` должен быть разбит на `xt` + `850`
- или `xt` + `850` также должно быть доступно для поиска как `xt850`

То есть это не проблема работы с опечатками и не проблема фразового поиска. Это несоответствие токенизации: индекс видит два токена, а запрос даёт один.

Новые настройки биграмм помогают решить именно эту проблему. С их помощью Manticore может индексировать выбранные соседние пары токенов так, чтобы они совпадали и со склеенными запросами.

## Как помогают биграммы

[bigram_index](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_index) может помочь и с [ускорением поиска фраз](/blog/how-to-speed-up-phrase-search-with-bigram-index/), и с сопоставлением названий моделей, но в этой статье мы фокусируемся на проблеме `xt 850` vs `xt850`.

Ключевая идея проста:

- находим соседние пары токенов, похожие на названия моделей
- также сохраняем эти пары в склеенной форме
- это позволяет запросам вроде `xt850`, `iphone5se` или `thinkpadx1` находить текст с пробелами

Именно для этого важен [bigram_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter).

## Что нужно знать о [bigram_delimiter](https://manual.manticoresearch.com/dev/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#bigram_delimiter)

`bigram_index` отвечает за то, какие соседние пары попадут в обработку.

`bigram_delimiter` определяет, в каком виде сохраняются подходящие биграммы:

- `true`: только внутренняя форма с разделителем
- `none`: только склеенный токен, например `galaxy24`
- `both`: обе формы

Практическую разницу проще всего понять на конкретных запросах:

- при `true` Manticore сохраняет только внутреннюю форму биграммы, нужную для оптимизации фраз, но не сохраняет склеенный вариант из пользовательского ввода. Поэтому запрос `xt850` не сработает как соответствие для `xt 850`
- при `none` Manticore сохраняет только склеенную форму, поэтому `xt850` может найти `xt 850`, но для таких пар поиск будет опираться только на это склеенное представление
- при `both` Manticore сохраняет оба варианта: внутреннее представление биграммы и склеенную форму. Поэтому `xt850` может найти `xt 850`, а стандартное поведение фразового поиска остается на месте

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

## Режим 1: `second_numeric`

```ini
bigram_index = second_numeric
bigram_delimiter = both
```

Этот режим подходит для названий моделей, где второй токен состоит только из цифр.

Такое часто встречается в товарных каталогах:

- `xt 850`
- `galaxy 24`
- `playstation 5`
- `pixel 8`

Идея проста: пользователи часто ищут такие модели в склеенном виде — `xt850`, `galaxy24` или `playstation5`, хотя в исходном тексте они записаны с пробелом.

`second_numeric` сохраняет пару только тогда, когда второй токен состоит исключительно из ASCII‑цифр.

Используйте его, когда:

- в каталоге встречаются линейки продуктов с поколениями и нумерованными моделями
- пользователи часто вводят такие запросы слитно, без пробелов
- второй токен в таких парах чаще всего состоит только из цифр

### Пример

```sql
DROP TABLE IF EXISTS bi_second_numeric_demo;

CREATE TABLE bi_second_numeric_demo(title text)
  bigram_index='second_numeric'
  bigram_delimiter='both';

INSERT INTO bi_second_numeric_demo VALUES
  (1,'xt 850 action camera'),
  (2,'galaxy 24 ultra'),
  (3,'playstation 5 slim'),
  (4,'iphone 5se case'),
  (5,'canon eos 80d body'),
  (6,'thinkpad x1 carbon');
```

Теперь проверим запросы по очереди:

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('xt850');

+------+----------------------+
| id   | title                |
+------+----------------------+
|    1 | xt 850 action camera |
+------+----------------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('galaxy24');

+------+-----------------+
| id   | title           |
+------+-----------------+
|    2 | galaxy 24 ultra |
+------+-----------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('playstation5');

+------+--------------------+
| id   | title              |
+------+--------------------+
|    3 | playstation 5 slim |
+------+--------------------+
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('iphone5se');

Empty set
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('eos80d');

Empty set
```

```sql
SELECT id, title FROM bi_second_numeric_demo WHERE MATCH('thinkpadx1');

Empty set
```

На этом примере хорошо видно, где проходит граница режима:

- `24` и `5` подходят
- `5se`, `80d` и `x1` не подходят

## Режим 2: `second_has_digit`

```ini
bigram_index = second_has_digit
bigram_delimiter = both
```

Этот режим - более гибкий вариант `second_numeric`.

Он сохраняет пару, когда второй токен содержит хотя бы одну ASCII‑цифру. Поэтому этот режим намного лучше подходит для реальных каталогов товаров, где идентификаторы моделей часто представляют собой смешанные буквенно-цифровые строки:

- `xt 850`
- `iphone 5se`
- `eos 80d`
- `thinkpad x1`

Используйте его, когда:

- в названиях моделей у вас смешаны буквы и цифры
- пользователи часто удаляют пробелы в своих запросах
- вам нужен удобный для каталога механизм поиска без необходимости индексировать каждую пару в таблице

### Пример

```sql
DROP TABLE IF EXISTS bi_second_has_digit_demo;

CREATE TABLE bi_second_has_digit_demo(title text)
  bigram_index='second_has_digit'
  bigram_delimiter='both';

INSERT INTO bi_second_has_digit_demo VALUES
  (1,'xt 850 action camera'),
  (2,'galaxy 24 ultra'),
  (3,'playstation 5 slim'),
  (4,'iphone 5se case'),
  (5,'canon eos 80d body'),
  (6,'thinkpad x1 carbon'),
  (7,'kindle paperwhite signature');
```

Затем проверьте запросы один за другим:

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('xt850');

+------+----------------------+
| id   | title                |
+------+----------------------+
|    1 | xt 850 action camera |
+------+----------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('galaxy24');

+------+-----------------+
| id   | title           |
+------+-----------------+
|    2 | galaxy 24 ultra |
+------+-----------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('iphone5se');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    4 | iphone 5se case     |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('eos80d');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    5 | canon eos 80d body  |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('thinkpadx1');

+------+---------------------+
| id   | title               |
+------+---------------------+
|    6 | thinkpad x1 carbon  |
+------+---------------------+
```

```sql
SELECT id, title FROM bi_second_has_digit_demo WHERE MATCH('kindlesignature');

Empty set
```

Часто это более подходящий вариант для смешанных идентификаторов моделей, потому что в реальных каталогах нередко встречаются формы вроде `5se`, `80d` или `x1`, а не только чисто цифровые суффиксы вроде `24`.

## Какой режим выбрать

Если вы хотите, чтобы запрос `xt850` находил `xt 850`, проще всего следовать такому правилу:

- используйте `second_numeric`, когда второй токен состоит только из цифр
- используйте `second_has_digit`, когда второй токен может быть смешанным, например `5se`, `80d` или `x1`

Есть один практический нюанс: в базовом сценарии это сохраняется и при дополнительных настройках обработки текста. `xt 850` все равно совпадает с `xt850`, если включены `morphology='stem_en'` и wordforms.

Но это не значит, что эти настройки сами нормализуют склеенный запрос. В тестах `iphones 5` совпадал с `iphones5`, но не с `iphone5`, даже при стемминге или замене `iphones` -> `iphone` через wordforms. Коротко: в простом случае `xt 850` и `xt850` совместимы с `morphology` и wordforms, но если они для вас важны, отдельно проверьте нужную форму запроса.

## Главное

Проблема `xt850` на самом деле не в одном конкретном названии продукта. Речь идёт о более широком несоответствии между тем, как пользователи вводят названия моделей, и тем, как поисковые движки разбивают их на токены.

В Manticore, начиная с версии `23.0.0`, для этого есть штатное решение: `bigram_delimiter` вместе с режимами `bigram_index`, которые учитывают цифры. Обычно это проще, чем дублировать поля или собирать отдельный пайплайн предобработки.

Если ваша основная проблема — производительность поиска фраз, а не сопоставление склеенных названий моделей, см. [Как ускорить поиск фраз с помощью bigram_index](/blog/how-to-speed-up-phrase-search-with-bigram-index/).
