12.2. Таблицы и индексы

В предыдущем разделе приводились примеры, которые показывали, как можно выполнить сопоставление с простыми текстовыми константами. В этом разделе показывается, как находить текст в таблице, возможно с применением индексов.

12.2.1. Поиск в таблице

Полнотекстовый поиск можно выполнить, не применяя индекс. Следующий простой запрос выводит заголовок (title) каждой строки, содержащей слово friend в поле body:

SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');

При этом также будут найдены связанные слова, такие как friends и friendly, так как все они сводятся к одной нормализованной лексеме.

В показанном выше примере для разбора и нормализации строки явно выбирается конфигурация english. Хотя параметры, задающие конфигурацию, можно опустить:

SELECT title
FROM pgweb
WHERE to_tsvector(body) @@ to_tsquery('friend');

Такой запрос будет использовать конфигурацию, заданную в параметре default_text_search_config.

В следующем более сложном примере выбираются десять документов, изменённых последними, со словами create и table в полях title или body:

SELECT title
FROM pgweb
WHERE to_tsvector(title || ' ' || body) @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;

Чтобы найти строки, содержащие NULL в одном из полей, нужно воспользоваться функцией coalesce, но здесь мы опустили её вызовы для краткости.

Хотя такие запросы будут работать и без индекса, для большинства приложений скорость будет неприемлемой; этот подход рекомендуется только для нерегулярного поиска и динамического содержимого. Для практического применения полнотекстового поиска обычно создаются индексы.

12.2.2. Создание индексов

Для ускорения текстового поиска мы можем создать индекс GIN (см. Раздел 12.9):

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', body));

Заметьте, что здесь используется функция to_tsvector с двумя аргументами. В выражениях, определяющих индексы, можно использовать только функции, в которых явно задаётся имя конфигурации текстового поиска (см. Раздел 11.7). Это объясняется тем, что содержимое индекса не должно зависеть от значения параметра default_text_search_config. В противном случае содержимое индекса может быть неактуальным, если разные его элементы tsvector будут создаваться с разными конфигурациями текстового поиска и нельзя будет понять, какую именно использовать. Выгрузить и восстановить такой индекс будет невозможно.

Так как при создании индекса использовалась версия to_tsvector с двумя аргументами, этот индекс будет использоваться только в запросах, где to_tsvector вызывается с двумя аргументами и во втором передаётся имя той же конфигурации. То есть, WHERE to_tsvector('english', body) @@ 'a & b' сможет использовать этот индекс, а WHERE to_tsvector(body) @@ 'a & b' — нет. Это гарантирует, что индекс будет использоваться только с той конфигурацией, с которой создавались его элементы.

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

CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector(config_name, body));

где config_name — имя колонки в таблице pgweb. Так можно сохранить имя конфигурации, связанной с элементом индекса, и, таким образом, иметь в одном индексе элементы с разными конфигурациями. Это может быть полезно, например, когда в коллекции документов хранятся документы на разных языках. И в этом случае в запросах должен использоваться тот же индекс (с таким же образом задаваемой конфигурацией), например, так: WHERE to_tsvector(config_name, body) @@ 'a & b'.

Индексы могут создаваться даже по объединению колонок:

CREATE INDEX pgweb_idx ON pgweb
  USING gin(to_tsvector('english', title || ' ' || body));

Ещё один вариант — создать отдельную колонку tsvector, в которой сохранить результат to_tsvector. Следующий пример показывает, как можно подготовить для индексации объединённое содержимое колонок title и body, применив функцию coalesce для получения желаемого результата, даже когда одна из колонок NULL:

ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
UPDATE pgweb SET textsearchable_index_col =
     to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));

Затем мы создаём индекс GIN для ускорения поиска:

CREATE INDEX textsearch_idx ON pgweb USING gin(textsearchable_index_col);

Теперь мы можем быстро выполнять полнотекстовый поиск:

SELECT title
FROM pgweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC
LIMIT 10;

Когда представление tsvector хранится в отдельной колонке, необходимо создать триггер, который будет поддерживать колонку с tsvector в актуальном состоянии при любых изменениях title или body. Как это сделать, рассказывается в Подразделе 12.4.3.

Хранение вычисленного выражения индекса в отдельной колонке даёт ряд преимуществ. Во-первых, для использования индекса в запросах не нужно явно указывать имя конфигурации текстового поиска. Как показано в вышеприведённом примере, в этом случае запрос может зависеть от default_text_search_config. Во-вторых, поиск выполняется быстрее, так как для проверки соответствия данных индексу не нужно повторно выполнять to_tsvector. (Это актуально больше для индексов GiST, чем для GIN; см. Раздел 12.9.) С другой стороны, схему с индексом по выражению проще реализовать и она позволяет сэкономить место на диске, так как представление tsvector не хранится явно.