12.4. Дополнительные возможности
В этом разделе описываются дополнительные функции и операторы, которые могут быть полезны при поиске текста.
12.4.1. Обработка документов
В Подразделе 12.3.1 показывалось, как обычные текстовые документы можно преобразовать в значения tsvector
. PostgreSQL предлагает также набор функций и операторов для обработки документов, уже представленных в формате tsvector
.
-
tsvector
||tsvector
Оператор конкатенации значений
tsvector
возвращает вектор, объединяющий лексемы и позиционную информацию двух векторов, переданных ему в аргументах. В полученном результате сохраняются позиции и метки весов. При этом позиции в векторе справа сдвигаются на максимальное значение позиции в векторе слева, что почти равносильно применениюto_tsvector
к результату конкатенации двух исходных строк документов. (Почти, потому что стоп-слова, исключаемые в конце левого аргумента, при конкатенации исходных строк влияют на позиции лексем в правой части, а при конкатенацииtsvector
— нет.)Преимущество же конкатенации документов в векторной форме по сравнению с конкатенацией текста до вызова
to_tsvector
заключается в том, что так можно разбирать разные части документа, применяя разные конфигурации. И так как функцияsetweight
помечает все лексемы данного вектора одинаково, разбирать текст и выполнятьsetweight
нужно до объединения разных частей документа с подразумеваемым разным весом.-
setweight(
вектор
tsvector
,вес
"char"
) returnstsvector
setweight
возвращает копию входного вектора, помечая в ней каждую позицию заданнымвесом
, меткойA
,B
,C
илиD
. (МеткаD
по умолчанию назначается всем векторам, так что при выводе она опускается.) Эти метки сохраняются при конкатенации векторов, что позволяет придавать разные веса словам из разных частей документа и, как следствие, ранжировать их по-разному.Заметьте, что веса назначаются позициям, а не лексемам. Если входной вектор очищен от позиционной информации,
setweight
не делает ничего.-
length(
вектор
tsvector
) returnsinteger
Возвращает число лексем, сохранённых в векторе.
-
strip(
вектор
tsvector
) returnstsvector
Возвращает вектор с теми же лексемами, что и в данном, но без информации о позиции и весе. Очищенный вектор обычно оказывается намного меньше исходного, но при этом и менее полезным. С очищенными векторами хуже работает ранжирование, а также оператор
<->
(ПРЕДШЕСТВУЕТ) типаtsquery
никогда не найдёт соответствие в них, так как не сможет определить расстояние между вхождениями лексем.
Полный список связанных с tsvector
функций приведён в Таблице 9.43.
12.4.2. Обработка запросов
В Подразделе 12.3.2 показывалось, как обычные текстовые запросы можно преобразовывать в значения tsquery
. PostgreSQL предлагает также набор функций и операторов для обработки запросов, уже представленных в формате tsquery
.
-
tsquery
&&tsquery
Возвращает логическое произведение (AND) двух данных запросов.
-
tsquery
||tsquery
Возвращает логическое объединение (OR) двух данных запросов.
-
!!
tsquery
Возвращает логическое отрицание (NOT) данного запроса.
-
tsquery
<->tsquery
Возвращает запрос, который ищет соответствие первому данному запросу, за которым следует соответствие второму данному запросу, с применением оператора
<->
(ПРЕДШЕСТВУЕТ) типаtsquery
. Например:SELECT to_tsquery('fat') <-> to_tsquery('cat | rat'); ?column? ---------------------------- 'fat' <-> ( 'cat' | 'rat' )
-
tsquery_phrase(
запрос1
tsquery
,запрос2
tsquery
[,расстояние
integer
]) returnstsquery
Возвращает запрос, который ищет соответствие первому данному запросу, за которым следует соответствие второму данному запросу (точное число лексем между ними задаётся параметром
расстояние
), с применением оператора<
типаN
>tsquery
. Например:SELECT tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10); tsquery_phrase ------------------ 'fat' <10> 'cat'
-
numnode(
query
tsquery
) returnsinteger
Возвращает число узлов (лексем и операторов) в значении
tsquery
. Эта функция помогает определить, имеет ли смыслзапрос
(тогда её результат > 0) или он содержит только стоп-слова (тогда она возвращает 0). Примеры:SELECT numnode(plainto_tsquery('the any')); ЗАМЕЧАНИЕ: запрос поиска текста игнорируется, так как содержит только стоп-слова или не содержит лексем numnode --------- 0 SELECT numnode('foo & bar'::tsquery); numnode --------- 3
-
querytree(
query
tsquery
) returnstext
Возвращает часть
tsquery
, которую можно использовать для поиска по индексу. Эта функция помогает выявить неиндексируемые запросы, к примеру такие, которые содержат только стоп-слова или условия отрицания. Например:SELECT querytree(to_tsquery('defined')); querytree ----------- 'defin' SELECT querytree(to_tsquery('!defined')); querytree ----------- T
12.4.2.1. Перезапись запросов
Семейство запросов ts_rewrite
ищет в данном tsquery
вхождения целевого подзапроса и заменяет каждое вхождение указанной подстановкой. По сути эта операция похожа на замену подстроки в строке, только рассчитана на работу с tsquery
. Сочетание целевого подзапроса с подстановкой можно считать правилом перезаписи запроса. Набор таких правил перезаписи может быть очень полезен при поиске. Например, вы можете улучшить результаты, добавив синонимы (например, big apple
, nyc
и gotham
для new york
) или сузить область поиска, чтобы нацелить пользователя на некоторую область. Это в некотором смысле пересекается с функциональностью тезаурусов (Подраздел 12.6.4). Однако при таком подходе вы можете изменять правила перезаписи «на лету», тогда как при обновлении тезауруса необходима переиндексация.
-
ts_rewrite (
query
tsquery
,цель
tsquery
,замена
tsquery
) returnstsquery
Эта форма
ts_rewrite
просто применяет одно правило перезаписи:цель
заменяетсяподстановкой
везде, где она находится взапросе
. Например:SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery); ts_rewrite ------------ 'b' & 'c'
-
ts_rewrite (
query
tsquery
,выборка
text
) returnstsquery
Эта форма
ts_rewrite
принимает начальныйзапрос
и SQL-командуselect
, которая задаётся текстовой строкой. Командаselect
должна выдавать два столбца типаtsquery
. Для каждой строки результатаselect
вхождения первого столбца (цели) заменяются значениями второго столбца (подстановкой) в текстезапроса
. Например: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_update_trigger(столбец_tsvector
,имя_конфигурации
,столбец_текста
[, ...]) tsvector_update_trigger_column(столбец_tsvector
,столбец_конфигурации
,столбец_текста
[, ...])
Эти триггерные функции автоматически вычисляют значение для столбца 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(sql_запрос
text
, [веса
text
,] OUTслово
text
, OUTчисло_док
integer
, OUTчисло_вхожд
integer
) returnssetof record
Здесь sql_запрос
— текстовая строка, содержащая SQL-запрос, который должен возвращать один столбец tsvector
. Функция ts_stat
выполняет запрос и возвращает статистику по каждой отдельной лексеме (слову), содержащейся в данных tsvector
. Её результат представляется в столбцах
слово
text
— значение лексемычисло_док
integer
— число документов (значенийtsvector
), в которых встретилось словочисло_вхожд
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;