---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.6
alternate:
  - https://ydb.tech/docs/en/dev/fulltext-indexes.md?version=main
  - https://ydb.tech/docs/ru/dev/fulltext-indexes.md?version=main
sourcePath: ru/core/dev/fulltext-indexes.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt

# Полнотекстовые индексы

Полнотекстовые индексы — это специализированный тип [вторичного индекса](https://ydb.tech/docs/ru/concepts/glossary.md?version=main#secondary-index), который позволяет эффективно выполнять поиск по текстовому содержимому в колонках таблицы: по словам, фразам и (с N-граммами) по подстрокам. В отличие от традиционных вторичных индексов, оптимизированных для поиска по равенству или диапазону, полнотекстовые индексы обеспечивают поиск по текстовому содержимому.

Общее описание полнотекстового поиска см. в разделе [Полнотекстовый поиск](https://ydb.tech/docs/ru/concepts/query_execution/fulltext_search.md?version=main).

## Характеристики полнотекстовых индексов {#characteristics}

Полнотекстовые индексы в YDB строятся путём токенизации текста и создания инвертированного индекса. Это позволяет:

* быстро фильтровать строки через [FulltextMatch](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-match);
* ранжировать результаты по релевантности ([BM25](https://en.wikipedia.org/wiki/Okapi_BM25)) через [FulltextScore](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-score) при использовании [fulltext_relevance](#relevance);
* применять нормализацию регистра, стемминг и N-граммы с помощью фильтров индекса.

В текущей реализации доступны два варианта индекса:

* [fulltext_plain](#basic) — базовый полнотекстовый индекс;
* [fulltext_relevance](#relevance) — полнотекстовый индекс со статистикой [BM25](https://en.wikipedia.org/wiki/Okapi_BM25) для расчёта релевантности.

Кроме этого, полнотекстовый индекс может быть **покрывающим** (через `COVER`) и включать копию данных дополнительных колонок из основной таблицы.


## Виды полнотекстовых индексов {#types}

YDB поддерживает два типа полнотекстовых индексов, которые различаются составом хранимой статистики:

* [fulltext_plain](#basic) — хранит только инвертированный индекс. Поддерживает фильтрацию через [FulltextMatch](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-match), но не позволяет ранжировать результаты по релевантности.
* [fulltext_relevance](#relevance) — дополнительно хранит частотную статистику (TF-IDF / [BM25](https://en.wikipedia.org/wiki/Okapi_BM25)), необходимую для работы [FulltextScore](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-score).

Индекс любого из этих типов также может быть [с фильтрацией](#filtered) для ограничения поиска конкретным логическим разделом.

### Базовый полнотекстовый индекс (`fulltext_plain`) {#basic}

Используйте `fulltext_plain`, когда достаточно проверить наличие термов в тексте без ранжирования по релевантности. Такой индекс компактнее `fulltext_relevance` и подходит для большинства задач фильтрации.

Пример создания глобального полнотекстового индекса по колонке `body`:

```yql
ALTER TABLE articles
  ADD INDEX ft_index
  GLOBAL USING fulltext_plain
  ON (body)
  WITH (tokenizer=standard, use_filter_lowercase=true);
```

Здесь `tokenizer=standard` разбивает текст на слова по пробелам и знакам препинания, а `use_filter_lowercase=true` нормализует все токены к нижнему регистру — это делает поиск регистронезависимым.

Пример запроса к индексу:

```yql
SELECT id, title
FROM articles VIEW ft_index
WHERE FulltextMatch(body, "поисковые термы")
LIMIT 20;
```

### Полнотекстовый индекс для ранжирования (`fulltext_relevance`) {#relevance}

`fulltext_relevance` хранит инвертированный индекс вместе с частотной статистикой ([BM25](https://en.wikipedia.org/wiki/Okapi_BM25)), которая позволяет функции [FulltextScore](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-score) вычислять оценку релевантности документа запросу. Используйте этот тип, когда нужно не просто найти документы с нужными словами, но и упорядочить их по степени соответствия.

Пример создания индекса:

```yql
ALTER TABLE articles
  ADD INDEX ft_index
  GLOBAL USING fulltext_relevance
  ON (body)
  WITH (tokenizer=standard, use_filter_lowercase=true);
```

Пример запроса с ранжированием:

```yql
SELECT id, title, FulltextScore(body, "поисковые термы") AS relevance
FROM articles VIEW ft_index
WHERE FulltextScore(body, "поисковые термы") > 0
ORDER BY relevance DESC
LIMIT 10;
```

### Поиск по подстроке (N-граммы) {#substr}

Если вам нужен поиск по подстроке, создайте индекс с N-граммами. Доступны два типа N-грамм:

* **Обычные N-граммы** (`use_filter_ngram`) — разбивают слова на все возможные подстроки заданной длины, что позволяет находить совпадения в любой части слова. Например, слово "search" будет разбито на "sea", "ear", "arc", "rch" и т.д.
* **Краевые N-граммы** (`use_filter_edge_ngram`) — создают подстроки только от начала слова, что идеально подходит для автодополнения. Например, слово "search" будет разбито на "se", "sea", "sear", "searc", "search".

При использовании N-грамм станут доступны:

* [FulltextMatch(..., "Wildcard" AS Mode)](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main#fulltext-match) — поиск с шаблонами `%` и `_` (аналогично `LIKE`);
* предикаты `LIKE` / `ILIKE` по индексируемой текстовой колонке — YDB автоматически использует N-граммовый индекс при обращении к нему через `VIEW IndexName`.

Пример индекса с N-граммами:

```yql
ALTER TABLE articles
  ADD INDEX ngram_index
  GLOBAL USING fulltext_plain
  ON (body)
  WITH (
    tokenizer=standard,
    use_filter_lowercase=true,
    use_filter_ngram=true,
    filter_ngram_min_length=3,
    filter_ngram_max_length=5
  );
```

Пример запроса с `FulltextMatch`:

```yql
SELECT id, title
FROM articles VIEW ngram_index
WHERE FulltextMatch(body, "%обуч%", "Wildcard" AS Mode)
LIMIT 20;
```

Пример запроса с `LIKE`:

```yql
SELECT id, title
FROM articles VIEW ngram_index
WHERE body LIKE "%обуч%ние%"
LIMIT 20;
```

Запрос с `LIKE` / `ILIKE` использует ту же логику, что и `FulltextMatch(body, ..., "Wildcard" AS Mode)`, и обращается к тому же N-граммовому индексу.

### Полнотекстовый индекс с фильтрацией {#filtered}

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

```yql
ALTER TABLE articles
  ADD INDEX ft_index
  GLOBAL USING fulltext_plain
  ON (user_id, body)
  WITH (tokenizer=standard, use_filter_lowercase=true);
```

Запросы к такому индексу должны включать предикат равенства для каждой колонки фильтрации:

```yql
SELECT id, title
FROM articles VIEW ft_index
WHERE user_id = 42 AND FulltextMatch(body, "поисковые термы")
LIMIT 20;
```

Поддерживается несколько колонок фильтрации. Предикаты равенства в `WHERE` могут быть указаны в любом порядке — YDB автоматически упорядочит их в соответствии с порядком колонок в индексе.

## Типы первичного ключа {#primary-key}

Внутри инвертированного индекса каждый проиндексированный документ идентифицируется числовым идентификатором документа (`doc_id`). Способ получения `doc_id` зависит от [первичного ключа](https://ydb.tech/docs/ru/concepts/glossary.md?version=main#primary-key) основной таблицы:

* **Первичный ключ из одной целочисленной колонки** (`Uint64`, `Int64`, `Uint32` или `Int32`) — значение первичного ключа используется напрямую в качестве `doc_id`. Это самая компактная форма, дополнительные структуры не создаются.
* **Любой другой первичный ключ** (например, `Utf8`, `String`, другие нецелочисленные типы или составной ключ из нескольких колонок) — YDB поддерживает системную колонку `__ydb_row_id` типа `Uint64` и использует её в качестве `doc_id`.

При создании полнотекстового индекса на таблице, первичный ключ которой не является одной целочисленной колонкой, YDB автоматически:

* добавляет в таблицу колонку `__ydb_row_id` — существующие строки заполняются во время построения индекса;
* генерирует значение `__ydb_row_id` для каждой строки, в которой эта колонка не указана в `INSERT` / `UPSERT`;
* создаёт уникальный [вторичный индекс](https://ydb.tech/docs/ru/concepts/glossary.md?version=main#secondary-index) с именем `__ydb_unique_row_id` по колонке `__ydb_row_id`. Во время запроса этот индекс сопоставляет найденный `__ydb_row_id` с первичным ключом таблицы перед чтением строки из основной таблицы.

Если на таблице создаётся несколько полнотекстовых индексов, все они **переиспользуют** одну и ту же колонку `__ydb_row_id` и индекс `__ydb_unique_row_id` — эти структуры создаются для таблицы только один раз.

{% note warning %}

Колонкой `__ydb_row_id` и индексом `__ydb_unique_row_id` управляет YDB:

* Позвольте YDB заполнять `__ydb_row_id` — не указывайте её в `INSERT` / `UPSERT`, и значение проставится автоматически. Модификация колонки `__ydb_row_id` пользователем запрещена — любые попытки задать или изменить значение этой колонки будут отклонены.
* Индекс `__ydb_unique_row_id` нельзя удалить, пока от него зависит хотя бы один полнотекстовый индекс. Сначала удалите зависимые полнотекстовые индексы.

{% endnote %}

## Полный синтаксис полнотекстовых индексов {#syntax}

Создание полнотекстового индекса:

* при создании таблицы: [CREATE TABLE](https://ydb.tech/docs/ru/yql/reference/syntax/create_table/fulltext_index.md?version=main);
* добавление к существующей таблице: [ALTER TABLE](https://ydb.tech/docs/ru/yql/reference/syntax/alter_table/indexes.md?version=main).

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

* [VIEW (Полнотекстовый индекс)](https://ydb.tech/docs/ru/yql/reference/syntax/select/fulltext_index.md?version=main).

Функции и выражения для полнотекстового поиска:

* [Базовые функции полнотекстового поиска](https://ydb.tech/docs/ru/yql/reference/builtins/fulltext.md?version=main);
* [LIKE / ILIKE с полнотекстовым индексом](https://ydb.tech/docs/ru/yql/reference/syntax/expressions.md?version=main#like-ilike-with-fulltext-index).

{% note info %}

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

Если не использовать выражение `VIEW`, запросы с `FulltextMatch` / `FulltextScore` завершатся с ошибкой.

Это ограничение может быть снято в будущих версиях YDB.

{% endnote %}

## Обновление полнотекстовых индексов {#update}

Полнотекстовые индексы автоматически поддерживаются при модификации данных. Таблицы с полнотекстовыми индексами поддерживают:

* `INSERT`
* `UPSERT`
* `REPLACE`
* `UPDATE`
* `DELETE`

## Удаление полнотекстовых индексов {#drop}

```yql
ALTER TABLE articles DROP INDEX ft_index;
```

## Ограничения {#limitations}

* Для таблиц с нецелочисленным или составным первичным ключом автоматически создаются системная колонка `__ydb_row_id` и уникальный индекс `__ydb_unique_row_id` (см. [Типы первичного ключа](#primary-key)).
* `BulkUpsert` не поддерживается для таблиц с полнотекстовыми индексами.
* Использование полнотекстового индекса необходимо задавать явно с помощью `VIEW IndexName`.
* В одном полнотекстовом индексе индексируется одна текстовая колонка.
* `FulltextMatch` / `FulltextScore` нельзя использовать c `OR` или `NOT`. Допускается комбинация с другими предикатами через `AND`.
* В одном чтении через `VIEW` поддерживается только один полнотекстовый предикат: нельзя использовать несколько `FulltextScore` и нельзя смешивать `FulltextMatch` и `FulltextScore` в одном `WHERE`.
* Для доступа к индексу по релевантности требуется ограничение `FulltextScore(...) > 0` в `WHERE` (иначе запрос завершится с ошибкой).
* [Полнотекстовые индексы с фильтрацией](#filtered): для каждой колонки фильтрации требуется предикат равенства в `WHERE`.
* [Полнотекстовые индексы с фильтрацией](#filtered): колонки фильтрации не должны быть колонками первичного ключа.
