В чем недостатки полнотекстового поиска (full text search), применяемого в современных СУБД? Вы не можете указать сколько слов до и после найденных надо вывести, какими тегами обрамлять найденные слова, в т.ч. в зависимости от того, в какой части строки они найдены, какие и сколько вариантов перестановок найденных слов выводить. Вы имеете желание написать произвольный FTS-запрос, но не имеете возможности. Попытаемся устранить это ограничение.
Пусть у нас есть таблица 's' с полями 'pk' (в ней первичный ключ), 's1' и 's2', единственная запись которой содержит
1, 10, "In the morning, dog comes, cat comes home too. Continue in the NEXT issue."
Вообразим, что строка разбита на слова, а слова хранятся во вложенной (nested) таблице с полями
|
Вложенная колонка, равно как и результат функции, хотя бы одним из аргументов которой является вложенное колонка, ведет себя
Операции с вложенной колонкой обладают следующими свойствами
Даже в одной строке может быть найдено несколько образцов, а значит даже одна запись может породить несколько: будем называть этот процесс размножением (propagation), а записи, порожденные из одной - порожденной группой (propagated group). Поэтому всегда в результирующий набор автоматически добавляется фиктивное целочисленное поле SYS_CLUE, которое содержит разные значения для записей одной порожденной группы [4]. Например следующий запрос, запрашивающий слова из определенного множества [5] и выводящий их и по одному слову слева и справа от них
SELECT s1, s2.@TOKEN FROM s WHERE s2.@SN in ( SELECT DISTINCT s2.@SN FROM s, ( SELECT s2.@SN as fn FROM s WHERE s2.@TOKEN in "comes next" ) WHERE abs(s2.@SN-fn) <= 1 );находит два образца и возвращает две записи
|
Если
Во всех случаях гарантируется, что повторный полнотекстовый поиск в той же записи или результатах другого полнотекстового поиска даст порожденные записи с теми же значениями поля SYS_CLUE.
Чтобы проводить разные операции (обрамлять разными тегами) с разными словами, достаточно разрешить давать алиасы аргументам функций, в частности - функции конкатенации. Тогда, например, обрамление слов из определенного множества тегами <b> и </b>, по одному слову слева и справа от них тегами <em> и </em>, и возвращение всех остальных слов между ними без обрамления выглядит так
SELECT s1, ("<b>" ||s2.@TOKEN as f1 ||"</b>" ) || ("<em>"||s2.@TOKEN as f2 ||"</em>") || ( s2.@TOKEN as f3 ) FROM s WHERE f1 IN "comes next" AND f2 IN ( SELECT DISTINCT ON(s2.@token, s2.@SN) s2.@token FROM s, ( SELECT s2.@SN as fn FROM s WHERE s2.@TOKEN in "comes next" ) WHERE abs(s2.@SN-fn)=1 ) AND f3 between SELECT MIN(s2.@SN) FROM s WHERE s2.@TOKEN in "comes next" AND SELECT MAX(s2.@SN) FROM s WHERE s2.@TOKEN in "comes next" AND NOT IN ( SELECT DISTINCT ON(s2.@token, s2.@SN) s2.@token FROM s, ( SELECT s2.@SN as fn FROM s WHERE s2.@TOKEN in "comes next" ) WHERE abs(s2.@SN-fn)=1 );И возвращает следующий результат
|
В результате индексации добавляются под-поля
Все грамматические формы одного слова могут рассматриваться как одна лексема. Тогда добавляется под-поле
Справочник грамматических форм может быть не загружен, или не содержать некоторых слов или их форм. Тогда индексированный поиск по всем словам (или их формам) невозможнен - только по проиндексированным. Поэтому как только построен индекс для текстового поля
|
|
CREATE INDEX i1 ON tokens( idtoken ); CREATE INDEX i2 ON tokens( token ); CREATE INDEX i3 ON tokens( idlexeme ); CREATE INDEX i4 ON items( idfield, pk, idtoken ); CREATE INDEX i5 ON items( idfield, pk, sn );Все эти индексы должны быть автоматически удалены при удалении любой из таблиц 'delimiters', 'tokens', 'items' (без 'delimiters' и 'tokens' невозможно построение второй таблицы, подобной 'items', по шаблону для сравнения - в нашем случае по константе "come next").
Чтобы можно было индексировать, не создавая справочника лексем, введем команду (отдельную от команды заполнения таблицы 'items')
TOKENIZE s(s2) INTO tokens DELIMITING delimiters [, delimiters2];которая оставит поле 'idlexeme' незаполненным. А для загрузки справочника лексем будем использовать команду заполнения таблицы из файла (поле 'idtoken' будет заполнено из его собственного sequence)
COPY tokens( idlexeme, token ) FROM c:/lexeme.txtРазложение поля 's2' всех записей будем производить командой
ITEMIZE s(s2) INTO items DELIMITING delimiters [, delimiters2] TOKENIZING tokens;Операции '=', IN и другие, работая с текстовыми полями и с 's2' в частности, используют индексы, построенные не для 's2', а для таблиц, указанных в параметре NOMENCLARURE [10], и тех, на которые таблицы из NOMENCLARURE ссылаются выше упомянутым внешним ключем
SET NOMENCLARURE items [, items2];
[1] Т.е. 'ORDER BY s2.@SN' писать не надо
[2] Вывод поля s2.@SN возвращает строку, состоящую из порядковых номеров найденных слов, а не из самих слов; поля s2.@BEGINNING - из смещений первых букв слов, s2.@END - из смещений последних букв слов
[3] К началу и/или концу найденной строки добавляется символы, указанные в OMITTED_FIRST и OMITTED_LAST, если для ее получения в исходной строке пришлось отбросить начальные/конечные слова
[4] "Всегда" - значит даже если все порожденные группы состоят из одной записи, а поле SYS_CLUE не упомянуто в запросе. Поле SYS_CLUE может содержать одинаковые значения в разных группах. Значение этого поля требуется клиентской программе, чтобы сообщить серверу, какой конкретный образец группы выбрал пользователь. Если нет первичного ключа, различить группы невозможно
[5] Можно указать перестановку слов из определенного множества (подробнее о перестановке '=~' на с.183-186 pdf-документа)
WHERE s2.@TOKEN =~ "come next"в т.ч. с ограничением количества перестановок (результаты всегда выдаются, начиная с наименьшего количества перестановок, в направлении возрастания количества)
WHERE s2.@TOKEN TO "come next" PERMUTATIONS <=2
[6] Поле SYS_CLUE может содержать одинаковые значения в декартовых произведениях разных пар групп
[7] Мы можем использовать квантор ALL перед названием под-поля, чтобы принудить к неиндексированному поиску по всем словам
SELECT s1, ALL s2.@TOKEN FROM s;
CREATE SEQUENCE delimiters_seq; CREATE TABLE delimiters ( iddelimiter integer DEFAULT nextval('delimiters_seq'), delimiter string );
[9] 'idfield' - уникальный системный идентификатор самого поля 's2'. Заполняется командой ITEMIZE, чтобы командой 'SELECT ... FROM items' можно было искать сразу во многих полях многих таблиц
[10] Параметр NOMENCLARURE является сессионным
P.S.
Статья разъясняет с.191-197 pdf-документа.
Тюрин Дмитрий, dmitryturin@yandex.ru