Когда мы набираем в Google или Yandex, к примеру, "Чань-буддизм иерархия язык", то вместе со ссылками получаем некоторый комментарий, например, "Иерархия деструктивных средств чань-буддийского языка", в комментарии сервер выделяет слова, которые нашел. Конечно, сервер не ищет в интернете - он ищет в своей базе данных. Но запросы в базу с выделением найденного нужны не только ему, но и нам, обычным пользователям. Роль "случайных" пользователей СУБД с каждым днем все возрастает, фактически мы составляем уже большинство. А простому смертному реализовать такое крайне сложно. В коммерческих СУБД не воплощают, а в бесплатных несут такую околесицу, что с нормальными когнитивными способностями и применить невозможно, см. например неудачную попытку в Postgres. Воистину очень печально отсутствие в каких-либо намерений по поддержке работы не-программистов. Конечно, совсем нелегко найти простой и элегантный синтаксис для того, чтобы указать сколько слов до и после найденных надо вывести, какими тегами обрамлять найденные слова, в т.ч. в зависимости от того, в какой части строки они найдены, какие и сколько вариантов перестановок найденных слов выводить. Однако автор хотел бы предпринять такую попытку и услышать комментарии общественности, насколько ему это удалось. Несколько слайдов, иллюстрирующих идеи, вы можете видеть в sql5.19.3.pdf на с.188-194. Предложения дополняют статью "Систематика и обобщение методологий моделирования и языков программирования"
Пусть у нас есть таблица 's' с полями 'pk' (в ней первичный ключ), 's1' и 's2', единственная запись которой содержит
1, 10, "In the morning, dog comes, cat comes home too. Continue in the NEXT issue."
Вообразим, что строка разбита на слова, а слова хранятся в некоторой таблице с колонками @TOKEN (само слово),@SN (порядковый номер слова в поле), @BEGINNING (смещение первой буквы слова), @END (смещение последней буквы слова).@TOKEN | @SN | @BEGINNING | @END |
---|---|---|---|
In | 1 | 1 | 2 |
the | 2 | 4 | 6 |
morning | 3 | 8 | 14 |
dog | 4 | 17 | 19 |
comes | 5 | 21 | 24 |
cat | 6 | 27 | 29 |
comes | 7 | 31 | 34 |
home | 8 | 36 | 39 |
too | 9 | 41 | 43 |
Continue | 10 | 46 | 53 |
in | 11 | 55 | 56 |
the | 12 | 58 | 60 |
NEXT | 13 | 62 | 65 |
issue | 14 | 67 | 71 |
Даже в одной строке может быть найдено несколько образцов, а значит каждая запись может быть возвращена несколько раз: будем называть этот процесс размножением (propagation), а записи, порожденные из одной - порожденной группой (propagated group). Чтобы в клиентской программе выбрать конкретный экземпляр группы, СУБД автоматически добавляет целочисленное поле SYS_CLUE, содержащее уникальный идентификатор записи внутри группы. В разных группах поле может содержать одинаковые значения, неоднозначность разрешает первичный ключ записей. Например, запрос слов из множества "comes next", который выводит найденные, а также и по одному слову слева и справа от них
SELECT s1, s2.@TOKEN FROM s WHERE s2.@SN IN ( SELECT s2.@SN FROM s, ( SELECT s2.@SN AS fn FROM s WHERE s2.@TOKEN IN "comes next" ) WHERE ABS(s2.@SN-fn) <= 1 );находит два образца и возвращает две записи (для строки "comes next" автоматически генерируется её вложенная таблица как только строка введена в СУБД, эту вложенную таблицу и использует оператор IN)
s1 | s2 | SYS_CLUE |
---|---|---|
10 | dog comes, cat ... the NEXT issue | 1 |
10 | cat comes home ... the NEXT issue | 2 |
Можно указать перестановку слов с помощью 'WHERE s2.@TOKEN =~ "come next" ' (подробнее о перестановке на с.180-183 вышеупомянутого pdf-документа), в т.ч. с ограничением количества перестановок 'WHERE s2.@TOKEN TO "come next" PERMUTATIONS <=2', результаты всегда выдаются, начиная с наименьшего количества перестановок, в направлении возрастания количества.
И опять же для полноты картины определимся, что комбинация полнотекстовых поисков обладает следующими свойствами
Чтобы в момент вывода слов во внешний мир проводить с ними различные операции, в т.ч. чтобы обрамлять разными тегами, достаточно давать алиасы аргументам функций. Тогда например, запрос, возвращающий найденные слова обрамленными тегами <b> и </b>, по одному слову слева и справа от них - тегами <em> и </em>, а все остальные слова между найденными без обрамления, выглядит как показано ниже. Чтобы читателю легче было визуально сгруппировать @TOKEN, относящиеся к разным подзапросам, автор выделил некоторые из них строчными буквами; а также для удобства чтения перенес DISTINCT от нескольких аргументов под WHERE - таков уж был убогий синтаксис современных СУБД. DISTINCT нужен ввиду декартового произведения колонок '@sn' (соответствующих @token) и 'fn'.
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 s2.@token FROM s, ( SELECT s2.@SN AS fn FROM s WHERE s2.@TOKEN IN "comes next" ) WHERE ABS(s2.@sn-fn)=1 AND DISTINCT(s2.@token, s2.@sn) ) 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 f3 NOT IN ( -- not in <em>, i.e. not f2 SELECT s2.@token FROM s, ( SELECT s2.@SN AS fn FROM s WHERE s2.@TOKEN IN "comes next" ) WHERE ABS(s2.@sn-fn)=1 AND DISTINCT(s2.@token, s2.@sn) );Этот запрос возвращает следующий результат
s1 | s2 | SYS_CLUE |
---|---|---|
10 | <em>dog</em> <b>comes</b>, <em>cat</em> comes home too. Continue in <em>the</em> <b>NEXT</b> <em>issue</em> | 1 |
10 | <em>cat</em> <b>comes</b> <em>home</em> too. Continue in <em>the</em> <b>NEXT</b> <em>issue</em> | 2 |
Для индексации нужна прежде всего некоторая таблица 'delimiters' из двух полей, первое содержит первичный ключ, второе - знаки-разделители: пробел, табуляцию, возврат каретки, переход на новую строку, все знаки пунктуации. Кроме того, если нужно рассматривать все грамматические формы одного слова как одну лексему, то нужна таблица, группирующая грамматические формы. Например, как это сделано в таблице 'tokens'. В её первые два поля удобно заносить командой 'TOKENIZE s(s2) INTO tokens DELIMITING delimiters [, delimiters2]', а во второе и третье - стандартной командой заполнения таблицы из файла 'COPY tokens( idlexeme, token ) FROM c:/lexeme.txt'.
idtoken | token | idlexeme |
---|---|---|
1 | in | 1 |
2 | the | 2 |
3 | morning | 3 |
4 | dog | 4 |
5 | comes | 5 |
12 | come | 5 |
6 | cat | 6 |
7 | home | 7 |
8 | too | 8 |
9 | continue | 9 |
10 | next | 10 |
11 | issue | 11 |
idfield | pk | idtoken | own name | abbreviation | sn | beginning | end |
---|---|---|---|---|---|---|---|
505 | 1 | 1 | yes | 1 | 1 | 2 | |
505 | 1 | 1 | 11 | 55 | 56 | ||
505 | 1 | 2 | 2 | 4 | 6 | ||
505 | 1 | 2 | 12 | 58 | 60 | ||
505 | 1 | 3 | 3 | 8 | 14 | ||
505 | 1 | 4 | 4 | 17 | 19 | ||
505 | 1 | 5 | 5 | 21 | 24 | ||
505 | 1 | 5 | 7 | 31 | 34 | ||
505 | 1 | 6 | 6 | 27 | 29 | ||
505 | 1 | 7 | 8 | 36 | 39 | ||
505 | 1 | 8 | 9 | 41 | 43 | ||
505 | 1 | 9 | yes | 10 | 46 | 53 | |
505 | 1 | 10 | yes | 13 | 62 | 65 | |
505 | 1 | 11 | 14 | 67 | 71 |
Если 'tokens' не содержит некоторых слов или их форм, то сам факт наличия индексов переключает поиск по всем словам или формам на поиск только по проиндексированным. Т.е. не только увеличивается скорость поиска, но и может сужаться диапазон слов, по которым поиск производится. Чтобы одновременно по оставшимся словам произвести не-индексированный поиск, будем указывать квантор ALL перед названием вложенной колонки 'SELECT s1, ALL s2.@TOKEN FROM s'. Команда TOKENIZE добавляет во вложенной таблице колонки 's2.@IDTOKEN', 's2.@IDFIELD', а команда ITEMIZE - колонку 's2.@IDLEXEME'. Этими тремя новыми колонками также можно пользоваться в запросах.
Дмитрий Тюрин (DmitryTurin.narod.ru):
dima.turin@centrum.cz
(все письма из домена .ru попадают в спам),
dima.turin@gmail.com