12.3. Управление полнотекстовым поиском#
12.3. Управление полнотекстовым поиском #
Чтобы реализовать полнотекстовый поиск, необходимо иметь функцию для создания tsvector
из документа и tsquery
из запроса пользователя. Также нам нужна функция, которая сравнивает документы с учетом их релевантности для запроса, чтобы вернуть результаты в полезном порядке. Важно также уметь красиво отображать результаты. Tantor BE предоставляет поддержку всех этих функций.
12.3.1. Разбор документов #
Tantor BE предоставляет функцию to_tsvector
для преобразования документа в тип данных tsvector
.
to_tsvector([config
regconfig
, ]document
text
) returnstsvector
to_tsvector
разбирает текстовый документ на компоненты,
сокращает компоненты до лексем и возвращает tsvector
, который
перечисляет лексемы вместе с их позициями в документе.
Документ обрабатывается в соответствии с указанной или установленной
конфигурацией текстового поиска по умолчанию.
Вот простой пример:
SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats'); to_tsvector ----------------------------------------------------- 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4
В приведенном выше примере мы видим, что полученный tsvector
не содержит слов a
, on
или it
, слово rats
стало rat
, а знак пунктуации -
был проигнорирован.
Функция to_tsvector
вызывает внутренний парсер, который разбивает текст документа на компоненты и назначает каждому компоненту тип. Для каждого компонента консультируется список словарей (Раздел 12.6), который может варьироваться в зависимости от типа компонента. Первый словарь, который распознает компонент, генерирует один или несколько нормализованных лексем для представления компонента. Например, слово rats
становится rat
, потому что один из словарей распознал, что слово rats
является множественным числом слова rat
. Некоторые слова распознаются как стоп-слова (Раздел 12.6.1), что приводит к их игнорированию, так как они встречаются слишком часто, чтобы быть полезными при поиске. В нашем примере это слова a
, on
, и it
. Если ни один словарь из списка не распознает компонент, то он также игнорируется. В этом примере это произошло с знаком пунктуации -, так как на самом деле нет словарей, назначенных для его типа компонента (Space symbols
), что означает, что пробельные компоненты никогда не будут индексироваться. Выбор парсера, словарей и типов компонентов для индексации определяется выбранной конфигурацией текстового поиска (Раздел 12.7). В одной базе данных может быть много разных конфигураций, и предопределенные конфигурации доступны для разных языков. В нашем примере мы использовали конфигурацию по умолчанию english
для английского языка.
Функция setweight
может быть использована для пометки записей tsvector
с заданным весом, где весом может быть одна из букв A
, B
, C
или D
.
Это обычно используется для отметки записей, поступающих из разных частей документа, таких как заголовок и тело. Позже эта информация может быть использована для ранжирования результатов поиска.
Поскольку to_tsvector
(NULL
) вернет
NULL
, рекомендуется использовать
coalesce
в случае, если поле может быть null.
Вот рекомендуемый метод создания
tsvector
из структурированного документа:
UPDATE tt SET ti = setweight(to_tsvector(coalesce(title,'')), 'A') || setweight(to_tsvector(coalesce(keyword,'')), 'B') || setweight(to_tsvector(coalesce(abstract,'')), 'C') || setweight(to_tsvector(coalesce(body,'')), 'D');
Здесь мы использовали функцию setweight
для пометки источника каждого лексемы в готовом значении tsvector
, а затем объединили помеченные значения tsvector
с помощью оператора конкатенации ||
. (Раздел 12.4.1 содержит подробности об этих операциях).
12.3.2. Разбор запросов #
Tantor BE предоставляет функции to_tsquery
, plainto_tsquery
, phraseto_tsquery
и websearch_to_tsquery
для преобразования запроса в тип данных tsquery
. Функция to_tsquery
предлагает больше возможностей, чем функции plainto_tsquery
и phraseto_tsquery
, но она более строга в отношении своего ввода. Функция websearch_to_tsquery
является упрощенной версией функции to_tsquery
с альтернативным синтаксисом, подобным тому, который используется поисковыми системами веб-страниц.
to_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
to_tsquery
создает значение tsquery
из
querytext
, который должен состоять из отдельных компонентов,
разделенных операторами tsquery
&
(И),
|
(ИЛИ), !
(НЕ) и
<->
(СЛЕДУЕТ ЗА), возможно, сгруппированных
с использованием скобок. Другими словами, входные данные для
to_tsquery
должны уже соответствовать общим правилам для
ввода tsquery
, описанным в Раздел 8.11.2. Разница заключается в том, что в то время как базовый
ввод tsquery
принимает компоненты на верном значении,
to_tsquery
нормализует каждый компонент в лексему, используя
указанную или заданную конфигурацию, и отбрасывает любые компоненты, которые являются
стоп-словами в соответствии с конфигурацией. Например:
SELECT to_tsquery('english', 'The & Fat & Rats'); to_tsquery --------------- 'fat' & 'rat'
Как и в основном вводе типа tsquery
, вес(а) можно присоединить к каждому
лексеме, чтобы ограничить ее соответствие только лексемам типа tsvector
с этими
весами. Например:
SELECT to_tsquery('english', 'Fat | Rats:AB'); to_tsquery ------------------ 'fat' | 'rat':AB
Также, *
может быть присоединен к лексеме для указания префиксного сопоставления:
SELECT to_tsquery('supern:*A & star:A*B'); to_tsquery -------------------------- 'supern':*A & 'star':*AB
Такой лексема будет соответствовать любому слову в tsvector
, которое начинается с заданной строки.
to_tsquery
также может принимать фразы, заключенные в апострофы. Это особенно полезно, когда конфигурация включает тезаурусный словарь, который может срабатывать на такие фразы. В приведенном ниже примере тезаурус содержит правило supernovae stars : sn
:
SELECT to_tsquery('''supernovae stars'' & !crab'); to_tsquery --------------- 'sn' & !'crab'
Без кавычек, to_tsquery
будет генерировать синтаксическую ошибку для компонентов, которые не разделены оператором AND, OR или FOLLOWED BY.
plainto_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
plainto_tsquery
преобразует неформатированный текст
querytext
в значение tsquery
.
Текст анализируется и нормализуется так же, как и для функции to_tsvector
,
затем оператор &
(AND) tsquery
вставляется
между выжившими словами.
Пример:
SELECT plainto_tsquery('english', 'The Fat Rats'); plainto_tsquery ----------------- 'fat' & 'rat'
Обратите внимание, что функция plainto_tsquery
не будет
распознавать операторы tsquery
, метки веса
или метки префиксного совпадения в своем вводе:
SELECT plainto_tsquery('english', 'The Fat & Rats:C'); plainto_tsquery --------------------- 'fat' & 'rat' & 'c'
Здесь все знаки препинания ввода были отброшены.
phraseto_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
phraseto_tsquery
ведет себя почти так же, как и
plainto_tsquery
, за исключением того, что он вставляет
оператор <->
(FOLLOWED BY) между
оставшимися словами вместо оператора &
(AND).
Кроме того, стоп-слова не просто отбрасываются, а учитываются путем
вставки операторов <
вместо
операторов N
><->
. Эта функция полезна
при поиске точных последовательностей лексем, так как операторы FOLLOWED BY
проверяют порядок лексем, а не только наличие всех лексем.
Пример:
SELECT phraseto_tsquery('english', 'The Fat Rats'); phraseto_tsquery ------------------ 'fat' <-> 'rat'
Как и функция plainto_tsquery
, функция phraseto_tsquery
не распознает операторы tsquery
, метки веса или метки префиксного совпадения в своем вводе:
SELECT phraseto_tsquery('english', 'The Fat & Rats:C'); phraseto_tsquery ----------------------------- 'fat' <-> 'rat' <-> 'c'
websearch_to_tsquery([config
regconfig
, ]querytext
text
) returnstsquery
websearch_to_tsquery
создает значение tsquery
из querytext
с использованием альтернативного синтаксиса, в котором простой неформатированный текст является допустимым запросом.
В отличие от функций plainto_tsquery
и phraseto_tsquery
, она также распознает определенные операторы. Кроме того, эта функция никогда не вызывает синтаксические ошибки, что позволяет использовать необработанный пользовательский ввод для поиска.
Поддерживается следующий синтаксис:
unquoted text
: текст, без кавычек будет преобразован в термины, разделенные операторами&
, как если бы он был обработан функциейplainto_tsquery
."quoted text"
: текст внутри кавычек будет преобразован в термины, разделенные операторами<->
, как если бы они были обработаны функциейphraseto_tsquery
.OR
: слово “or” будет преобразовано в оператор|
.-
: тире будет преобразовано в оператор!
.
Другая пунктуация игнорируется. Так что, как plainto_tsquery
и phraseto_tsquery
, функция websearch_to_tsquery
не будет распознавать операторы tsquery
, метки веса или метки префиксного совпадения в своем вводе.
Примеры:
SELECT websearch_to_tsquery('english', 'The fat rats'); websearch_to_tsquery ---------------------- 'fat' & 'rat' (1 row) SELECT websearch_to_tsquery('english', '"supernovae stars" -crab'); websearch_to_tsquery ---------------------------------- 'supernova' <-> 'star' & !'crab' (1 row) SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"'); websearch_to_tsquery ----------------------------------- 'sad' <-> 'cat' | 'fat' <-> 'rat' (1 row) SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"'); websearch_to_tsquery --------------------------------------- 'signal' & !( 'segment' <-> 'fault' ) (1 row) SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->'); websearch_to_tsquery ---------------------- 'dummi' & 'queri' (1 row)
12.3.3. Ранжирование результатов поиска #
Ранжирование пытается измерить, насколько релевантны документы для конкретного запроса, чтобы при наличии множества совпадений наиболее релевантные могли быть показаны первыми. Tantor BE предоставляет две предопределенные функции ранжирования, которые учитывают лексическую, пространственную и структурную информацию; то есть они учитывают, как часто термины запроса появляются в документе, насколько близко друг к другу находятся термины в документе и насколько важна часть документа, где они встречаются. Однако понятие релевантности является нечетким и очень зависит от конкретного приложения. Различные приложения могут требовать дополнительной информации для ранжирования, например, времени модификации документа. Встроенные функции ранжирования являются только примерами. Вы можете написать свои собственные функции ранжирования и/или комбинировать их результаты с дополнительными факторами, чтобы соответствовать вашим конкретным потребностям.
Две функции ранжирования, доступные в настоящее время, это:
-
ts_rank([
weights
float4[]
, ]vector
tsvector
,query
tsquery
[,normalization
integer
]) returnsfloat4
Ранжирует векторы на основе частоты совпадения их лексем.
-
ts_rank_cd([
weights
float4[]
, ]vector
tsvector
,query
tsquery
[,normalization
integer
]) returnsfloat4
Эта функция вычисляет ранжирование плотности покрытия для данного вектора документа и запроса, как описано в статье Кларка, Кормака и Тудхоупа "Ранжирование релевантности для запросов от одного до трех терминов" в журнале "Information Processing and Management", 1999 года. Плотность покрытия похожа на ранжирование
ts_rank
, за исключением того, что учитывается близость совпадающих лексем друг к другу.Эта функция требует информации о позиции лексем для выполнения своего вычисления. Поэтому она игнорирует любые “удаленные” лексемы в
tsvector
. Если во входных данных нет непроанализированных лексем, результат будет равен нулю. (См. Раздел 12.4.1 для получения дополнительной информации о функцииstrip
и позиционной информации вtsvector
s).
Для обоих этих функций, необязательный аргумент weights
предлагает возможность взвешивать экземпляры слов более или менее сильно в зависимости от их маркировки. Массивы весов указывают, насколько сильно взвешивать каждую категорию слов, в следующем порядке:
{D-weight, C-weight, B-weight, A-weight}
Если не предоставлены weights
,
то используются следующие значения по умолчанию:
{0.1, 0.2, 0.4, 1.0}
Обычно веса используются для отметки слов из специальных областей документа, таких как заголовок или начальный абстракт, чтобы они могли быть рассмотрены с большей или меньшей важностью, чем слова в теле документа.
Поскольку в длинном документа вероятнее найти искомый термин, разумно учитывать размер документа, например, сто слов документа с пятью вхождениями искомого слова, вероятно, более релевантен, чем тысяча слов документа с пятью вхождениями. Обе функции ранжирования принимают целочисленный параметр normalization
, который определяет, должна ли и каким образом длина документа влиять на его ранг. Целочисленный параметр управляет несколькими поведениями, поэтому он является битовой маской: вы можете указать одно или несколько поведений, используя |
(например, 2|4
).
0 (по умолчанию игнорирует длину документа)
1 делит ранг на 1 + логарифм длины документа
2 делит ранг на длину документа
4 делит ранг на среднее гармоническое расстояние между интервалами (это реализовано только функцией
ts_rank_cd
)8 делит ранг на количество уникальных слов в документе
16 делит ранг на 1 + логарифм числа уникальных слов в документе
32 делится на ранг плюс 1
Если указано более одного флага, преобразования применяются в указанном порядке.
Важно отметить, что функции ранжирования не используют никакой глобальной информации, поэтому невозможно произвести справедливую нормализацию до 1% или 100%, как иногда требуется. Опция нормализации 32 (rank/(rank+1)
) может быть применена для масштабирования всех рангов в диапазон от нуля до единицы, но, конечно, это всего лишь косметическое изменение; оно не повлияет на порядок результатов поиска.
Вот пример, который выбирает только десять наивысших совпадений:
SELECT title, ts_rank_cd(textsearch, query) AS rank FROM apod, to_tsquery('neutrino|(dark & matter)') query WHERE query @@ textsearch ORDER BY rank DESC LIMIT 10; title | rank -----------------------------------------------+---------- Neutrinos in the Sun | 3.1 The Sudbury Neutrino Detector | 2.4 A MACHO View of Galactic Dark Matter | 2.01317 Hot Gas and Dark Matter | 1.91171 The Virgo Cluster: Hot Plasma and Dark Matter | 1.90953 Rafting for Solar Neutrinos | 1.9 NGC 4650A: Strange Galaxy and Dark Matter | 1.85774 Hot Gas and Dark Matter | 1.6123 Ice Fishing for Cosmic Neutrinos | 1.6 Weak Lensing Distorts the Universe | 0.818218
Это тот же пример с использованием нормализованного ранжирования:
SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank FROM apod, to_tsquery('neutrino|(dark & matter)') query WHERE query @@ textsearch ORDER BY rank DESC LIMIT 10; title | rank -----------------------------------------------+------------------- Neutrinos in the Sun | 0.756097569485493 The Sudbury Neutrino Detector | 0.705882361190954 A MACHO View of Galactic Dark Matter | 0.668123210574724 Hot Gas and Dark Matter | 0.65655958650282 The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973 Rafting for Solar Neutrinos | 0.655172410958162 NGC 4650A: Strange Galaxy and Dark Matter | 0.650072921219637 Hot Gas and Dark Matter | 0.617195790024749 Ice Fishing for Cosmic Neutrinos | 0.615384618911517 Weak Lensing Distorts the Universe | 0.450010798361481
Ведение рейтинга может быть дорогостоящим, так как требует обращения к tsvector
каждого соответствующего документа, что может быть связано с операциями ввода-вывода и, следовательно, замедлить процесс. К сожалению, практически невозможно избежать этого, так как обычно запросы приводят к большому количеству совпадений.
12.3.4. Выделение результатов #
Для представления результатов поиска идеально показывать часть каждого документа и
как он связан с запросом. Обычно поисковые системы показывают фрагменты
документа с выделенными поисковыми терминами. Tantor BE
предоставляет функцию ts_headline
, которая
реализует эту функциональность.
ts_headline([config
regconfig
, ]document
text
,query
tsquery
[,options
text
]) returnstext
ts_headline
принимает документ вместе с запросом и возвращает отрывок из документа, в котором термины из запроса выделены. В частности, функция использует запрос для выбора соответствующих текстовых фрагментов, а затем выделяет все слова, которые встречаются в запросе, даже если позиции этих слов не соответствуют ограничениям запроса. Конфигурация, используемая для разбора документа, может быть указана с помощью config
; если config
не указан, используется конфигурация default_text_search_config
.
Если указана строка options
, она должна состоять из списка, разделенного запятыми, из одной или нескольких пар option
=
value
.
Доступные варианты:
MaxWords
,MinWords
(целые числа): эти числа определяют самые длинные и самые короткие заголовки для вывода. Значения по умолчанию - 35 и 15.ShortWord
(integer): слова этой длины или меньше будут удалены в начале и конце заголовка, если они не являются поисковыми терминами. Значение по умолчанию - три, что исключает общие английские артикли.HighlightAll
(boolean): еслиtrue
, весь документ будет использоваться в качестве заголовка, игнорируя предыдущие три параметра. По умолчаниюfalse
.MaxFragments
(целое число): максимальное количество текстовых фрагментов для отображения. Значение по умолчанию - ноль, что выбирает метод генерации заголовков без фрагментации. Значение больше нуля выбирает метод генерации заголовков с фрагментацией (см. ниже).StartSel
,StopSel
(строки): строки, которыми отделяются слова запроса, появляющиеся в документе, чтобы отличить их от других слов. Значения по умолчанию - “<b>
” и “</b>
”, которые могут быть подходящими для вывода в HTML.FragmentDelimiter
(строка): Когда отображается более одного фрагмента, они будут разделены этой строкой. По умолчанию используется “...
”.
Эти имена опций распознаются без учета регистра. Вы должны заключать строковые значения в двойные кавычки, если они содержат пробелы или запятые.
В генерации заголовков, не основанной на фрагментах, функция ts_headline
находит совпадения для заданного запроса query
и выбирает одно из них для отображения, предпочитая совпадения, содержащие больше слов запроса в пределах допустимой длины заголовка.
В генерации заголовков на основе фрагментов функция ts_headline находит совпадения запроса и разбивает каждое совпадение на фрагменты, содержащие не более MaxWords
слов, предпочитая фрагменты с большим количеством слов запроса и, при возможности, расширяя фрагменты, чтобы включить окружающие слова. Режим на основе фрагментов более полезен, когда совпадения запроса охватывают большие разделы документа или когда необходимо отобразить несколько совпадений.
В любом режиме, если не удается определить совпадения запроса, будет отображен один фрагмент из первых MinWords
слов в документе.
Например:
SELECT ts_headline('english', 'The most common type of search is to find all documents containing given query terms and return them in order of their similarity to the query.', to_tsquery('english', 'query & similarity')); ts_headline ------------------------------------------------------------ containing given <b>query</b> terms + and return them in order of their <b>similarity</b> to the+ <b>query</b>. SELECT ts_headline('english', 'Search terms may occur many times in a document, requiring ranking of the search matches to decide which occurrences to display in the result.', to_tsquery('english', 'search & term'), 'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>'); ts_headline ------------------------------------------------------------ <<Search>> <<terms>> may occur + many times ... ranking of the <<search>> matches to decide
ts_headline
использует исходный документ, а не
tsvector
сводку, поэтому он может работать медленно и должен использоваться с
осторожностью.