Сделать полнотекстовый поиск управляемым и удобным
04.06.2010
—
Новости Hi-Tech
|
Когда мы набираем в Google или Yandex, к примеру, "Чань-буддизм иерархия язык", то вместе со ссылками получаем некоторый комментарий, например, "Иерархия деструктивных средств чань-буддийского языка", в комментарии сервер выделяет слова, которые нашел. Конечно, сервер не ищет в Интернете - он ищет в своей базе данных. Но запросы в базу с выделением найденного нужны не только ему, но и нам, обычным пользователям. Роль "случайных" пользователей СУБД с каждым днем все возрастает, фактически мы составляем уже большинство. А простому смертному реализовать такое крайне сложно. В коммерческих СУБД не воплощают, а в бесплатных несут такую околесицу, что с нормальными когнитивными способностями и применить невозможно, см., например, неудачную попытку в Postgres. Воистину очень печально отсутствие каких-либо намерений по поддержке работы не-программистов. Конечно, совсем нелегко найти простой и элегантный синтаксис для того, чтобы указать, сколько слов до и после найденных надо вывести, какими тегами обрамлять найденные слова, в т.ч. в зависимости от того, в какой части строки они найдены, какие и сколько вариантов перестановок найденных слов выводить. Однако автор хотел бы предпринять такую попытку и услышать комментарии общественности, насколько ему это удалось. Несколько слайдов, иллюстрирующих идеи, вы можете видеть в
Пусть у нас есть таблица 's' с полями 'pk' (в ней первичный ключ), 's1' и 's2', единственная запись которой содержит: 1, 10, "In the morning, dog comes, cat comes home too. Continue in the NEXT issue." Вообразим, что строка разбита на слова, а слова хранятся в некоторой таблице с колонками @TOKEN (само слово), @SN (порядковый номер слова в поле), @BEGINNING (смещение первой буквы слова), @END (смещение последней буквы слова).
Пусть эта таблица вложена в текстовое поле 's2' (мы такое вообразим, т.е. это nested table), и мы синтаксически имеем доступ к этим колонкам как к s2.@TOKEN, s2.@SN, s2.@BEGINNING, s2.@END. Будем называть их вложенными колонками (nested columns). Упоминание их после WHERE позволяет гибко формулировать условия для полнотекстового поиска. Т.е. в подзапросах они ведут себя как колонки, но во внешний мир СУБД возвращает их как обычное поле под именем 's2', образованное агрегатом конкатенации, прошедшимся по вложенной колонке. Это позволяет автоматически видеть результаты поиска как текстовую строку. Например, вывод поля s2.@SN возвращает строку, состоящую из порядковых номеров найденных слов, а не из самих слов; поля s2.@BEGINNING - из смещений первых букв слов, s2.@END - из смещений последних букв слов. При конкатенации гарантируется, что:
Чтобы обрамить то, что пожелаем, тегами-константами, операции с вложенными колонками обладают следующими свойствами:
Для полноты картины отметим также вопрос, остающийся за пределами настоящей статьи, а именно - возможность изменять текстовые поля с помощью SQL, не прибегая в СУБД к громаде строковых функций, например:
Даже в одной строке может быть найдено несколько образцов, а значит каждая запись может быть возвращена несколько раз: будем называть этот процесс размножением (propagation), а записи, порожденные из одной - порожденной группой (propagated group). Чтобы в клиентской программе выбрать конкретный экземпляр группы, СУБД автоматически добавляет целочисленное поле SYS_CLUE, содержащее уникальный идентификатор записи внутри группы. В разных группах поле может содержать одинаковые значения, неоднозначность разрешает первичный ключ записей. Например, запрос слов из множества "comes next", который выводит найденные, а также и по одному слову слева и справа от них находит два образца и возвращает две записи (для строки "comes next" автоматически генерируется её вложенная таблица, как только строка введена в СУБД, эту вложенную таблицу и использует оператор IN) 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 );
Можно указать перестановку слов с помощью '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) ); Этот запрос возвращает следующий результат
Для индексации нужна прежде всего некоторая таблица 'delimiters' из двух полей, первое содержит первичный ключ, второе - знаки-разделители: пробел, табуляцию, возврат каретки, переход на новую строку, все знаки пунктуации. Кроме того, если нужно рассматривать все грамматические формы одного слова как одну лексему, то нужна таблица, группирующая грамматические формы. Например, как это сделано в таблице 'tokens'. В её первые два поля удобно заносить командой 'TOKENIZE s(s2) INTO tokens DELIMITING delimiters [, delimiters2]', а во второе и третье - стандартной командой заполнения таблицы из файла 'COPY tokens( idlexeme, token ) FROM c:/lexeme.txt'.
Далее, нам нужна некоторая таблица типа 'items', поле 'idtoken' которой ссылается внешним ключом на одноименное поле таблицы 'tokens', и в поля 'idfield', 'pk', 'sn', 'beginning', 'end' которой скопированы: идентификатор поля 's2', взятый из системных таблиц, описывающих схему базы данных; первичный ключ таблицы 's'; поля @SN, @BEGINNING, @END таблицы, вложенной в поле 's2'. Поля 'own name' и 'abbreviation' указывают, является ли слово именем собственным или аббревиатурой. Поле 'idfield' нужно, чтобы командой 'SELECT ... FROM items' искать сразу во многих таблицах базы данных. Разложение поля 's2' сразу всех записей будем производить командой 'ITEMIZE s(s2) INTO items DELIMITING delimiters [, delimiters2] TOKENIZING tokens'.
Теперь индексирование представляет собой стандартную команду CREATE INDEX для таблиц 'tokens' и 'items'. Все эти индексы автоматически удаляются при удалении любой из таблиц 'delimiters', 'tokens', 'items'. Может понадобиться отключать одни 'items' и подключать к полнотекстовому поиску другие 'items' - будем делать это с помощью 'SET NOMENCLARURE items [, items2]'. Параметр NOMENCLARURE является сессионным, т.е. два одновременных соединения одного и того же пользователя с СУБД могут иметь различные значения этого параметра. Если 'tokens' не содержит некоторых слов или их форм, то сам факт наличия индексов переключает поиск по всем словам или формам на поиск только по проиндексированным. Т.е. не только увеличивается скорость поиска, но и может сужаться диапазон слов, по которым поиск производится. Чтобы одновременно по оставшимся словам произвести не-индексированный поиск, будем указывать квантор ALL перед названием вложенной колонки 'SELECT s1, ALL s2.@TOKEN FROM s'. Команда TOKENIZE добавляет во вложенной таблице колонки 's2.@IDTOKEN', 's2.@IDFIELD', а команда ITEMIZE - колонку 's2.@IDLEXEME'. Этими тремя новыми колонками также можно пользоваться в запросах. Дмитрий ТЮРИН, Чтобы разместить новость на сайте или в блоге скопируйте код:
На вашем ресурсе это будет выглядеть так
Нелегко найти простой и элегантный синтаксис для того, чтобы указать, сколько слов до и после найденных надо вывести, какие и сколько вариантов перестановок...
|
|