|
| ||||||||||||
| ||||||||||||
|
2007 г
Введение в полнотекстовый поиск в PostgreSQLОлег Бартунов, Федор СигаевАвторское неформальное описание полнотекстового поиска встроенного в PostgreSQL версии* 8.3+, примеры и рекомендации по настройке. Также приведен справочник SQL команд для управления полнотекстовым поиском. Полное описание полнотекстового поиска доступно на сайте разработчиков [FTSBOOK]. Содержание
ВведениеПолнотекстовый поиск в базах данных является одним из востребованных механизмов доступа к содержимому любой современной информационной системы, которые хранят метаинформацию, а зачастую, и сами документы, в базе данных. Современные веб-сайты, по сути, являются интерфейсом, способом организации доступа к базам данных. По мере накопления документов в системе неминуемо возникает проблема организации эффективной навигации по системе, чтобы посетитель сайта смог за минимальное количество кликов найти нужный документ. Помимо стандартной, зачастую ручной, навигации с использованием рубрикации (тематической, по типу материалов, категории пользователей и т.д.), полнотекстовый поиск является одним из самых эффективных методов навигации, особенно для новичков, незнакомых с устройством сайта. Из нашего повседневного опыта мы понимаем, что хороший поиск - это поиск, который в ответ на наш запрос быстро найдет релевантные документы. И такие машины, казалось бы, существуют, например, широко известные поисковые машины как глобальные - "Google", так и наши российские - "Яндекс", "Рамблер". Более того, существует большое количество поисковиков, платных и бесплатных, которые позволяют индексировать всю вашу коллекцию документов и организовать вполне качественный поиск. Владельцу сайта остается только "скармливать" таким поисковикам контент по мере его появления. Это можно организовать несколькими способами - доступ через http-протокол, используя URL документа, как это делают большие внешние поисковики, или организация доступа к содержимому базы данных. В обоих случаях полнотекстовый индекс является внешним по отношению к базе данных. Часто такой подход оправдан и хорошо работает на многих сайтах, несмотря на некоторые недостатки, такие как неполная синхронизация содержимого БД, нетранзакционность, отсутствие доступа к метаданным и использование их для ограничения области поиска или, например, организации определенной политики доступа к документам, и т.д. Мы не будем касаться таких поисковых машин, а будем рассматривать полнотекстовый поиск, который полностью интегрирован с СУБД. Очевидно, что подобный поиск обязан соответствовать архитектуре СУБД, что налагает определенные ограничения на алгоритмы и методы доступа к данным. Несмотря на то, что подобные ограничения могут влиять на производительность поиска, полный доступ ко всем метаданным базы данных дает возможность для реализации очень сложных поисков, просто невозможных для внешних поисковиков. Например, понятие документа в БД отличается от обычного восприятия как страница на сайте, которую можно сохранить, открыть, модифицировать, удалить. То, что пользователь или поисковый робот видит на сайте является результатом лишь одной комбинацией метаданных, полное множество которых практически недоступно для поисковых роботов. Существует даже понятие "скрытого веба" (Hidden Web), недоступного для поисковых машин и который во много раз превышает размеры видимого веба. Одним из компонентов этой "скрытой" части веба является содержимое баз данных. Что такое документ в базе данных ? Это может быть произвольный текстовый атрибут или их комбинация. Атрибуты могут храниться в разных таблицах и тогда документ может являться результатом сложной "связки" нескольких таблиц. Более того, текстовые атрибуты могут быть на самом деле результатом работы программ-конвертеров, которые вытаскивают текстовую информацию из бинарных полей (.doc, .pdf, .ps, ...). В большинстве случаев, документ является результатом работы SQL команд и виртуальным по своей природе. Очевидно, что единственное требование для документа является наличие уникального ключа, по которому его можно идентифицировать. Для внешнего поисковика такой документ является просто набором слов ("bag of words"), без никакого понимания структуры, т.е. из каких атрибутов этот документ был составлен, какова важность того или иного документа. Вот пример документа, составленного из нескольких текстовых атрибутов. SELECT m.title || m.author || m.abstract || d.body as document FROM messages m, docs d WHERE m.id = d.id and m.id = 12;Интуитивно ясно, что не все части документа одинаково важны. Так, например, заголовок или абстракт обладают большей информативной плотностью, чем остальная часть документа.
Запрос имеет чисто иллюстративный характер, так как
на самом деле, здесь надо было бы использовать функцию Как и обычный документ он состоит из слов, по которым его можно найти. Для этого документ надо уметь разбивать на эти слова, что также может быть не простой задачей, так как для разных задач понятие слова может быть разным. Мы используем термин "токен" для обозначения "слов", которые получаются после работы парсера, и термин "лексема" для обозначения того, что будет индексировано. Итак, парсер разбивает документ на токены, часть из которых индексируется. Каким образом токен становится лексемой - это определяется конкретной задачей, например, для поиска по цветам требуется индексировать не только обычные слова, обозначающие цвета красок, но и их различные эквиваленты, использующиеся в веб-технологиях, например, их шестнадцатеричные обозначения. Полнотекстовый поиск в PostgreSQLКак и многие современные СУБД, PostgreSQL [PGSQL] имеет встроенный механизм полнотекстового поиска. Отметим, что операторы поиска по текстовым данных существовали очень давно, это операторыLIKE, ILIKE, ~, ~*.
Однако, они не годились для эффективного полнотекстового поиска, так как
Идея нового поиска состояла в том, чтобы затратить время на обработку
документа один раз и сохранить время при поиске, использовать специальные
программы-словари для нормализации слов, чтобы не заботиться,
например, о формах слов, учитывать информацию о важности различных атрибутов
документа и положения слова из запроса в документе для ранжирования
найденных документов. Для этого, требовалось создать новые типы данных,
соответствующие документу и запросу, и полнотекстовый оператор для
сравнения документа и запроса, который возвращает PostgreSQL предоставляет возможность как для создания новых типов данных, операторов, так и создания индексной поддержки для доступа к ним, причем с поддержкой конкурентности и восстановления после сбоев! Однако, надо понимать, что индексы нужны только для ускорения поиска, сам поиск обязан работать и без них.
Таким образом, были созданы новые типы данных - =# select 'cat & rat':: tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector; ?column? ---------- t =# select 'fat & cow':: tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector; ?column? ---------- fКроме этого, были реализованы вспомогательные функции
=# select "Alias","Token","Description" from ts_debug('12 cats');
Alias | Token | Description
-------+-------+------------------
uint | 12 | Unsigned integer
blank | | Space symbols
lword | cats | Latin word
Каждому типу токена ставится в соответствие
набор словарей, которые будут стараться распознать и "нормализовать" его.
Порядок словарей фиксирован и важен, так как именно в этом порядке токен
будет попадать на вход словарю, до тех пор, пока он не опознается одним из
них.
Если токен не распознался ни одним из словарей, или словарь опознал его
как стоп-слово, то этот токен не индексируется.
Таким образом, можно сказать, что для каждого типа токена существует
правило обработки токена, которое описывает схему попадания токена в
полнотекстовый индекс.
=# select "Alias","Token","Dicts list","Lexized token" from ts_debug('as 12
cats');
Alias | Token | Dicts list | Lexized token
-------+-------+----------------------+---------------------------
lword | as | {pg_catalog.en_stem} | pg_catalog.en_stem: {}
blank | | |
uint | 12 | {pg_catalog.simple} | pg_catalog.simple: {12}
blank | | |
lword | cats | {pg_catalog.en_stem} | pg_catalog.en_stem: {cat}
На этом примере мы видим, что токен 'as' обработался словарем
pg_catalog.en_stem, распознался как стоп-слово и не попал
в полнотекстовый индекс, в то время как токены '12' и 'cats' распознались
словарями, нормализовались и попали в индекс.
Каждый словарь по-своему понимает, что такое "нормализация", однако, интуитивно понятно, что в результате нормализации, группы слов, объединенные по тому или иному признаку, приводятся к одному слову. Это позволяет при поиске этого "нормализованного" слова найти все документы, содержащие слова из этой группы. Наиболее привычная нормализация для нас - это приведение существительного к единственному числу и именительному падежу, например, слово 'стол' является нормальной формой слов 'столы', 'столов', 'столами', 'столу' и т.д. Не менее естественным представляется приведение имен директорий '/usr/local/bin', '/usr/local/share/../bin', '/usr/local/./bin/' к к стандартному виду '/usr/local/bin'.
Комбинация парсера и правил обработки токенов определяет
полнотекстовую конфигурацию, которых
может быть произвольное количество.
Большое количество конфигураций
для 10 европейских языков и разных локалей уже встроено в PostgreSQL и
хранится в системном каталоге, в схеме Сами парсеры и словари также хранятся в системе, их можно добавлять, изменять и удалять с помощью SQL команд.
Несмотря на богатые возможности по настраиванию полнотекстового поиска
практически под любую задачу, возможности, предоставленные по умолчанию,
вполне достаточны для организации полноценного поиска для широкого класса
задач. Более того, для очень простого поиска, когда не требуется ранжирования
документов, например, поиск по заголовкам новостей, когда есть
естественный способ
сортировки документов по времени, можно организовать с помощью всего
одной команды. Для примера мы будем использовать таблицу
=# \d apod
Table "public.apod"
Column | Type | Modifiers
----------+----------+-----------
id | integer | not null
title | text |
body | text |
sdate | date |
keywords | text |
Indexes:
"apod_pkey" PRIMARY KEY, btree (id)
В этой таблице sdate - это дата документа, а атрибут
keywords - строка с ключевыми словами через запятую, которые
вручную редактор перевода присвоил документу. Создадим индекс по заголовкам:
CREATE INDEX tit_idx ON apod USING gin(title);После этого уже можно искать SELECT title FROM apod WHERE title @@ 'supernovae stars' ORDER by sdate limit 10;Чтобы понять, что на самом деле происходит при создании индекса, опишем все шаги.
Полнофункциональный поиск требует создания нового атрибута для хранения
=# UPDATE apod SET fts= setweight( coalesce( to_tsvector(keywords),''),'A')|| setweight( coalesce( to_tsvector(title),''),'B') || setweight( coalesce( to_tsvector(body),''),'D');В этом примере мы добавили атрибут fts, который представляет собой
конкатенацию
текстовых полей keywords, title и body.
При этом, с помощью функции setweight мы приписали разные веса
лексемам из разных частей. Заметим, что мы приписали только "метки", не
численные значения, которые будут приписаны этим самым меткам в момент поиска.
Это позволяет настраивать поиск буквально налету, например, используя
один и тот же полнотекстовый индекс можно организовывать поиск только по
заголовкам и ключевым словам.
=# select * from apod where fts @@ to_tsquery('supernovae:ab');
На этом мы закончим введение в полнотекстовый поиск в PostgreSQL и приведем список основных возможностей.
Что надо знать о полнотекстовой конфигурации1) FTS конфигурация объединяет все необходимое для организации полнотекстового поиска, а именно:
2) FTS конфигураций может быть много, они могут быть определены в
разных схемах, но только одна в данной схеме может иметь флаг
=# \dF *.russ*utf8
List of fulltext configurations
Schema | Name | Locale | Default | Description
------------+--------------+-------------+---------+-----------------------------------------
pg_catalog | russian_utf8 | ru_RU.UTF-8 | Y | default configuration for Russian/UTF-8
public | russian_utf8 | ru_RU.UTF-8 | Y |
(2 rows)
В зависимости
от search_path мы будем иметь разную активную FTS конфигурацию.
=# show tsearch_conf_name;
tsearch_conf_name
-------------------------
pg_catalog.russian_utf8
(1 row)
=# set search_path=public, pg_catalog;
SET
=# show tsearch_conf_name;
tsearch_conf_name
---------------------
public.russian_utf8
Таким образом, чтобы не возникали разного рода конфузы мы рекомендуем:
3) FTS конфигурация как любой обычный объект базы данных имеет владельца, ее можно удалять, создавать, изменять только при наличии соответствующих прав. 4) Как правило, для успешного поиска требуется следить, чтобы использовалась одна и та же FTS конфигурация при индексировании и при поиске. * Прим. ред. На момент публикации статьи версия PostgreSQL 8.3 еще не вышла, но полнотекстовый поиск будет организован в ней именно так, как здесь описано - соответствующий фрагмент уже принят разработчиками. |
|
CITForum © 1997–2025