12.4. Дополнительные возможности#

12.4. Дополнительные возможности

12.4. Дополнительные возможности #

Этот раздел описывает дополнительные функции и операторы, которые полезны в связи с текстовым поиском.

12.4.1. Работа с документами #

Раздел 12.3.1 показал, как сырые текстовые документы могут быть преобразованы в значения типа tsvector. Tantor BE также предоставляет функции и операторы, которые могут использоваться для манипуляции документами, которые уже находятся в форме tsvector.

tsvector || tsvector

Оператор конкатенации tsvector возвращает вектор, который объединяет лексемы и информацию о позиции двух векторов, заданных в качестве аргументов. Позиции и метки веса сохраняются при конкатенации. Позиции, появляющиеся в правом векторе, смещаются наибольшей позицией, упомянутой в левом векторе, так что результат почти эквивалентен результату выполнения функции to_tsvector для конкатенации двух исходных строк документа. (Эквивалентность не является точной, потому что любые стоп-слова, удаленные из конца аргумента слева, не повлияют на результат, в то время как они бы повлияли на позиции лексем в аргументе справа, если бы использовалась текстовая конкатенация).

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

setweight(vector tsvector, weight "char") returns tsvector

setweight возвращает копию входного вектора, в котором каждая позиция помечена заданным весом weight, либо A, B, C, или D. (D является значением по умолчанию для новых векторов и поэтому не отображается при выводе). Эти метки сохраняются при конкатенации векторов, что позволяет разным словам из разных частей документа иметь разные веса при использовании функций ранжирования.

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

length(vector tsvector) returns integer

Возвращает количество лексем, хранящихся в векторе.

strip(vector tsvector) returns tsvector

Возвращает вектор, который содержит те же лексемы, что и заданный вектор, но не содержит информации о позиции или весе. Результат обычно намного меньше, чем у необработанного вектора, но он также менее полезен. Ранжирование по релевантности не работает так хорошо на обработанных векторах, как на необработанных. Кроме того, оператор <-> (СЛЕДУЕТ ЗА) tsquery никогда не будет соответствовать обработанному вводу, так как он не может определить расстояние между вхождениями лексемы.

Список всех функций, связанных с типом данных tsvector, доступен в Таблица 9.43.

12.4.2. Изменение запросов #

Раздел 12.3.2 показал, как сырые текстовые запросы могут быть преобразованы в значения типа tsquery. Tantor BE также предоставляет функции и операторы, которые могут использоваться для манипуляции запросами, которые уже находятся в форме tsquery.

tsquery && tsquery

Возвращает результат логической операции AND для двух заданных запросов.

tsquery || tsquery

Возвращает OR-комбинацию двух заданных запросов.

!! tsquery

Возвращает отрицание (NOT) заданного запроса.

tsquery <-> tsquery

Возвращает запрос, который ищет совпадение с первым заданным запросом, сразу за которым следует совпадение со вторым заданным запросом, используя оператор <-> (FOLLOWED BY) типа tsquery. Например:

SELECT to_tsquery('fat') <-> to_tsquery('cat | rat');
          ?column?
----------------------------
 'fat' <-> ( 'cat' | 'rat' )

tsquery_phrase(query1 tsquery, query2 tsquery [, distance integer ]) returns tsquery

Возвращает запрос, который ищет совпадение с первым заданным запросом, за которым следует совпадение со вторым заданным запросом на расстоянии ровно distance лексем, используя оператор <N> типа tsquery. Например:

SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10);
  tsquery_phrase
------------------
 'fat' <10> 'cat'

numnode(query tsquery) returns integer

Возвращает количество узлов (лексемы плюс операторы) в tsquery. Эта функция полезна для определения, является ли query значимым (возвращает > 0) или содержит только стоп-слова (возвращает 0). Примеры:

SELECT numnode(plainto_tsquery('the any'));
NOTICE:  query contains only stopword(s) or doesn't contain lexeme(s), ignored
 numnode
---------
       0

SELECT numnode('foo & bar'::tsquery);
 numnode
---------
       3

querytree(query tsquery) returns text

Возвращает часть tsquery, которая может быть использована для поиска в индексе. Эта функция полезна для обнаружения неподдающихся индексации запросов, например, содержащих только стоп-слова или только отрицательные термины. Например:

SELECT querytree(to_tsquery('defined'));
 querytree
-----------
 'defin'

SELECT querytree(to_tsquery('!defined'));
 querytree
-----------
 T

12.4.2.1. Переписывание запросов #

Семейство функций ts_rewrite выполняет поиск заданного подзапроса в tsquery и заменяет каждое его вхождение на заменяющий подзапрос. В сущности, эта операция является версией замены подстроки, специфичной для tsquery. Комбинацию цели и замены можно рассматривать как правило переписывания запроса. Совокупность таких правил переписывания может быть мощным инструментом поиска. Например, вы можете расширить поиск с помощью синонимов (например, new york, big apple, nyc, gotham) или сузить поиск, чтобы направить пользователя на актуальную тему. Есть некоторое перекрытие функциональности между этой функцией и тезаурусными словарями (Раздел 12.6.4). Однако, вы можете изменять набор правил переписывания на лету, без переиндексации, в то время как обновление тезауруса требует переиндексации, чтобы быть эффективным.

ts_rewrite (query tsquery, target tsquery, substitute tsquery) returns tsquery

Эта форма функции ts_rewrite просто применяет одно правило перезаписи: target заменяется на substitute везде, где оно появляется в query. Например:

SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);
 ts_rewrite
------------
 'b' & 'c'

ts_rewrite (query tsquery, select text) returns tsquery

Эта форма функции ts_rewrite принимает начальный query и команду SQL select, которая задается в виде текстовой строки. select должен возвращать два столбца типа tsquery. Для каждой строки результата select значения первого столбца (цель) заменяются значениями второго столбца (замена) внутри текущего значения query. Например:

CREATE TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');

SELECT ts_rewrite('a & b'::tsquery, 'SELECT t,s FROM aliases');
 ts_rewrite
------------
 'b' & 'c'

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

Давайте рассмотрим реальный астрономический пример. Мы расширим запрос supernovae с помощью правил переписывания, основанных на таблице.

CREATE TABLE aliases (t tsquery primary key, s tsquery);
INSERT INTO aliases VALUES(to_tsquery('supernovae'), to_tsquery('supernovae|sn'));

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
           ts_rewrite
---------------------------------
 'crab' & ( 'supernova' | 'sn' )

можно изменить правила перезаписи, просто обновив таблицу:

UPDATE aliases
SET s = to_tsquery('supernovae|sn & !nebulae')
WHERE t = to_tsquery('supernovae');

SELECT ts_rewrite(to_tsquery('supernovae & crab'), 'SELECT * FROM aliases');
                 ts_rewrite
---------------------------------------------
 'crab' & ( 'supernova' | 'sn' & !'nebula' )

Переписывание может быть медленным, когда есть много правил переписывания, так как оно проверяет каждое правило на возможное совпадение. Чтобы отфильтровать очевидно неподходящие правила, можно использовать операторы содержания для типа tsquery. В приведенном ниже примере мы выбираем только те правила, которые могут соответствовать исходному запросу:

SELECT ts_rewrite('a & b'::tsquery,
                  'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t');
 ts_rewrite
------------
 'b' & 'c'

12.4.3. Триггеры для автоматических обновлений #

Примечание

Метод, описанный в данном разделе, устарел и заменен использованием хранимых генерируемых столбцов, как описано в Раздел 12.2.2.

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

tsvector_update_trigger(tsvector_column_name,​ config_name, text_column_name [, ... ])
tsvector_update_trigger_column(tsvector_column_name,​ config_column_name, text_column_name [, ... ])

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

CREATE TABLE messages (
    title       text,
    body        text,
    tsv         tsvector
);

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);

INSERT INTO messages VALUES('title here', 'the body text is here');

SELECT * FROM messages;
   title    |         body          |            tsv
------------+-----------------------+----------------------------
 title here | the body text is here | 'bodi':4 'text':5 'titl':1

SELECT title, body FROM messages WHERE tsv @@ to_tsquery('title & body');
   title    |         body
------------+-----------------------
 title here | the body text is here

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

Первый аргумент триггера должен быть именем столбца tsvector, который будет обновляться. Второй аргумент указывает конфигурацию полнотекстового поиска, которая будет использоваться для выполнения преобразования. Для tsvector_update_trigger имя конфигурации просто указывается вторым аргументом триггера. Оно должно быть указано с указанием схемы, как показано выше, чтобы поведение триггера не менялось при изменении search_path. Для tsvector_update_trigger_column второй аргумент триггера является именем другого столбца таблицы, который должен иметь тип regconfig. Это позволяет выбирать конфигурацию для каждой строки. Оставшиеся аргументы - это имена текстовых столбцов (типа text, varchar или char). Они будут включены в документ в указанном порядке. Значения NULL будут прне указаны (но другие столбцы все равно будут проиндексированы).

Ограничением встроенных триггеров является то, что они обрабатывают все входные столбцы одинаково. Чтобы обрабатывать столбцы по-разному — например, весить заголовок по-другому, чем текст — необходимо написать пользовательский триггер. Вот пример использования PL/pgSQL в качестве языка триггера:

CREATE FUNCTION messages_trigger() RETURNS trigger AS $$
begin
  new.tsv :=
     setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
     setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
  return new;
end
$$ LANGUAGE plpgsql;

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
    ON messages FOR EACH ROW EXECUTE FUNCTION messages_trigger();

Учтите, что важно явно указывать имя конфигурации при создании значений tsvector внутри триггеров, чтобы содержимое столбца не подвергалось изменениям default_text_search_config. Невыполнение этого может привести к проблемам, таким как изменение результатов поиска после выполнения операций резервного копирования и восстановления.

12.4.4. Сбор статистики документа #

Функция ts_stat полезна для проверки вашей конфигурации и поиска кандидатов на стоп-слова.

ts_stat(sqlquery text, [ weights text, ]
        OUT word text, OUT ndoc integer,
        OUT nentry integer) returns setof record

sqlquery - это текстовое значение, содержащее SQL-запрос, который должен возвращать один столбец типа tsvector. ts_stat выполняет запрос и возвращает статистику о каждом отдельном лексеме (слове), содержащемся в данных типа tsvector. Возвращаемые столбцы:

  • word text — значение лексемы

  • ndoc integer — количество документов (tsvectors), в которых встречается данное слово

  • nentry integer — общее количество вхождений слова

Если веса weights предоставлены, считаются только вхождения с одним из этих весов.

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

SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

То же самое, но считаются только вхождения слов с весом A или B:

SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;