| Писание же твое приято бысть и разумлено внятельно. | |
| Первое послание Ивана Грозного Курбскому |
Встроенная в СУБД Oracle текстовая поисковая машина Oracle Text способна не только выполнять полнотекстовый поиск в документах и по кратким описаниям, но и делать проверку на соответствие заданным по желанию условиям. Для этого применяется разновидность CTXSYS.CTXRULE текстового индекса и оператор MATCHES. В статье показаны примеры их употребления. Статью удобно рассматривать как продолжение статей «Oracle: работать с текстовыми документами очень просто», «Текстовые документы в Oracle: разнообразие источников, форматов, запросов» и «Как работать с картотекой (набором данных с краткими описаниями)?».
Некоторые основные возможности использования индекса типа CTXSYS.CTXRULE будут рассмотрены ниже на двух примерах: простом и более реалистичном.
CREATE TABLE rules ( id NUMBER PRIMARY KEY, query VARCHAR2 ( 1000 ) ); INSERT INTO rules VALUES ( 1, 'lamb' ); INSERT INTO rules VALUES ( 2, 'little OR lamb' ); INSERT INTO rules VALUES ( 3, 'lamb NOT mary' ); INSERT INTO rules VALUES ( 4, 'little lamb' ); INSERT INTO rules VALUES ( 5, 'lamb little' ); INSERT INTO rules VALUES ( 6, 'lamb NEAR mary' );
Строим индекс и готовим сценарный файл для опытов:
CREATE INDEX rules_idx ON rules ( query ) INDEXTYPE IS CTXSYS.CTXRULE; COLUMN category FORMAT 99999 COLUMN query FORMAT A50 SELECT id category, query FROM rules WHERE MATCHES ( query, '&1' ) > 0 . SAVE matches REPLACE SET VERIFY OFF
Проверяем соответствие трех «документов» шести заведенным «классификационным признакам»:
CTX> @matches 'Mary had a little lamb'
CATEGORY QUERY
-------- --------------------------------------------------
1 lamb
6 lamb NEAR mary
2 little OR lamb
4 little lamb
CTX> @matches 'Twinkle, twinkle little star'
CATEGORY QUERY
-------- --------------------------------------------------
2 little OR lamb
CTX> @matches 'This Lamb is my lamb'
CATEGORY QUERY
-------- --------------------------------------------------
1 lamb
2 little OR lamb
3 lamb NOT mary
Дальнейшие опыты рекомендуется провести самостоятельно. При необходимости следует воспользоваться известной из предыдущего материала процедурой CTX_DDL.SYNC_INDEX перестройки индекса. Также рекомендуется удостовериться, что в плане обработки наших запросов стоит обращение к индексу RULES_IDX (имеющийся там шаг обращения к таблице RULES вызван нашим желанием выдать значение поля ID строки из этой таблицы; если этого не сделать, обращение к таблице RULES пропадет).
OBJECT_NAME OBJECT_TYPE ------------------------------ ----------------------- DR$RULES_IDX$I TABLE DR$RULES_IDX$K TABLE DR$RULES_IDX$N TABLE DR$RULES_IDX$R TABLE RULES TABLE DR$RULES_IDX$X INDEX RULES_IDX INDEX SYS_IOT_TOP_53386 INDEX SYS_IOT_TOP_53391 INDEX SYS_LOB0000053383C00006$$ LOB SYS_LOB0000053388C00002$$ LOB
А вот примерно какие появятся структуры хранения:
SEGMENT_NAME SEGMENT_TYPE ------------------------------ ----------------------------- DR$RULES_IDX$X INDEX SYS_C005906 INDEX SYS_IOT_TOP_53386 INDEX SYS_IOT_TOP_53391 INDEX SYS_IL0000053383C00006$$ LOBINDEX SYS_IL0000053388C00002$$ LOBINDEX SYS_LOB0000053383C00006$$ LOBSEGMENT SYS_LOB0000053388C00002$$ LOBSEGMENT DR$RULES_IDX$I TABLE DR$RULES_IDX$R TABLE RULES TABLE
Очевидно, техническая организация индекса типа CTXSYS.CTXRULE почти та же, что и для типа CTXSYS.CONTEXT, то есть это четыре таблицы и необходимые для них служебные структуры. (Почти - потому что в таблице DR$RULES_IDX$I в нашем случае появилось дополнительное поле TOKEN_EXTRA. Дальнейшее изучение предлагается предпринять самостоятельно).
В наборах из нескольких запросов-«правил», подобно нашему случаю, это приводит к чудовищному перерасходу дисковой памяти; очевидно, сама возможность рассчитана на большие наборы.
В статье «Как работать с картотекой (набором данных с краткими описаниями) ?» рассматривалось заведение в БД и индексирование «картотеки» с краткими описаниями новостей, полученных из канала RSS в интернете для Oracle Technology Network, и со ссылками на источник. Возьмем одну такую ссылку:
CTX> COLUMN link FORMAT A75 CTX> SELECT link FROM otnnews WHERE ROWNUM = 1; LINK --------------------------------------------------------------------------- http://www.oracle.com/technology/pub/articles/hunter_rac10gr2_iscsi.html?rs sid=rss_otn_news
Вот начало документа HTML, расположенного по этой ссылке:
Проделаем следующее:
Если обращение в интернет будет осуществляться через приближенный (proxy) сервер, требуется предварительно выдать что-то вроде:
EXECUTE UTL_HTTP.SET_PROXY ('http://имя:пароль@адрес:порт')
Для извлечения документа из интернета мне очень хотелось бы воспользоваться типом HTTPURITYPE, но проверка показывает, что этот тип не способен посылать запросы HTTP методом POST, а для нашей страницы, как опять-таки показывает проверка, требуется именно это. Притом, страница динамическая: обратите внимание на завершение адреса текстом ?rssid=rss_otn_news. (Несложная проверка параметров ответа при обращении по этому адресу показывает, что получателем запроса является сервлет на Oracle Application Server). Поэтому придется прибегнуть к пакету UTL_HTTP и программированию.
Для удобства занесем основную часть адреса и параметры в переменные SQL*Plus:
VARIABLE url VARCHAR2 ( 1000 ) VARIABLE parameters VARCHAR2 ( 1000 ) VARIABLE length VARCHAR2 ( 1000 ) EXECUTE :url := - 'http://www.oracle.com/technology/pub/articles/hunter_rac10gr2_iscsi.html' EXECUTE :parameters := 'rssid=rss_otn_news' EXECUTE :length := '18'
Для простоты количество символов в подстроке параметров (18) я посчитал вручную.
Заведем переменную SQL*Plus для хранения документа:
VARIABLE htmlclob CLOB EXECUTE :htmlclob := EMPTY_CLOB ( )
При работе учтите, что значение переменной SQL*Plus типа CLOB потеряется, как только вы завершите сеанс связи с СУБД, например в результате выдачи CONNECT. (В действительности, переменная HTMLCLOB есть переменная-«локатор», указывающий на временный LOB-объект со временем жизни сеанса; этот-то LOB-объект и пропадает без нашей воли по завершению сеанса, после чего локатор начнет указывать «в никуда»).
Следующий блок на PL/SQL прочтет по нужному адресу в интернете документ и разместит его в переменной SQL*Plus:
DECLARE req UTL_HTTP.REQ; resp UTL_HTTP.RESP; name VARCHAR2 ( 256 ); value VARCHAR2 ( 4000 ); BEGIN req := UTL_HTTP.BEGIN_REQUEST ( :url, method => 'POST' ); UTL_HTTP.SET_HEADER ( r => req, name => 'Content-Type', value => 'text/html' ); UTL_HTTP.SET_HEADER ( r => req, name => 'Content-Length', value => :length ); UTL_HTTP.WRITE_LINE ( r => req, data => :parameters ); resp := UTL_HTTP.GET_RESPONSE ( req ); DBMS_OUTPUT.PUT_LINE ( 'HTTP response status code: ' || resp.status_code ); DBMS_OUTPUT.PUT_LINE ( 'HTTP response reason: ' || resp.reason_phrase ); LOOP UTL_HTTP.READ_LINE ( resp, value, TRUE ); :htmlclob := :htmlclob || value; END LOOP; EXCEPTION WHEN UTL_HTTP.END_OF_BODY THEN UTL_HTTP.END_RESPONSE ( resp ); END; /
Выдача на экран добавлена для контроля. Проверить, что документ действительно прочитался, можно, например, так:
CTX> EXECUTE DBMS_OUTPUT.PUT_LINE ( DBMS_LOB.GETLENGTH ( :htmlclob ) ) 166426 PL/SQL procedure successfully completed.
При желании можно было пометить документ в таблицу, но здесь довольно и оставить его в переменной SQL*Plus.
Создадим таблицу классификации, заполним ее правилами и построим индекс:
CREATE TABLE category ( id NUMBER, query VARCHAR2 ( 2000 ) ); INSERT INTO category VALUES ( 1, 'rac | "real application clusters"' ); INSERT INTO category VALUES ( 2, 'linux | unix' ); INSERT INTO category VALUES ( 3, 'installation | configuration' ); INSERT INTO category VALUES ( 4, 'ms windows OR microsoft NEAR windows' ); INSERT INTO category VALUES ( 5, 'standby AND switchover' ); CREATE INDEX category_idx ON category ( query ) INDEXTYPE IS CTXSYS.CTXRULE;
Проверка:
CTX> COLUMN query FORMAT A60
CTX> SELECT id, query FROM category WHERE MATCHES ( query, :htmlclob ) > 0;
ID QUERY
---------- ----------------------------------------
1 rac | "real application clusters"
2 linux | unix
3 installation | configuration
4 ms windows OR microsoft NEAR windows
В данном случае документ удовлетворяет четырем категориям имеющейся классификации под номерами 1 - 4 и не удовлетворяет категории под номером 5.
Реальность нередко оказывается сложнее, чем хотелось бы. Вот примеры.
Интерес привлекает фрагмент в конце страницы, который я обвел эллипсом. Судя по всему, наш документ не исчерпывается одной страницей HTML и состоит на деле из трех страниц! Фактический переход по ссылкам Page 2 и Page 3 подтверждает эту догадку. Это - одна из сложностей, которая может нас подстерегать при организации автоматического классифицирования.
Технически проблему документа, логически единого, но разнесенного по нескольким страницам, можно учесть дополнительным программированием, включающим выявление подобного рода продолжений, но при этом мы вынуждены будем учитывать особенности вполне конкретного канала новостей RSS, так как в другом канале ссылки на продолжение документа могут оказаться организованы совсем иначе. Это лишит создаваемую программу автоматического классифицирования общности.
Если документ представлен файлом формата PDF, RTF, простого текста или иным, проблема попадания в поле зрения не относящихся к документу слов не возникает.
Выполнять полнотекстовый поиск, а также классификацию, можно к тому же применительно к документам, находящимся не только в интернете, но и в БД, и в файловой системе.
В то же время затронутые возможности являются базовыми, начальными, и не исчерпывают содержание Oracle Text. Например, за рамками рассмотрения оказались средства «интеллектуального анализа» документов, которые по аналогии с Data Mining получили название Text Mining. Описывать эти средства мимоходом нереально.
| Возможно более уместно здесь было бы привести для слова rule другой, более специфичный перевод "направляющая". |