WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 13 | 14 || 16 | 17 |   ...   | 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 15 ] --

Код, реализующий этот метод, может выглядеть так: scott@TKYTE816> REM Пользователь SCOTT должен иметь привилегию CREATE ANY ->CONTEXT scott@TKYTE816> REM или роль с такой привилегий, иначе код не сработает scott@TKYTE816> create or replace context bv_context using dyn_demo 2/ Context created. scott@TKYTE816> create or replace package body dyn_demo 2 as 3 4 procedure do_query(p_cnames in array, 5 p_operators in array, 6 p_values in array) 7 is 8 type rc is ref cursor;

9 10 l_query long;

11 l_sep varchar2(20) default ' where ';

12 l_cursor rc;

13 l_ename emp.ename%type;

14 l_empno emp.empno%type;

15 l_job emp.job%type;

16 begin 17 /* 18 * Это наш постоянный список выбора — мы всегда 19 * выбираем эти три столбца. Изменяются 20 * условия выбора. 21 */ 22 l_query := 'select ename, empno, job from emp';

23 24 for i in 1.. p_cnames.count loop 25 l_query := l_query || l_sep | | 26 p_cnames(i) || ' ' || 27 p_operators(i) || ' ' || 28 'sys_context(''BV_CONTEXT'',''' || 29 p_cnames(i) || ''')';

Динамический SQL 30 l_sep := ' and ';

31 dbms_session.set_context('bv_context', 32 p_cnames(i), 33 p_values(i));

34 end loop;

35 36 open l_cursor for l_query;

37 loop 38 fetch l_cursor into l_ename, l_empno, l_job;

39 exit when l_cursor%notfound;

40 dbms_output.put_line(l_ename M ', ' ||l_empno ||,',' || l_job);

41 end loop;

42 close l_cursor;

43 end;

44 45 end dyn_demo;

46 / Package body created. scott@TKYTE816> set serveroutput on scott@TKYTE816> begin 2 dyn_demo.do_query( dyn_demo.array('ename', 'job'), 3 dyn_demo.array('like', '='), 4 5 6 dyn_demo.arrayC%A%', end;

/ 'CLERK'));

ADAMS,7876,CLERK JAMES,7900,CLERK PL/SQL procedure successfully completed. Так что, с точки зрения использования связываемых переменных, все гораздо сложнее, чем в случае пакета DBMS_SQL, — необходим хитрый прием. После того, как вы поймете суть этого приема, вполне можно использовать встроенный динамический SQL вместо средств пакета DBMS_SQL, если только запрос выдает фиксированное количество результатов и используется контекст приложения. Чтобы эффективно решать с помощью встроенного динамического SQL подобного рода задачи, необходимо создать и использовать контекст приложения. В конечном итоге оказывается, что представленный выше пример с курсорными переменными при реализации с помощью встроенного динамического SQL работает быстрее. В случае простых запросов, когда временем обработки самого запроса можно пренебречь, встроенный динамический SQL обеспечивает скорость выборки данных почти вдвое выше, чем пакет DBMS_SQL.

Количество столбцов выходных данных на этапе компиляции не известно Здесь все понятно: если клиент, выбирающий и обрабатывающий данные, создается на PL/SQL, необходимо использовать пакет DBMS_SQL. Если клиент, выбирающий и обрабатывающий данные, — приложение на процедурном языке программирования, ис Глава пользующее интерфейсы ODBC, JDBC, OCI и т.п., необходимо использовать встроенный динамический SQL. Рассмотрим ситуацию, когда, получая запрос во время выполнения, мы не знаем, сколько столбцов входит в список выбора. Необходимо определить это в коде PL/SQL. Оказывается, встроенный динамический SQL использовать нельзя, поскольку придется включить в код оператор вида: FETCH курсор INTO переменная!, переменная2, переменная3,...;

но сделать этого нельзя, потому что до момента выполнения не известно, сколько переменных надо в него поместить. Это один из случаев, когда придется использовать средства пакета DBMS_SQL, поскольку он позволяет применять следующие конструкции: 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 while (dbms_sql.fetch_rows(l_theCursor) > 0) loop /* Строим длинную строку результатов, — это эффективнее, чей * вызывать DBMS_OUTPUT.PUT_LINE в цикле. */ l_cnt := l_cnt+l;

l_line := l_cnt;

/* Шаг 8 — получить и обработать данные столбцов. */ for i in 1.. l_colCnt loop dbms_sql.column_value(l_theCursor, i, l_columnValue);

l_line := l_line || ',' || l_columnValue;

end loop;

/* Теперь выдаем строку. */ dbms_output.put_line(l_line);

end loop;

Можно проходить по столбцам в цикле, как если бы они представляли собой массив. Представленная выше конструкция взята из следующего фрагмента кода: scott@TKYTE816> create or replace 2 procedure dump_query(p_query in varchar2) 3 is 4 l_columnValue varchar2(4000);

5 l_status integer;

6 l_colCnt number default 0;

7 l_cnt number default 0;

8 l_line long;

9 10 /* Мы будем использовать эту таблицу, чтобы узнать, 11 * сколько столбцов придется выбирать, чтобы определить их, 12 * а затем выбрать их значения. 13 */ 14 l_descTbl dbms_sql.desc_tab;

15 16 17 /* Шаг 1: открыть курсор. */ 18 l_theCursor integer default dbms_sql.open_cursor;

19 begin Динамический SQL 21 /* Шаг 2: проанализировать запрос, чтобы можно было получить -> описание его результатов. */ 22 dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);

23 24 /* Шаг З: получаем описание результатов запроса. */ 25 dbms_sql.describe_columns(l_theCursor, l_colCnt, l_descTbl);

26 27 /* Шаг 4 в этом примере не используется, потому что связывать -> ничего не нужно. 28 * Шаг 5: необходимо определить каждый столбец, сообщить серверу, 29 * что и куда мы будем выбирать. В данном случае все данные 30 * будут выбираться в одну локальную переменную типа -> varchar2(4ООО). 31 */ 32 for i in 1.. l_colCnt 33 loop 34 dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);

35 end loop;

36 37 /* Шаг 6: выполнить оператор. */ 38 l_status := dbms_sql.execute(l_theCursor);

39 40 /* Шаг 7: выбрать все строки. */ 41 while (dbms_sql.fetch_rows(l_theCursor) > 0) 42 loop 43 /* Строим длинную строку результатов — это эффективнее, чем 44 * вызывать DBMS_OUTPOT.PUT_LINE в цикле. 45 */ 46 l_cnt :- l_cnt+l;

47 l_line := l_cnt;

48 /* Шаг 8: получаем и обрабатываем данные столбцов. */ 49 for i in 1.. l_colCnt loop 50 dbms_sql.column_value(l_theCursor, i, l_columnValue);

51 l_line := l_line || ',' || l_columnValue;

52 end loop;

53 54 /* Теперь выдаем строку. */ 55 dbms_output.put_line(l_line);

56 end loop;

57 58 /* Step 9: закрываем курсор, чтобы освободить ресурсы. */ 59 dbms_sql.close_cursor(l_theCursor);

60 exception 61 when others then 62 dbms_sql.close_cursor(l_theCursor);

63 raise;

64 end dump_query;

65 / Procedure created. Из этого следует, что пакет DBMS_SQL позволяет с помощью процедуры DBMS_SQL.DESCRIBE_COLUMNS получить количество, имена и типы данных стол Глава бцов в запросе. В качестве примера ее использования рассмотрим обобщенную процедуру сброса результатов запроса в файл операционной системы. Она отличается от SQL-Unloader, рассмотренной в главе 9 при создании средств выгрузки данных. В данном примере данные сбрасываются в файл в виде записей фиксированной длины, в которых столбцы всегда начинаются с одной и той же позиции. Для этого анализируются результаты вызова DBMS_SQL.DESCRIBE_COLUMNS, в которых помимо количества выбираемых столбцов можно найти и их максимальный размер. Прежде чем рассмотреть пример полностью, давайте подробней разберемся с процедурой DESCRIBE_COLUMNS. После анализа запроса с помощью этой процедуры можно обратиться к серверу за информацией о том, что можно ожидать при выборке данных по этому запросу. Эта процедура создает массив записей с информацией об именах, типах данных, максимальном размере столбцов и т.п. Вот пример использования процедуры DESCRIBE_COLUMNS. Выдаются данные, возвращаемые ею для запроса;

благодаря этому можно узнать, какая информация доступна: scott@TKYTE816> create or replace 2 procedure desc_query(p_query in varchar2) 3 is 4 l_columnValue varchar2(4000);

5 l_status integer;

6 l_colCnt number default 0;

7 l_cnt number default 0;

8 l_line long;

9 10 /* На используем эту таблицу, чтобы узнать, какие данные w выбирает запрос 11 */ 12 l_descTbl dbms_sql.desc_tab;

13 14 15 /* Шаг 1: открыть курсор. */ 16 l_theCursor integer default dbms_sql.open_cursor;

17 begin 18 19 /* Шаг 2: проанализировать входной запрос, чтобы можно было w описать его результаты. */ 20 dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);

21 22 /* Шаг З: описываем результаты запроса. 23 * Переменная L_COLCNT будет содержать количество выбранных в 24 * запросе столбцов. Оно будет равно L_DESCTBL.COUNT;

25 * эта переменная содержит избыточную информацию. Переменная 26 * L_DESCTBL содержит полезные сведения о выбранных столбцах. 27 */ 28 29 dbms_sql.describe_columns(c => l_theCursor, 30 col_cnt => l_colCnt, 31 desc_t => l_descTbl);

Динамический SQL 33 for i in 1.. l_colCnt 34 loop 35 dbms_output.put_line 36 ('Column Type ' || l_descTbl(i).col_type);

37 dbms_output.put_line 38 ('Max Length ' || l_descTbl(i).col_max_len);

39 dbms_output.put_line 40 ('Name ' || l_descTbl(i).colname);

41 dbms_output.put_line 42 ('Name Length ' || l_descTbl(i).col_name_len);

43 dbms_output.put_line 44 ('ObjColumn Schema Name.' || l_descTbl(i).col_schema_name);

45 dbms_output.put_line 46 ('Schema Name Length....' || l_descTbl(i).col_schema_name_len) ;

47 dbms_output.put_line 48 ('Precision ' || l_descTbl(i),col_precision) ;

49 dbms_output.put_line 50 ('Scale ' || l_descTbl(i).col_scale);

51 dbms_output.put_line 52 ('Charsetid ' || l_descTbl (i). col_Charsetid) ;

53 dbms_output.put_line 54 ('Charset Form ' || l_descTbl(i).col_charsetform) ;

55 if (l_desctbl(i).col_null_ok) then 56 dbms_output.put_line( 'Nullable Y');

57 else 58 dhms_output.put_line( 'Nullable N') ;

59 end if;

60 dbms_output.put_line(' ') ;

61 end loop;

62 63 /* Шаг 9: закрыть курсор и освободить ресурсы. */ 64 dbms_sql.close_cursor(l_theCursor);

65 exception 66 when others then 67 dbms_sql.close_cursor(l_theCursor);

68 raise;

69 end desc_query;

70 / Procedure created. scott@TKYTE816> set serveroutput on scott@TKYTE816> exec desc_query('select rowid, ename from emp');

Column Type 11 Max Length 16 Name ROWID Name Length 5 ObjColumn Schema Name. Schema Name Length.... 0 Precision 0 Scale 0 Charsetid Глава Charset Form Nullable 0 Y Column Type 1 Max Length 10 Name ENAME Name Length 5 ObjColumn Schema Name. Schema Name Length....0 Precision 0 Scale 0 Charsetid 31 Charset Form 1 Nullable У PL/SQL procedure successfully completed. К сожалению, значение COLUMN TYPE — число, а не имя типа данных, так что если не знать, что значение 11 соответствует типу ROWID, а значение 1 — типу VARCHAR2, расшифровать эти результаты не удастся. В руководстве Oracle Call Interface Programmer's Guide представлен полный список внутренних кодов типов данных и соответствующих имен типов. Этот список воспроизведен ниже.

VARCHAR2, NVARCHAR2 NUMBER LONG ROWID DATE RAW LONG RAW CHAR, NCHAR Пользовательский тип (объектный тип, VARRAY, вложенная таблица) REF CLOB, NCLOB BLOB BFILE UROWID 1 2 8 11 12 23 24 96 108 111 112 113 114 Теперь мы готовы рассмотреть всю подпрограмму, которая может принять практически любой запрос и сбросить результаты его выполнения в файл операционной системы (предполагается, что пакет UTL_FILE настроен;

эта настройка подробно описана в приложении А):

Динамический SQL scott@TKYTE816> create or replace 2 function dump_fixed_width(p_query in varchar2, 3 p_dir in varchar2, 4 p_filename in varchar2) 5 return number 6 is 7 l_output utl_file.file_type;

8 l_theCursor integer default dbms_sql.open_cursor;

9 l_columnValue varchar2(4000);

10 l_status integer;

11 l_colCnt number default 0;

12 l_cnt number default 0;

13 l_line long;

14 l_descTbl dbms_sql.desc_tab;

15 l_dateformat nls_session_parameters.value%type;

16 begin 17 select value into l_dateformat 18 from nls_session_parameters 19 where parameter = 'NLS_DATE_FORMAT';

20 21 /* Используем формат даты, включающий время. */ 22 execute immediate 23 'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';

24 l_output := utl_file.fopen(p_dir, p_filename, 'w', 32000);

25 26 /* Анализируем входной запрос, чтобы можно было получить его -> описание. */ 27 dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);

28 29 /* Теперь получаем описание результатов запроса. */ 30 dbms_sql.describe_columns(l_theCursor, l_colCnt, l_descTbl);

31 32 /* Необходимо определить каждый столбец и указать серверу, 33 * что и куда мы будем выбирать. В данном случае, все данные 34 * будут выбираться в одну переменную типа varchar2(4000). 35 * 36 * Мы также определим максимальный размер каждого столбца. Это 37 * делается для того, чтобы при выдаче данных каждое поле 38 * начиналось и заканчивалось в одной и той же позиции в каждой -> записи. 39 */ 40 for i in 1.. l_colCnt loop 41 dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);

42 43 if (l_descTbl(i).col_type = 2) /* тип number */ 44 then 45 L_descTbl(i).col_max_len := l_descTbl(i).col_precision+2;

46 elsif (l_descTbl(i).col_type = 12) /* тип date */ 47 then 48 /* длина заданного выше формата даты */ 49 l_descTbl(i).col_max_len := 20;

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 -> 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 Глава end if;

end loop;

l_status := dbms_sql.execute(l_theCursor);

while (dbms_sql.fetch_rows(l_theCursor) > 0) loop /* Строим большую строку результата. Это более эффективно, * чем вызывать процедуру UTL_FILE.PUT в цикле. */ l_line := null;

for i in 1.. l_colCnt loop dbms_sql.column_value(l_theCursor, i, l_columnValue);

l_line := l_line || rpad(nvl(l_columnValue, ' '), l_descTbl(i).col_max_len);

end loop;

/* Теперь выдаем строку в файл и увеличиваем значение счетчика. */ utl_file.put_line(l_output, l_line);

l_cnt := l_cnt+l;

end loop;

/* Освобождаем ресурсы. */ dbms_sql.close_cursor(l_theCursor);

utl_file.fclose(l_output);

/* Восстанавливаем формат даты... и завершаем работу. */ execute immediate 'alter session set nls_date_format=''' || l_dateformat || ''' ';

return l_cnt;

exception when others then dbms_sql.close_cursor (l_theCursor) ;

execute immediate 'alter session set nls_date_format=' '' t || l_dateformat || ''' ';

end dump_fixed_width;

/ Function created. Итак, эта функция использует подпрограмму DBMS_SQL.DESCRIBE_COLUMNS для поиска количества столбцов и их типов данных. Я изменил некоторые значения максимальных размеров, чтобы учесть используемый формат даты, а также десятичную запятую и знак в числах. Представленная выше подпрограмма не может выгрузить данные типа LONG, LONG RAW, CLOB и BLOB. Ее легко изменить для поддержки данных типа CLOB и даже LONG. Придется специальным образом выполнять связывание переменных этих типов, а также использовать пакет DBMS_CLOB для выборки данных типа CLOB и подпрограмму DBMS_SQL.COLUMN_VALUE_LONG — для данных типа Динамический SQL LONG. Следует заметить, что добиться этого с помощью встроенного динамического SQL невозможно — его нельзя использовать, если список выбора в PL/SQL не известен.

Многократное выполнение одного и того же оператора В данном случае придется выбирать между средствами пакета DBMS_SQL и встроенным динамическим SQL. За счет большего объема и сложности кода можно достичь более высокой производительности. Чтобы продемонстрировать это, я создам подпрограмму, динамически вставляющую в таблицу большое количество строк. В ней используется динамический SQL, поскольку до начала выполнения имя таблицы, куда будут вставляться данные, неизвестно. Для сравнения создадим четыре аналогичных подпрограммы: Подпрограмма DBMSSQL_ARRAY NATIVE_DYNAMIC_ARRAY DBMSSQL_NOARRAY NAT1VE_DYNAMIC_NOARRAY Назначение Использует обработку массивов в PL/SQL для множественной вставки строк Использует эмуляцию обработки массивов с помощью таблиц объектного типа Выполняет построчную обработку при вставке строк Выполняет построчную обработку при вставке строк Первый метод (используемый в подпрограмме DBMSSQL_ARRAY) наиболее масштабируем и обеспечивает наибольшую производительность. В моих тестах на различных платформах первый и второй методы были очень близки по результатам в однопользовательской среде: если на машине не работают другие пользователи, они более-менее сопоставимы. На некоторых платформах встроенный динамический SQL работал немного быстрее, на других — пакет DBMS_SQL. В многопользовательской среде, однако, из-за повторного полного анализа запроса при каждом выполнении во встроенном динамическом SQL, подход с использованием обработки массивов средствами пакета DBMS_SQL обеспечивал лучшую масштабируемость. При этом не нужно было выполнять мягкий разбор запроса при каждом выполнении. Необходимо также учесть, что для эмуляции обработки массивов во встроенном динамическом SQL пришлось применить трюк. Так что код в обоих случаях оказался достаточно сложным. Обычно код, где используется встроенный динамический SQL, намного проще, чем код с вызовами DBMS_SQL, но не в этом случае. Единственный определенный вывод, который можно сделать, — третий и четвертый методы намного медленнее первых двух. Следующие результаты были получены на платформе Solaris для одного пользователя, но результаты на платформе Windows были аналогичными. Выполните тесты на своей платформе, чтобы получить наиболее достоверные результаты. scott@TKYTE816> create or replace type vcArray as table of varchar2(400) 2/ Type created. scott@TKYTE816> create or replace type dtArray as table of date 2/ Глава Type created. scott@TKYTE816> create or replace type nmArray as table of number 2/ Type created. Эти типы необходимы для эмуляции обработки массивов с помощью встроенного динамического SQL. Массивы именно этих типов мы и будем использовать (во встроенном динамическом SQL вообще нельзя использовать PL/SQL-таблицы). Теперь представим спецификацию пакета, который будет использоваться для тестов: scott@TKYTE816> create or replace package load_data 2 as 3 4 procedure dbmssql_array(p_tname in varchar2, 5 p_arraysize in number default 100, 6 p_rows in number default 500);

7 8 procedure dbmssql_noarray(p_tname in varchar2, 9 p_rows in number default 500);

10 11 12 procedure native_dynamic_noarray(p_tname in varchar2, 13 p_rows in number default 500);

14 15 procedure native_dynamic_array(p_tname in varchar2, 16 p_arraysize in number default 100, 17 p_rows in number default 500);

18 end load_data;

19 / Package created. Каждая из представленных выше процедур будет динамически вставлять строки в таблицу, заданную параметром P_TNAME. Количество вставляемых строк определяет параметр P_ROWS;

при использовании обработки массивов их размер задается параметром P_ARRAYSIZE. Теперь переходим к реализации: scott@TKYTE816> create or replace package body load_data 2 as 3 4 procedure dbmssql_array(p_tname in varchar2, 5 p_arraysize in number default 100, 6 p_rows in number default 500) 7 is 8 l_stmt long;

9 l_theCursor integer;

10 l_status number;

11 l_coll dbms_sql.number_table;

12 l_col2 dbms_sql.date_table;

13 l_col3 dbms_sql.varchar2_table;

14 l_cnt number default 0;

15 begin Динамический SQL 16 l_stmt := 'insert into ' || p_tname || 17 ' q1 (a, b, c) values (:a, :b, :c)';

18 19 l_theCursor := dbms_sql.open_cursor;

20 dbms_sql.parse(l_theCursor, l_stmt, dbms_sql.native);

21 /* 22 * Здесь мы будем формировать данные. После формирования 23 * ARRAYSIZE строк, мы вставляем их все сразу. В конце 24 * цикла, если еще остались строки, мы их тоже вставляем. 25 */ 26 for i in 1.. p_rows 27 loop 28 l_cnt := l_cnt+l;

29 l_coll(l_cnt) := i;

30 l_col2(l_cnt) := sysdate+i;

31 l_col3(l_cnt) := to_char(i);

32 33 if (l_cnt = p_arraysize) 34 then 35 dbms_sql.bind_array(l_theCursor, ':a', l_coll, 1, l_cnt);

36 dbms_sql.bind_array(l_theCursor, ':b', l_col2, 1, l_cnt);

37 dbms_sql.bind_array(l_theCursor, ':c', l_col3, 1, l_cnt);

38 l_status := dbms_sql.execute(l_theCursor);

39 l_cnt := 0;

40 end if;

41 end loop;

42 if (l_cnt > 0) 43 then 44 dbms_sql.bind_array(l_theCursor, ':a', l_coll, 1, l_cnt);

45 dbms_sql.bind_array(l_theCursor, ':b', l_col2, 1, l_cnt);

46 dbms_sql.bind_array(l_theCursor, ':c', l_col3, 1, l_cnt);

47 l_status := dbms_sql.execute(l_theCursor);

48 end if;

49 dbms_sql.close_cursor(l_theCursor);

50 end;

51 Итак, в этой подпрограмме используются средства пакета DBMS_SQL для вставки массива из N строк с помощью одной операции. Мы используем перегруженную подпрограмму BIND_VARIABLE, позволяющую пересылать PL/SQL-таблицу соответствующего типа с загружаемыми данными. Мы также указываем границы массива, чтобы сервер Oracle "знал", где начинается и заканчивается блок данных в переданной PL/SQLтаблице. В данном случае всегда следует начинать с индекса 1 и заканчивать индексом L_CNT. Обратите внимание, что для имени таблицы в операторе INSERT задан псевдоним (корреляционное имя) Q1. Я сделал это для того, чтобы при анализе производительности с помощью утилиты TKPROF можно было определить, какие операторы INSERT использовались той или иной подпрограммой. Вообще, код получается довольно простым. Теперь представим реализацию на базе пакета DBMS_SQL без обработки массивов:

Глава 52 procedure dbmssql_noarray(p_tname in varchar2, 53 p_rows in number default 500) 54 is 55 l_stmt long;

56 l_theCursor integer;

57 l_status number;

58 begin 59 l_stmt := 'insert into ' || p_tname || 60 ' q2 (a, b, c) values (:a, :b, :c)';

61 62 l_theCursor := dbms_sql.open_cursor;

63 dbms_sql.parse(l_theCursor, l_stmt, dbms_sql.native);

64 /* 65 * Здесь мы будем формировать данные. 66 * Каждая строка вставляется отдельным оператором 67 * в цикле. 68 */ 69 for i in 1.. p_rows 70 loop 71 dbms_sql.bind_variable(l_theCursor, ':a', i ) ;

72 dbms_sql.bind_variable(l_theCursor, ':b', sysdate+i);

73 dbms_sql.bind_variable(l_theCursor, ':с', to_char(i));

74 l_status := dbms_sql.execute(l_theCursor);

75 end loop;

76 dbms_sql.close_cursor(l_theCursor);

77 end;

78 Эта подпрограмма отличается от предыдущей только тем, что не формируются массивы. Если вы пишете код, подобный этому, советую прибегнуть к обработке массивов. Как вскоре будет показано, это может существенно повысить производительность приложения. Теперь переходим к подпрограмме, использующей встроенный динамический SQL: 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 procedure native_dynamic_noarray(p_tname in varchar2, p_rows in number default 500) is begin /* * Здесь мы формируем строку и вставляем ее. * Что может быть проще для написания и выполнения! */ for i in 1.. p_rows loop execute immediate 'insert into ' || p_tname || ' q3 (a, b, c) values (:a, :b, :c)' using i, sysdate+i, to_char(i);

end loop;

end;

Динамический SQL В этой подпрограмме массивы не обрабатываются. Простенькая программа;

ее легко создать, но вот производительность будет очень низкой из-за постоянно выполняемых разборов оператора. Наконец, пример эмуляции вставки массивов с помощью встроенного динамического SQL: 96 procedure native_dynamic_array(p_tname in varchar2, 97 p_arraysize in number default 100, 98 p_rows in number default 500) 99 is 100 l_stmt long;

101 l_theCursor integer;

102 l_status number;

103 l_coll nmArray := nmArray();

104 l_col2 dtArray := dtArray();

105 l_col3 vcArray := vcArray();

106 l_cnt number := 0;

107 begin 108 /* 109 * Здесь мы будем формировать данные. После формирования 110 * ARRAYSIZE строк мы вставляем их все сразу. В конце цикла, 111 * если еще остались строки, мы их тоже вставляем. 112 */ 113 l_coll.extend(p_arraysize);

114 l_соl2.extend(p_arraysize);

115 l_соl3.extend(p_arraysize);

116 for i in 1.. p_rows 117 loop 118 l_cnt := l_cnt+l;

119 l_coll(l_cnt) := i;

120 l_col2(l_cnt) := sysdate+i;

121 l_col3(l_cnt) := to_char(i);

122 123 if (l_cnt = p_arraysize) 124 then 125 execute immediate 126 'begin 127 forall i in 1.. :n 128 insert into ' || p_tname || ' 129 q4 (a, b, c) values (:a(i), :b(i), :c(i));

130 end;

' 131 USING l_cnt, l_coll, l_col2, l_col3;

132 l_cnt := 0;

133 end if;

134 end loop;

135 if (l_cnt > 0) 136 then 137 execute immediate 138 'begin 139 forall i in 1.. :n 140 insert into ' || p_tname || ' 141 q4 (a, b, c) values (:a(i), :b(i), :c(i));

Глава 142 end;

' 143 USING l_cnt, l_coll, l_col2, l_col3;

144 end if;

145 end;

146 147 end load_data;

148 / Package body created. Как видите, тут все немного запутано. Наша подпрограмма создает код, который будет динамически выполняться. В этом динамически формируемом коде используется оператор FORALL для множественной вставки строк из массивов. Поскольку в операторе EXECUTE IMMEDIATE можно использовать только типы данных SQL, пришлось заранее создать в базе данных соответствующие типы. После этого необходимо динамически выполнять оператор: begin forall i in 1.. :n insert into t (a,b,c) values (:a(I), :b(I), :c(I));

end;

подставляя в него количество вставляемых строк и три массива значений. Как будет показано ниже, обработка массивов ускоряет вставку во много раз. Необходимо решить, стоит ли это ускорение того, чтобы отказаться от простоты написания подпрограммы с помощью встроенного динамического SQL при отсутствии массивов. Конечно, трудно что-то противопоставить одной строке кода! Если речь идет о программе одноразового использования, для которой производительность несущественна, я бы выбрал самый простой способ. Если речь идет о многократно используемой подпрограмме, которую будут использовать достаточно долго, я бы выбрал пакет DBMS_SQL, если скорость работы имеет значение и количество связываемых переменных заранее не известно, и — встроенный динамический SQL, если производительность приемлема, а количество связываемых переменных хорошо известно. Наконец, не стоит забывать о результатах, представленных в главе 10, — там было показано, что желательно сокращать количество мягких разборов. Пакет DBMS_SQL позволяет это сделать, а встроенный динамический SQL — нет. Необходимо хорошо представлять себе, что именно надо сделать, и выбирать соответствующий подход. Если пишется программа загрузки данных, которую запускают раз в день, и при этом запросы анализируются всего несколько сотен раз, встроенный динамический SQL прекрасно подходит. С другой стороны, если пишется подпрограмма, использующая один и тот же динамический SQL-оператор десятки раз и выполняемая одновременно десятками пользователей, имеет смысл использовать средства пакета DBMS_SQL, чтобы можно было проанализировать запрос один раз, а затем только выполнять. Я выполнил представленные ранее подпрограммы с помощью следующего тестового кода (помните, мы работаем в однопользовательской системе!): create table t (a int, b date, с varchar2(15));

alter session set sql_trace=true;

truncate table t;

Динамический SQL exec load_data.dbmssql_array('t', 50, 10000);

truncate table t ;

exec load_data.native_dynamic_array('t', 50, 10000);

truncate table t ;

exec load_data.dbmssql_noarray('t', 10000) truncate table t ;

exec load_data.native_dynamic_noarray('t', 10000) В отчете TKPROF можно обнаружить следующее: B G N load_data.dbms_sql_array('t', 50, 10000);

END;

EI call Parse Execute Fetch total count 1 1 0 2 cpu 0.01 2.58 0.00 2.59 elapsed 0.00 2.83 0.00 2.83 disk 0 0 0 0 query 0 0 0 0 current 0 0 0 rows 0 1 0 BEGIN load_data.native_dynamic_array('t', 50, 10000);

END;

call Parse Execute Fetch total total count 1 1 0 2 cpu 0.00 2.39 0.00 2.39 2.39 elapsed 0.00 2.63 0.00 2.63 2.63 disk 0 0 0 0 query 0 0 0 0 current 0 0 0 0 rows 0 1 0 Общие профили выполнения очень близки: 2,59 и 2,30 секунд процессорного времени. Различие — в деталях. Если вы обратили внимание, в представленном ранее коде я сделал каждый оператор вставки уникальным, добавив псевдонимы таблиц Ql, Q2, Q3 и Q4. Благодаря этому можно определить, сколько раз анализировался каждый оператор. В подпрограмме на основе пакета DBMS_SQL и массивов использовался псевдоним Q1, а в подпрограмме со встроенным динамическим SQL — псевдоним Q4. Получены следующие результаты: i n s e r t into t q1 (a, b, c) values (:a, :b, :c) call Parse и: begin f o r a l l i in 1.. :n i n s e r t into t q4 (a, b, c) values ( : a ( i ), : b ( i ), : c ( i ) ) ;

end;

call Parse count 200 cpu 0.10 elapsed 0.07 disk 0 query 0 current 0 rows 0 count 1 cpu 0.00 elapsed 0.01 disk 0 query 0 current 0 rows call Глава INSERT INTO T Q4 (A,B,C) VALUES (:Ы,:Ь2,:ЬЗ) count 200 cpu 0.07 elapsed disk 0.04 0 query 0 current 0 rows Parse Как видите, подпрограмме, использующей средства пакета DBMS_SQL, хватило всего одного разбора, а вот при использовании встроенного динамического SQL анализировать операторы пришлось 400 раз. В загруженной системе, где одновременно работает множество пользователей, это может существенно снизить производительность. Поскольку избежать избыточных разборов можно и соответствующий код с использованием средств пакета DBMS_SQL не намного сложнее, я считаю оптимальным при решении подобного рода задач использовать DBMS_SQL. Хотя код и сложнее, но для обеспечения более высокой масштабируемости я бы использовал именно его. Если сравнить результаты процедур, не обрабатывающих массивы, оказывается, что они существенно хуже: BEGIN load_data.dbmssql_noarray('t', 10000);

END;

call Parse Execute Fetch total call Parse Execute Fetch total count 1 1 0 2 count 1 1 0 2 cpu 0.00 7.66 0.00 7.66 cpu 0.00 6.15 0.00 6.15 elapsed disk 0.00 7.68 0.00 7.68 elapsed 0.00 6.25 0.00 6.25 0 0 0 0 disk 0 0 0 0 query 0 0 0 0 query 0 0 0 0 current 0 0 0 0 current 0 0 0 0 rows 0 1 0 1 rows 0 1 0 BEGIN load_data.native_dynamic_noarray('t', 10000);

END;

Несомненно, имеет смысл использовать встроенный динамический SQL. Но и без массивов я все равно использовал бы средства пакета DBMS_SQL. И вот почему: insert into t q2 (a, b, c) values (:a, :b, :c) call Parse call Parse count 1 count 10000 cpu 0.00 cpu 1.87 elapsed 0.00 elapsed 1.84 disk 0 disk 0 query 0 query 0 current 0 current 0 rows 0 rows insert into t q3 (a, b, c) values (:a, :b, :c) Оказывается, что при использовании встроенного динамического SQL пришлось выполнить 10000 мягких разборов, и лишь один — при использовании пакета DBMS_SQL В многопользовательской среде реализация на основе пакета DBMS_SQL окажется намного более масштабируемой.

Динамический SQL Аналогичные результаты можно получить и при обработке множества строк, выдаваемых по динамически формируемому запросу. Обычно данные можно выбирать из курсорных переменных массивами, но только из строго типизированных. Это такие курсорные переменные, структура которых известна на этапе компиляции. Встроенный динамический SQL поддерживает использование только слабо типизированных курсорных переменных и поэтому не поддерживает множественную выборку, BULK COLLECT. Если попытаться выполнить оператор BULK COLLECT для динамически открытой курсорной переменной, будет получено сообщение об ошибке: ORA-01001: Invalid Cursor Вот сравнение двух процедур, выбирающих и подсчитывающих все строки из представления ALL_OBJECTS. Процедура, использующая средства пакета DBMS_SQL и обрабатывающая массивы, работает почти вдвое быстрее: scott@TKYTE816> create or replace procedure native_dynamic_select 2 as 3 type rc is ref cursor;

4 l_cursor rc;

5 l_oname varchar2(255);

6 l_cnt number := 0;

7 l_start number default dbms_utility.get_time;

8 begin 9 open l_cursor for 'select object_name from all_objects';

10 11 loop 12 fetch l_cursor into l_oname;

13 exit when l_cursor%notfound;

14 l_cnt := l_cnt+l;

15 end loop;

16 17 close l_cursor;

18 dbms_output.put_line(L_cnt || ' rows processed');

19 dbms_output.put_line 20 (round((dbms_utility.get_time-l_start)/100, 2) || ' seconds');

21 exception 22 when others then 23 if (l_cursor%isopen) 24 then 25 close l_cursor;

26 end if;

27 raise;

28 end;

29 / Procedure created. scott@TKYTE816> create or replace procedure dbms_sql_select 2 as 3 l_theCursor integer default dbms_sql.open_cursor;

4 l_columnValue dbms_sql.varchar2_table;

5 l_status integer;

6 l_cnt number := 0;

Глава 7 l_start number default dbms_utility.get_time;

8 begin 9 10 dbms_sql.parse(l_theCursor, 11 'select object_name from all_objects', 12 dbms_sql.native);

13 14 dbms_sql.define_array(l_theCursor, 1, l_columnValue, 100, 1);

15 l_status := dbms_sql.execute(l_theCursor);

16 loop 17 l_status := dbms_sql.fetch_rows(l_theCursor);

18 dbms_sql.column_value(l_theCursor,1,l_columnValue);

19 20 l_cnt := l_status+l_cnt;

21 exit when l_status <> 100;

22 end loop;

23 dbms_sql.close_cursor(l_theCursor);

24 dbms_output.put_line(L_cnt || ' rows processed');

25 dbms_output.put_line 26 (round((dbms_utility.get_time-l_start)/100, 2 ) || ' seconds');

27 exception 28 when others then 29 dbms_sql.close_cursor(l_theCursor);

30 raise;

31 end;

32 / Procedure created. scott@TKYTE816> set serveroutput on scott@TKYTE816> exec native_dynamic_select 19695 rows processed 1.85 seconds PL/SQL procedure successfully completed. scott@TKYTE816> exec native_dynamic_select 19695 rows processed 1.86 seconds PL/SQL procedure successfully completed. scott@TKYTE816> exec dbms_sql_select 19695 rows processed 1.03 seconds PL/SQL procedure successfully completed. scott@TKYTE816> exec dbms_sql_select 19695 rows processed 1.07 seconds PL/SQL procedure successfully completed. Снова приходится выбирать между производительностью и простотой кода. Для обработки массивов средствами пакета DBMS_SQL необходимо написать намного боль Динамический SQL ше кода, чем при использовании встроенного динамического SQL, но производительность при этом существенно возрастает.

Проблемы Как и в случае любого средства, при использовании динамического SQL есть ряд нюансов, которые необходимо учитывать. В этом разделе мы рассмотрим их последовательно. При использовании динамического SQL в хранимых процедурах возникает три основных проблемы: • нарушается цепочка зависимостей;

• код становится более "хрупким";

• настройка, обеспечивающая предсказуемое время выполнения, существенно усложняется.

Нарушение цепочки зависимостей Обычно при компиляции процедуры в базе данных все объекты, на которые она ссылается, а также все объекты, ссылающиеся на нее, регистрируются в словаре данных. Например, я создам функцию: ops$tkyte@DEV816> create or replace function count_emp return number 2 as 3 l_cnt number;

4 begin 5 select count(*) into l_cnt from emp;

6 return l_cnt;

7 end;

8/ Function created. ops$tkyte@DEV816> s e l e c t referenced_name, 2 from user_dependencies 3 where name = 'COUNT_EMP' 4 and type = 'FUNCTION' 5/ REFERENCED_NAME STANDARD SYS_STUB_FOR_PURITY_ANALYSIS EMP 3 rows selected. referenced_type REFERENCED_T PACKAGE PACKAGE TABLE Сравним это с тем, что зарегистрировано при последнем создании использующей встроенный динамический SQL функции GET_ROW_CNTS: ops$tkyte@DEV816> select referenced_name, referenced_type 2 from user_dependencies 3 where name = 'GET_ROW_CNTS' Глава and t y p e = 'FUNCTION' / REFERENCED_T PACKAGE PACKAGE REFERENCED_NAME STANDARD SYS_STUB_FOR_PURITY_ANALYSIS 2 rows selected.

Для функции, статически ссылающейся на таблицу ЕМР, эта ссылка зарегистрирована в таблице зависимостей. Для функции же с динамическим SQL — нет, поскольку она не зависит от таблицы ЕМР. В данном случае это вообще не проблема, поскольку использование динамического SQL дает другое существенное преимущество — возможность определить количество строк в таблице. Ранее, однако, мы иногда использовали динамический SQL без особой нужды, и эта нарушенная цепочка зависимостей — очень плохой побочный эффект. Необходимо знать, на какие объекты ссылаются процедуры и где они используются. При использовании динамического SQL такие взаимосвязи не отслеживаются.

"Хрупкость" кода При использовании только статического SQL можно быть уверенным, что если ух программа успешно скомпилирована, во встроенных в нее SQL-операторах нет синтаксических ошибок, т.к. при компиляции все проверяется. При использовании динамического SQL его корректность будет определена только на этапе выполнения. Более того, поскольку SQL-операторы создаются динамически, необходимо проверять возможные ветвления кода, чтобы убедиться в корректности генерируемого SQL-кода. Если при определенных входных данных генерируется корректный SQL-оператор, это еще не означает, что все будет работать при любых входных данных. Это верно для любого кода, но использование динамического SQL делает код несколько уязвимым. Динамический SQL позволяет сделать многое, что по-другому сделать нельзя, но по возможности надо использовать статический SQL. Он выполняется быстрее, требует меньше ресурсов и менее уязвим.

Сложность настройки Это неочевидно, но приложение, динамически создающее запросы, настраивать сложно. Обычно можно получить полный список запросов, используемых приложением, выделить те из них, которые влияют на производительность, и бесконечно их настраивать. Если набор выполняемых приложением запросов не известен до того, как приложение начнет работать, нельзя точно узнать, какой будет его производительность. Предположим, создается хранимая процедура, динамически строящая запрос на основе введенных пользователем в Web-форме данных. Если не протестировать все возможные запросы, которые могут быть сгенерированы этой процедурой, нельзя понять, все ли необходимые индексы созданы и можно ли вообще считать настройку системы законченной. Даже при небольшом количестве столбцов (скажем, пяти) могут быть заданы десятки условий. Это не означает, что следует отказаться от использования динамичес Динамический SQL кого SQL, но будьте готовы к такого рода проблемам, если выполняются запросы, о возможности генерирования которых системой вы даже не задумывались.

Резюме В этой главе мы детально изучили использование динамического SQL в хранимых процедурах;

рассмотрели различия между его реализацией средствами встроенного динамического SQL и пакета DBMS_SQL;

выяснили, когда использовать тот или иной подход. Оба подхода имеют свои преимущества и назначение. Динамический SQL позволяет создавать процедуры, реализовать которые иначе просто невозможно, — универсальные утилиты выгрузки и загрузки данных и т.п. На сайте издательства Wrox вы можете найти программы, использующие динамический SQL, утилиту для загрузки файлов dBASE III в базу данных Oracle с помощью PL/SQL, сценарий для печати результатов выполнения запросов в SQL*Plus по столбцам (об этом мы поговорим подробно в главе 23), программы для транспонирования результирующих множеств и многое другое.

interMedia interMedia — это набор средств, тесно интегрированных в СУБД Oracle и обеспечивающих загрузку в базу данных и безопасное управление разнородной информацией (rich content), а также доступ к ней в приложениях. Подобная информация широко используется в большинстве современных Web-приложений и включает текст, данные, изображения, аудио- и видеофайлы. Эта глава посвящена одному из моих любимых компонентов interMedia: interMedia Text. Я считаю, что технология interMedia Text обычно используется мало. Это происходит из-за недостаточного понимания ее сути и возможностей. Большинству специалистов известны только общие сведения о возможностях interMedia;

еще они знают, как обеспечить поддержку работы с текстом для своих таблиц. При ближайшем же рассмотрении оказывается, что interMedia Text — замечательное и уникальное средство СУБД Oracle. После краткого обзора истории interMedia мы: • обсудим использование компонента interMedia Text, в частности, для поиска текста, индексирования данных из множества различных источников и поиска в приложениях XML;

• кратко рассмотрим, как реализованы соответствующие возможности в СУБД;

• рассмотрим ряд особенностей компонента interMedia Text: индексирование, использование оператора ABOUT и поиска в разделах.

Глава Краткий исторический экскурс В ходе разработки большого проекта в 1992 году я впервые столкнулся с компонентом interMedia Text, или, как он тогда назывался, SQL*TextRetrieval. В это время одной из моих задач была интеграция множества различных СУБД для создания большой распределенной сети баз данных. Одна из этих СУБД была настолько "закрытой", насколько это вообще возможно. Она не обеспечивала SQL-интерфейс для управления базой данных и доступа к текстовым данным. Мы должны были создать для нее SQL-интерфейс. Примерно в середине работы над проектом наш консультант по технологиям Oracle предоставил информацию о следующем поколении программного продукта Oracle SQL*TextRetrieval, которое должно было называться TextServer3. Одно из преимуществ TextServer3 состояло в высокой степени оптимизации для работы в клиент/серверной среде. Кроме того, в составе TextServer3 предлагался несколько заумный интерфейс на языке С, но теперь я, по крайней мере, мог хранить все текстовые данные в базе данных Oracle и иметь при этом возможность обращаться к другим данным в той же базе данных с помощью SQL-операторов. Мне это понравилось. В 1996 году корпорация Oracle выпустила следующее поколение продукта TextServer под названием ConText Option, которое существенно отличалось от предыдущих версий. Не надо было больше хранить тексты и управлять ими через функциональный интерфейс на языке С или в среде Forms. Все можно было сделать в SQL. Компонент ConText Option предоставил множество PL/SQL-процедур и пакетов, позволяющих сохранять текст, создавать индексы, выполнять запросы, выполнять операции сопровождения для индексов и т.п., и для этого больше не требовалось писать ни одной строки кода на языке С. Среди многих преимуществ ConText Option, по моему мнению, два были наиболее существенными. Первое, и самое главное — ConText Option перестал быть лишь слабо интегрированным, периферийным компонентом СУБД. Он поставлялся в составе СУБД Oracle7 как отдельно лицензируемый необязательный компонент СУБД и был интегрирован в Oracle7. Второе преимущество состояло в том, что компонент ConText Option не только выполнял стандартную процедуру поиска текста, но и поддерживал лингвистический анализ текстов и документов, что позволило разработчикам создавать системы, "читающие между строк" и реально учитывающие общий смысл текстов. Не забывайте также, что все это было доступно через SQL, т.е. существенно упрощало использование этих развитых средств. Одним из существенных усовершенствований СУБД Oracle8i является стройная система расширения возможностей. На основе поддерживаемых служб разработчики получили средства создания специализированных, сложных типов данных, а также возможность организовывать собственные базовые службы СУБД для поддержки этих типов данных. В рамках этой системы расширения можно создавать новые типы индексов, использовать специализированные методы сбора статистической информации, а также интегрировать в СУБД Oracle специализированные функции оценки стоимости и избирательности методов доступа. На основе этой информации оптимизатор запросов может разумно и эффективно обращаться к новым типам данных. Создававшая ConText Option команда разработчиков оценила значимость этой системы расширения и занялась со interMedia зданием современного продукта, interMedia Text, который впервые появился в составе Oracle8i в 1999 году.

Использование компонента interMedia Text Компонент interMedia Text можно использовать в приложениях для многих целей, в том числе: • Поиск текста. Компонент interMedia Text позволяет быстро создать приложение, обеспечивающее эффективный поиск в текстовых данных. • Управление разнородными документами. Компонент interMedia Text позволяет создать приложение, обеспечивающее поиск по документам в различных форматах, в том числе в текстовых файлах, файлах Microsoft Word, Lotus 1-2-3 и Microsoft Excel. • Индексирование текста из различных источников данных. Компонент interMedia Text позволяет создать приложение, управляющее текстовыми данными не только в базе данных Oracle, но и в файловой системе и в сети Internet. • Создание приложений, "читающих между строк". Помимо обеспечения эффективного поиска слов и фраз, компонент interMedia Text позволяет построить "базу знаний" с краткими резюме по каждому документу или проклассифицировать документы по описываемым в них понятиям, а не просто по содержащимся словам. • Поиск в приложениях XML. Компонент interMedia Text предоставляет разработчикам приложений все необходимые средства для создания систем, не только запрашивающих содержимое ХМL-документов, но и позволяющих выполнять запросы к определенной структуре в XML-документе. Наличие всех этих функциональных возможностей в СУБД Oracle позволяет при работе с текстовыми данными в полном объеме использовать присущую ей масштабируемость и защиту данных.

Поиск текста Разумеется, есть много способов поиска текста в базе данных Oracle и без использования компонента interMedia. В следующем примере мы создадим простую таблицу, вставим несколько строк, а затем воспользуемся стандартной функцией INSTR и оператором LIKE для поиска по текстовому столбцу таблицы: SQL> 2 3 4 5 create table mytext (id number primary key, thetext varchar2(4000) ) / Table created. SQL> insert into mytext 2 values(1, 'The headquarters of Oracle Corporation is ' || Глава 17 'in Redwood Shores, California.');

1 row created. SQL> insert into mytext 2 values(2, 'Oracle has many training centers around the world.');

1 row created. SQL> commit;

Commit complete. SQL> select id 2 from mytext 3 where instr(thetext, 'Oracle') > 0;

ID 1 2 SQL> select id 2 from mytext 3 where thetext like '%Oracle%';

ID 1 2 С помощью встроенной функции SQL INSTR можно искать вхождения подстроки в строке. С помощью оператора LIKE можно также искать строки, соответствующие шаблону. Во многих случаях функция INSTR или оператор LIKE идеально подходят для решения поставленной задачи, и все прочие средства просто избыточны, особенно при поиске в сравнительно небольших таблицах. Однако эти методы поиска текста обычно требуют полного просмотра таблицы и огромных ресурсов. Более того, функциональные возможности такого поиска весьма ограничены. Они не помогут, например, если необходимо создать приложение, поддерживающее следующие запросы: • найти все строки, содержащие слово "Oracle" рядом со словом "Corporation" так, что их разделяет не более двух слов;

• найти все строки, содержащие слово "Oracle" или слово "California", отсортировав результаты по релевантности;

• найти все строки со словами, имеющими корень "train" (например, trained, training, trains);

• выполнить поиск строки в библиотеке документов независимо от регистра символов. Эти запросы — лишь малая часть того, что нельзя сделать традиционными средствами, но легко делается с помощью компонента interMedia Text. Чтобы продемонстрировать, насколько легко interMedia позволяет отвечать на приведенные выше запросы, необходимо сначала создать индекс interMedia Text по нашему текстовому столбцу:

interMedia Чтобы использовать PL/SQL-пакеты компонента interMedia Text, пользователю должна быть предоставлена роль С7ХАРР.

S L create index mytext_idx Q> 2 on mytext(thetext) 3 indextype is CTXSYS.CONTEXT 4/ Index created.

Создав индекс нового типа, CTXSYS.CONTEXT, мы обеспечили возможность эффективного поиска текста для существующей таблицы. Теперь можно использовать множество операторов, поддерживаемых компонентом interMedia Text, для сложной обработки текстовых данных. Следующие примеры демонстрируют использование оператора CONTAINS для ответа на четыре представленных ранее запроса (не обращайте пока внимания на особенности синтаксиса SQL-операторов, поскольку он будет подробно рассмотрен далее): SQL> s e l e c t id 2 from mytext 3 where contains(thetext, 'near((Oracle,Corporation),10)') > 0;

ID 1 SQL> 2 3 4 5 select score(l), id from mytext where contains(thetext, 'oracle or California', 1) > 0 order by score(l) desc / ID 1 SCORE(l) 4 SQL> select id 2 from mytext 3 where contains(thetext, '$train') > 0;

ID 2 Помимо функциональных возможностей индексы interMedia Text превосходят традиционные реляционные методы поиска текста в столбце и по производительности. Этот абсолютно новый тип индекса в базе данных Oracle предоставляет ценную информацию об индексированных данных, которую оптимизатор может использовать при создании плана выполнения запроса. Поэтому ядро СУБД Oracle получает оптимальный способ доступа к данным столбца, проиндексированным с помощью компонента interMedia Text.

Глава Управление разнородными документами Помимо возможности индексировать текстовые столбцы в базе данных компонент interMedia Text включает набор фильтров документов для множества форматов. Компонент interMedia Text будет автоматически обрабатывать документы Microsoft Word 2000 для Windows, Microsoft Word 98 для Macintosh, электронные таблицы Lotus 1-2-3, документы в формате Adobe Acrobat PDF и даже файлы презентаций Microsoft PowerPoint. Всего в составе компонента interMedia Text поставляется более 150 фильтров для разных типов файлов и документов. Наличие тех или иных фильтров зависит от версии Oracle 8i. Например, версии Oracle 8.1.5 и Oracle 8.1.6 были выпущены раньше, чем появился формат Microsoft Word 2000 для Windows. Поэтому в составе компонента interMedia Text версии 8.1.5 или 8.1.6 нет фильтра для этого типа документов, а в составе interMedia Text в Oracle 8.1.7 — уже есть. Технология фильтрования, включенная в состав interMedia Text, получена по лицензии от корпорации Inso Corporation и с точки зрения точности и эффективности я считаю ее лучшей из имеющихся на рынке. Список текущих поддерживаемых форматов файлов представлен в приложении к руководству Oracle8i interMedia Text Reference. На момент написания этой книги фильтры Inso не были доступны на платформе Linux, и перенос их на эту платформу не планировался, что очень печально. Это не означает, что компонент interMedia Text нельзя использовать на платформе Linux, но если используется версия Oracle 8i для Linux, придется либо ограничиться текстовыми и HTML-документами, либо создавать так называемые пользовательские фильтры, объекты USER_FILTER.

Индексирование текста из различных источников данных Компонент interMedia Text обеспечивает не только хранение файлов в базе данных. Источник данных (datastore object) interMedia Text позволяет указать, где именно должен храниться текст или данные. Источник данных обеспечивает необходимую для индекса interMedia Text информацию о том, где находятся данные. Эту информацию можно задать только при создании индекса. Как было показано в предыдущем примере, данные для индекса interMedia Text могут поступать непосредственно из базы данных — храниться в столбце таблицы. Этот источник данных, DIRECT_DATASTORE, является стандартным, если явно не указан другой источник. Столбец может быть типа CHAR, VARCHAR, VARCHAR2, BLOB, CLOB или BFILE. Можно создать индекс interMedia Text и по столбцу типа LONG или LONG RAW, но с момента выхода версии Oracle 8 эти типы считаются устаревшими, и их не стоит использовать во вновь создаваемых приложениях. Еще один полезный тип источника данных для текста, хранящегося в столбцах таблиц базы данных, — DETAIL_DATASTORE. Отношение главный/подчиненный часто interMedia встречается в приложениях. Это отношение задает связь между строкой в главной (родительской) таблице и нулем или более строк в подчиненной таблице и реализуется с помощью требования внешнего ключа в подчиненной таблице, ссылающегося на главную. Счет-фактура — хороший пример отношения главный/подчиненный: обычно одному счету-фактуре соответствует ноль или более строк в подчиненной таблице, описывающей купленные товары. Источник данных типа DETAIL_DATASTORE позволяет разработчику учесть эту логическую взаимосвязь таблиц. Давайте рассмотрим пример. Необходимо создать такую структуру из главной и подчиненной таблицы, чтобы запрос с помощью средств interMedia Text обращался к главной таблице, но фактически данные для interMedia Text брались бы из подчиненной таблицы. Создадим сначала главную и подчиненную таблицы и наполним их данными: SQL> 2 3 4 5 6 SQL> 2 3 4 5 6 create table purchase_order (id number primary key, description varchar2(100), line_item_body char(1) ) / create table line_item (po_id number, po_sequence number, line_item_detail varchar2(1000) ) / Table created.

Table created. SQL> insert into purchase_order (id, description) 2 values(1, 'Many Office Items') 3/ 1 row created. SQL> insert into line_item(po_id, po_sequence, line_item_detail) 2 values(1, 1, 'Paperclips to be used for many reports') 3/ 1 row created. SQL> insert into line_item(po_id, po_sequence, line_item_detail) 2 values(1, 2, 'Some more Oracle letterhead') 3/ 1 row created. SQL> insert into line_item(po_id, po_sequence, line_item_detail) 2 values(1, 3, 'Optical mouse') 3/ 1 row created. SQL> commit;

1254 Глава Обратите внимание: столбец LINE_ITEM_BODY по сути "фиктивный" — он существует, чтобы можно было создать индекс interMedia Text по главной таблице. Я никогда не буду вставлять в него данные. Прежде чем создавать индекс, необходимо задать параметры interMedia Text так, чтобы при создании индекса были найдены индексируемые данные: SQL> 2 3 4 5 6 7 8 begin ctx_ddl.create_preference('po_pref', 'DETAIL_DATASTORE');

ctx_ddl.set_attribute('po_pref', ' d e t a i l _ t a b l e ', ' l i n e _ t e m ' );

ctx_ddl.set_attribute('po_pref', 'detail_key', ' p o _ i d ' ) ;

ctx_ddl.set_attribute('po_pref', ' d e t a i l _ l i n e n o ', 'po_sequence');

ctx_ddl.set_attribute('po_pref', ' d e t a i l _ t e x t ', '1ine_item_detail');

end;

/ PL/SQL procedure successfully completed. Сначала мы создаем пользовательский параметр PO_PREF. Это источник данных типа DETAIL_DATASTORE, в котором будет храниться вся необходимая информация для поиска данных в подчиненной таблице. В следующих строках мы задаем имя подчиненной таблицы, ключ, по которому выполняется соединение с главной таблицей, порядок следования строк и, наконец, индексируемый столбец. Теперь создадим индекс и проверим его в работе: SQL> create index po_index on purchase_order(line_item_body) 2 indextype is ctxsys.context 3 parameters('datastore po_pref') 4/ Index created. SQL> select id 2 from purchase_order 3 where contains(line_item_body, 'Oracle') > 0 4/ ID 1 Хотя индекс создается по столбцу LINE_ITEM_BODY, при создании можно задать и столбец главной таблицы DESCRIPTION. Помните, однако, что любые изменения этого столбца (этот столбец — не фиктивный) вызовут переиндексацию строки главной таблицы и связанных с ней строк подчиненной таблицы. Компонент interMedia Text поддерживает также внешние источники данных, в частности файлы, не входящие в базу данных, а также универсальные локаторы ресурсов (Uniform Resource Locators — URLs). Во многих производственных средах файлы обычно хранятся в доступной по сети общей файловой системе. Необязательно хранить тексты и документы приложения в базе данных Oracle. Можно создать источник данных типа FILE_DATASTORE — это позволит серверу Oracle управлять только текстовым индексом и не заниматься хранением и защитой файлов. При использовании источника данных FILE_DATASTORE не надо хранить текст документа в столбце. Необходимо interMedia хранить ссылку на файл в файловой системе, по которой можно обратиться к файлу на сервере. Так что, даже если используется, например, Windows-клиент, а сервер Oracle работает на вашей любимой разновидности ОС UNIX, ссылка на файл должна задаваться по правилам файловой системы ОС UNIX, например /export/home/tkyte/ MyWordDoc.doc. Учтите, что этот способ доступа к внешним файлам никак не связан с альтернативным способом доступа из базы данных Oracle с помощью данных типа BFILE. Еще один тип источника данных, внешнего по отношению к базе данных, — URL_DATASTORE. Он очень похож на источник данных FILE_DATASTORE, но вместо ссылки на файл в файловой системе в столбце таблицы хранится URL. В момент индексации строки компонент interMedia Text фактически прочитает данные по протоколу HTTP. Но и в этом случае сервер Oracle не хранит и не управляет этими данными. Индекс создается на основе профильтрованного содержимого потока данных HTTP, a сами выбранные по URL данные не сохраняются. Протокол FTP тоже поддерживается при использовании источника данных URL_DATASTORE, так что interMedia Text позволяет индексировать также файлы, доступные для сервера баз данных по FTP. При использовании версии Oracle 8.I.7 или более новой можно также встраивать имя пользователя и пароль для FTP непосредственно в строку URL (например, ftp:// uid:pwd@ftp.bogus.com/tmp/test.doc). Некоторые думают, что источник данных URL_DATASTORE пригоден только для создания поискового робота (кстати, этой возможностью поиска в Web он в исходном виде не обладает). Это неверно. Мои коллеги создали очень большую распределенную систему баз данных с возможностью доступа из сети Internet. Она должна была обеспечить единый универсальный интерфейс поиска текстовых данных во всех задействованных базах. Для этого можно было создать систему индексов interMedia Text по таблицам в каждой базе данных, а затем выполнять декомпозицию запроса на множество распределенных запросов к этим базам данных. Однако они не пошли по пути, ведущему к неоптимальной производительности, а решили выделить один сервер для поддержки компонента interMedia Text и создали все индексы с помощью источника данных URL_DATASTORE. В этой системе удаленные базы данных отвечали за вставку адресов URL для новых или изменившихся документов в базу данных, обеспечивающую поиск. Таким образом, при каждом создании нового или изменении существующего документа серверу, обеспечивающему индексацию документов, передается URL для получения содержания этого документа. Индексирующей машине не приходится искать новые и измененные документы: предоставляющие их серверы просто уведомляют ее о новых поступлениях. При этом не только не нужен распределенный запрос, но и создается централизованный источник информации, что упрощает администрирование.

Компонент interMedia Text - часть базы данных Oracle Одной из наиболее существенных причин для использования компонента interMedia Text вместо решений на базе файловой системы является то, что этот компонент входит в базу данных Oracle. Во-первых, в базе данных Oracle поддерживаются транзакции, а в файловой системе — нет. Целостность данных не нарушается, а свойства ACID реляционной базы данных распространяются и на компонент interMedia Text.

Глава Свойства ACID (неделимость, согласованность, изолированность и продолжительность) представлены в главе 4 (в первой части книги — прим. научн. ред./ Во-вторых, в Oracle для работы с базой данных предлагается язык SQL, и компонент interMedia Text полностью доступен в SQL-операторах. Это позволяет при разработке приложений использовать множество инструментальных средств, "понимающих" язык SQL. При желании (хотя я этого и не рекомендую) можно создать приложение, использующее возможности компонента interMedia Text в электронных таблицах Microsoft Excel, подключаясь к базе данных Oracle через интерфейс ODBC. Поскольку в течение своей карьеры я часто выполнял функции администратора базы данных, меня очень радует тот факт, что все данные будут содержаться в базе данных Oracle, и при ее резервном копировании будет копироваться также приложение и все его данные. При необходимости можно будет восстановить приложение и его данные по состоянию на любой момент времени. Если используется решение на базе файловой системы, придется проверять, создана ли резервная копия базы данных и соответствующей файловой системы, и надеяться, что в момент копирования они были согласованы. При использовании компонента interMedia Text для индексирования информации, содержащейся вне базы данных Oracle, однако, необходимо немного изменить стратегию резервного копирования. Если используются источники данных URL_DATASTORE или FILE_DATASTORE, компонент interMedia Text поддерживает только ссылки на документы, но не сами документы. Документы эти со временем устаревают, удаляются или оказываются недоступными по другим причинам, и это может отрицательно сказаться на работе приложения. Кроме того, при резервном копировании базы данных Oracle уже не происходит полное резервное копирование приложения. Необходимо придумать отдельную стратегию резервного копирования для документов, хранящихся вне базы данных Oracle.

Смысловой анализ Обратитесь к своей любимой поисковой системе, введите часто встречающееся в сети Internet слово, например 'database', и ждите в ответ огромного количества результатов поиска. Индексирование текстов —очень мощное средство, которое можно использовать во многих приложениях. Но его бывает недостаточно. Это особенно верно для очень больших объемов данных, анализировать которые пользователю сложно. Компонент interMedia Text включает интегрированные средства, позволяющие преобразовать все эти данные в полезную информацию. В компонент interMedia Text интегрирована расширяемая база знаний, которая используется в ходе индексирования и анализа текста и обеспечивает возможность лингвистического анализа. Можно не только искать текст, но и анализировать его смысл. Так что при создании индекса interMedia Text можно дополнительно сгенерировать список тем документов. Это позволяет создавать приложения, например, для анализа документов и классификации их по темам, а не по содержащимся словам или фразам. Когда возможность тематической классификации впервые появилась в базе данных Oracle, я придумал простой тест, чтобы оценить в ее возможности. Я загрузил в таблицу базы данных Oracle около тысячи коротких новостей из различных компьютерных жур interMedia налов. Затем создал индекс interMedia Text по столбцу, использовавшемуся для хранения текста статей, и сгенерировал список тем для каждой статьи. Выполнив поиск документов, посвященных теме "database", я обнаружил, что в их числе оказались статьи, не содержащие слова "database" и тем не менее отнесенные компонентом interMedia Text к теме "база данных" (database). Сначала я подумал, что это — ошибка в компоненте interMedia Text, но, разобравшись, понял, что обладаю потрясающей возможностью — находить в базе данных текст по смыслу. Речь не идет о статистическом анализе или хитроумном способе подсчета вхождений слов — это именно лингвистический анализ текста. Продемонстрирую эти возможности на примере: SQL> 2 3 4 5 SQL> 2 3 4 5 6 SQL> 2 3 4 5 6 7 create table mydocs (id number primary key, thetext varchar2(4000) ) / create table mythemes (query_id number, theme varchar2(2000), weight number ) / insert into mydocs(id, thetext) values(1, 'Go to your favorite Web search engine, type in a frequently occurring word on the Internet like ''database'', and wait for the plethora of search results to return.' ) / Table created.

Table created.

1 row created. SQL> commit;

Commit complete. SQL> create index my_idx on mydocs(thetext) indextype is ctxsys.context;

Index created. SQL> begin 2 ctx_doc.themes(index_name => 'my_idx', 3 textkey => '1', 4 restab => 'mythemes' 5 );

6 end;

7/ PL/SQL procedure successfully completed. SQL> select theme, weight from mythemes order by weight desc;

Глава THEME occurrences search engines Internet result returns databases searches favoritism type plethora frequency words 12 rows selected.

WEIGHT 12 12 11 11 11 11 10 6 5 4 3 PL/SQL-блок берет таблицу, на которую ссылается индекс MY_IDX, находит строку со значением key = 1 и выбирает проиндексированные данные. Затем эти данные обрабатываются тематическим анализатором. Анализатор генерирует список затронутых в документе тем, присваивая им "вес" (например, статья про банковскую деятельность может касаться тем "деньги", "кредит" и т.п.). Затем информация об этих темах помешается в таблицу MYTHEMES. Если проделать это для всех данных в приложении, пользователи смогут искать не только строки, содержащие определенное слово, но и строки, наиболее близкие по смыслу к определенному тексту. Учтите, что если предоставить компоненту interMedia Text больше данных для анализа, сгенерированный список тем может оказаться намного точнее, чем для рассмотренного простого предложения. Учтите также, что я создал столбец ID как первичный ключ. Для компонента interMedia Text в Oracle 8i 8.1.6 и более ранних версиях необходимо наличие первичного ключа для таблицы, прежде чем по ней можно будет создавать индекс interMedia Text. В Oracle 8i 8.1.7 и последующих версиях компонент interMedia Text больше не требует наличия первичного ключа при создании индекса.

Поиск в приложениях XML У меня часто спрашивают, как обеспечить эффективный поиск в документе со встроенной разметкой, например, на языке HTML или XML. К счастью, компонент interMedia Text позволяет очень просо решить эту задачу — за счет использования объектов, называемых разделами. Это решение легко использовать, сочетая возможности анализа XML (XML parsing) и задания разделов в источнике данных URL_DATASTORE. Если XML соответствует декларируемым целям, т.е. является средством взаимодействия разнородных систем, то разработчик приложения с помощью компонента interMedia Text может легко создать оперативную базу знаний с возможностями поиска данных из различных систем. Полный пример индексирования ХМL-документов представлен далее в этой главе.

interMedia Как работает компонент interMedia Text В этом разделе описано, как реализован компонент interMedia Text и что дает его использование. Как уже упоминалось, компонент interMedia Text создан с использованием стандартного механизма расширения Oracle. С помощью соответствующих средств команда разработчиков компонента interMedia Text смогла добавить в СУБД Oracle специфический тип индекса для текста. Внимательней присмотревшись к используемым объектам базы данных, можно "приподнять занавес" и получить представление о том, как реализован этот компонент. Объекты базы данных, составляющие компонент interMedia Text, всегда принадлежат пользователю CTXSYS: SQL> connect ctxsys/ctxsys Connected. SQL> select indextype_name, implementation_name 2 from user_indextypes;

INDEXTYPE_NAME CONTEXT CTXCAT IMPLEMENTATION_NAME TEXTINDEXMETHODS CATINDEXMETHODS Как видите, в схеме, которой принадлежит компонент interMedia Text, имеется два типа индексов. Один из индексов, CONTEXT, знаком большинству пользователей компонента interMedia Text. Второй индекс, СТХСАТ, — это индекс для каталога, обеспечивающий подмножество возможностей, доступных при использовании индекса CONTEXT. Индекс для каталога, появившийся в версии Oracle 8.1.7, идеально подходит для текстовых данных, представляющих собой небольшие фрагменты текста. S L select library_name, file_spec, dynamic from user_libraries;

Q> LIBRARY_NAME DR$LIB DR$LIBX FILE_SPEC O:\Oracle\Ora81\Bin\oractxx8.dll D N Y Как видите, с компонентом interMedia Text связаны две библиотеки. DRSLIB не является динамически компонуемой и представляет собой библиотеку проверенного кода в самой СУБД Oracle. DRSLIBX — это разделяемая, динамически компонуемая библиотека, зависящая от соответствующей операционной системы. Поскольку этот запрос был выполнен к базе данных, работающей в среде Windows, имя файла, содержащего эту разделяемую библиотеку, отражает особенности Windows. Если выполнить такой же запрос в среде UNIX, результат будет другим. Эти библиотеки специально предназначены для компонента interMedia Text. Они содержат набор методов, позволяющих ядру СУБД Oracle обрабатывать соответствующие индексы interMedia Text. SQL> select operator_name, number_of_binds from user_operators;

0PERATOR_NAME CATSEARCH NUMBER_OF_BINDS Глава 8 CONTAINS SCORE В рамках механизма расширения можно также создавать уникальные объекты базы данных — операторы. Оператор используется индексом;

с каждым оператором ассоциируется ряд связываний (bindings). Во многом аналогично языку PL/SQL, где можно определять функции с одинаковыми именами, но с различными типами параметров (сигнатурой), механизм расширения позволяет определить оператор, соответствующий различным пользовательским методам, в зависимости от сигнатуры использования. SQL> select distinct method_name, type_name from user_method_params order by 2 type_name;

METHOD_NAME ODCIGETINTERFACES ODCIINDEXALTER ODCIINDEXCREATE ODCIINDEXDELETE ODCIINDEXDROP ODCIINDEXGETMETADATA ODCIINDEXINSERT ODCIINDEXTRUNCATE ODCIINDEXUPDATE ODCIINDEXUTILCLEANUP ODCIINDEXOTILGETTABLENAMES RANK ODCIGETINTERFACES ODCIINDEXALTER ODCIINDEXCREATE ODCIINDEXDROP ODCIINDEXGETMETADATA ODCIINDEXTRUNCATE ODCIINDEXUTILCLEANUP ODCIINDEXUTILGETTABLENAMES ODCIGETINTERFACES 21 rows selected. После просмотра этих результатов становится понятным, как разработчик может использовать механизм расширения в базе данных Oracle. С каждым типом ассоциированы наборы поименованных методов, которые механизм расширения различает по уникальным именам. Например, методы, связанные с поддержкой индекса ODCIIndexInsert, ODCIIndexUpdate и ODCIIndexDelete, вызываются СУБД Oracle при создании, изменении или удалении данных, связанных с индексом. Таким образом, при необходимости вставки новой строки в индекс interMedia Text ядро Oracle вызывает метод, ассоциированный с ODCIIndexInsert. Это специально созданная подпрограмма, выполняющая необходимые операции с индексом interMedia Text, а затем уведомляющая СУБД Oracle о завершении обработки. Ознакомившись с основами реализации компонента interMedia Text, давайте рассмотрим некоторые из объектов базы данных, связываемые с этим специализированным индексом interMedia Text при его создании в базе данных. TYPE_NAME CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CATINDEXMETHODS CTX_FEEDBACK_ITEM_TYPE TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTINDEXMETHODS TEXTOPTSTATS interMedia SQL> s e l e c t table_name 2 from u s e r _ t a b l e s 3 where table_name l i k e '%MYTEXT%';

TOBLE_NAME MYTEXT SQL> 2 3 4 create index mytext_idx on mytext(thetext) indextype is ctxsys.context / Index created. SQL> select table_name 2 from user_tables 3 where table_name l i k e '%MYTEXT%';

TABLE_NAME DR$MYTEXT_IDX$I DR$MYTEXT_IDX$K DR$MYTEXT_IDX$N DR$MYTEXT_IDX$R MYTEXT Мы начали сеанс SQL*Plus с запроса к представлению USER_TABLES, выбирающего все имена таблиц, содержащие подстроку MYTEXT. После создания таблицы MYTEXT и индекса interMedia Text по столбцу этой таблицы оказывается, что имя пяти таблиц, включая исходную, содержит эту подстроку. Таким образом, при создании индекса interMedia Text автоматически создается еще четыре таблицы. Имена этих таблиц всегда будут иметь префикс DRS, за которым следует имя созданного индекса и один из суффиксов — $I, $К, $N или $R. Соответствующие таблицы всегда создаются в той же схеме, что и индекс interMedia Text. Давайте подробнее рассмотрим их структуру. SQL> desc dr$mytext_idx$i;

Name TOKEN_TEXT TOKEN_TYPE TOKEN_FIRST TOKEN_LAST TOKEN_COUNT TOKEN_INFO SQL> desc dr$mytext_idx$k;

Name DOCID TEXTKEY Null? NOT NOT NOT NOT NOT NULL NULL NULL NULL NULL Type VARCHAR2(64) NUMBER(3) NUMBER (10) NUMBER(10) NUMBER(10) BLOB Type Null?

NUMBER(38) NOT NULL ROWID Глава SQL> desc dr$mytext_idx$n;

Name NLT_DOCID NLT_MARK SQL> desc dr$mytext_idx$r;

Name ROW_NO DATA Null?

Type NOT NULL NUMBER(38) NOT NULL CHAR(l) Null? Type NUMBER(3) BLOB Для каждого индекса interMedia Text создается набор таблиц с подобной структурой. Таблица лексем, DR$MYTEXT_IDX$I, — это основная таблица индекса interMedia Text. Эта таблица используется для хранения каждой проиндексированной лексемы и битовой карты с установленными битами для всех документов, содержащих эту лексему. В этой таблице хранится и другая двоичная информация для оценки близости лексем в тексте. Обратите внимание, что я умышленно использую термин "лексема" в этом абзаце, поскольку компонент interMedia Text позволяет индексировать тексты на языках с иероглифической письменностью, включая китайский, японский и корейский. Было бы некорректно говорить об использовании таблицы DR$I для индексирования "слов". Таблицы DR$K и DR$R по сути поддерживают соответствие между идентификаторами строк (ROWID) и идентификаторами документов. Последняя таблица, DRSN, или "таблица отсутствующих строк", используется для поддержки списка удаленных документов/строк. При удалении строки из таблицы, по которой создан индекс interMedia Text, физическое удаление информации об этой строке из индекса interMedia Text откладывается. В этой служебной таблице записываются идентификаторы документов из удаленных строк для последующего удаления при следующей перестройке или оптимизации индекса. Учтите также, что таблицы DR$K и DR$N создаются как организованные по индексу. Обращения к этим таблицам в коде компонента interMedia Text обычно затрагивают оба столбца таблиц. Для повышения эффективности и сокращения объема ввода-вывода эти таблицы организуются по индексу. Подводя итоги этого раздела, хочу подчеркнуть, что, хотя и интересно разобраться, как компонент interMedia Text реализован с помощью механизма расширения Oracle, это вовсе не обязательно для эффективного использования interMedia Text. Многие разработчики создавали весьма сложные приложения с использованием компонента interMedia Text, ничего не зная о назначении создаваемых таблиц.

Индексирование с помощью interMedia Text Используя простую таблицу, созданную в предыдущем разделе, давайте по шагам пройдем процесс вставки текста, чтобы увидеть момент фактического выполнения соответствующих изменений компонентом interMedia Text: SQL> delete from mytext;

2 rows deleted.

interMedia SQL> insert into mytext(id, thetext) 2 values(1, 'interMedia Text is quite simple to use');

1 row created. SQL> insert into mytext(id, thetext) 2 values(2, 'interMedia Text is powerful, yet easy to learn');

1 row created. SQL> commit;

Commit complete. Итак, можно ли сейчас по запросу поиска Text получить обе строки таблицы? Может быть. Если индекс interMedia Text не синхронизирован, то выполненные изменения в нем еще не учтены. Синхронизация индекса означает выполнение всех ожидающих учета изменений. Как же определить, есть ли в индексе interMedia Text изменения, ожидающие учета? SQL> select pnd_index_name, pnd_rowid from ctx_user_pending;

PND_INDEX_NAME MYTEXT_IDX MYTEXT_IDX PND_ROWID AAAGF1AABAAAIV0AAA AAAGF1AABAAAIV0AAB Выполняя запрос к представлению CTX_USER_PENDING, можно определить, что ожидают изменения две строки индекса interMedia Text с именем MYTEXT_IDX. Представление CTX_USER_PENDING создано по принадлежащей пользователю CTXSYS таблице DRSPENDING. При любой вставке строки в таблицу MYTEXT в таблицу DRSPENDING будет вставляться строка для индекса MYTEXT_IDX interMedia Text. Обе вставки выполняются в одной физической транзакции, поэтому, если транзакция, вставившая строку в таблицу MYTEXT, будет отменена, произойдет также отмена вставки в таблицу DRSPENDING. Есть три различных способа синхронизации индекса interMedia Text. Эта синхронизация может выполняться в различных условиях и по разным причинам. Позже я скажу о том, когда предпочтительнее использовать тот или иной метод. Простейший метод синхронизации индекса — запуск программы ctxsrv. Эта программа работает аналогично демонам в ОС UNIX. Программа запускается, работает в фоновом режиме и время от времени автоматически синхронизирует индекс. Этот метод рекомендуется использовать при работе с небольшим количеством (до 10000) строк, каждая из которых содержит небольшой объем текста. Другой метод синхронизации индекса — выполнение оператора ALTER INDEX. Можно организовать очередь изменений, ожидающих выполнения, а затем построить и выполнить пакет действий по синхронизации индекса. Во многих случаях это лучший метод синхронизации индекса, обеспечивающий минимальную фрагментацию. Для синхронизации индекса используется следующий оператор: alter index [схема.]индекс rebuild [online] parameters('sync [memory обьем_памяти]') Имеет смысл пересоздавать индекс в оперативном режиме (online), чтобы он оставался доступным в процессе синхронизации. Кроме того, можно задать объем исполь Глава зуемой при этом памяти. Чем больше памяти выделено процессу синхронизации, тем большим может быть пакет индексируемых изменений и тем меньше окажется в итоге индекс interMedia Text. Хотя многие и сочтут третий метод синхронизации индекса одноразовым, я настаиваю, что простое пересоздание индекса тоже является методом синхронизации. При выполнении оператора CREATE INDEX для создания индекса типа CONTEXT будет создан индекс и проиндексированы все данные столбцов, по которым он создается. При этом часто действия выполняются циклически: в таблице есть данные, мы создаем индекс, а затем добавляем новые строки. Поскольку изменения, связанные с добавлением новых строк, не выполняются до момента синхронизации индекса, многие приходят к выводу, что единственный способ поддержать актуальность индекса — удалить и создать его заново! Индекс действительно синхронизируется, но такой метод крайне неэффективен, и я не рекомендую его использовать. Семантика языка SQL принципиально не позволяет двум пользователям одновременно выполнять оператор ALTER INDEX REBUILD для одного и того же индекса, но ничто не мешает пересоздавать или синхронизировать одновременно несколько индексов interMedia Text. Продолжая пример, синхронизируем индекс: SQL> alter index mytext_idx rebuild online parameters('sync memory 20M');

Index altered. SQL> select pnd_index_name, pnd_rowid from ctx_user_pending;

no rows selected Теперь индекс синхронизирован, и можно выполнять запрос, использующий его: SQL> select id 2 from mytext 3 where contains(thetext, 'easy') > 0 4/ ID 2 Просмотрим данные в одной из служебных таблиц, созданных автоматически при создании индекса interMedia Text: SQL> select token_text, token_type from dr$mytext_idx$i;

TOKEN_TEXT EASY INTERMEDIA LEARN POWERFUL QUITE SIMPLE TEXT USE YET TOKEN_TYPE 0 0 0 0 0 0 0 0 interMedia interMedia Text learning TOKEN_TEXT powerfulness simplicity 1 TOKEN_TYPE 1 Выполняя запрос к таблице DR$I, соответствующей индексу MYTEXT_IDX, мы можем оценить, какая часть информации обработана компонентом interMedia Text при синхронизации индекса. Во-первых, обратите внимание, что многие значения в столбце TOKEN_TEXT целиком состоят из прописных букв. Это реальные слова из текста, переведенные в верхний регистр. При необходимости можно потребовать от компонента interMedia Text создавать индекс по словам с учетом регистра при выполнении оператора CREATE INDEX.

Обратите также внимание, что некоторые лексемы, для которых в столбце TOKEN_TYPE находится значение 1, хранятся в смешанном регистре. Что еще важнее, ни в одной из строк таблицы MYTEXT нет слов "simplicity" и "learning". Так откуда же взялись эти данные? При разбиении лексическим анализатором блока текста на английском языке на лексемы стандартным действием является добавление в индекс информации о тематике текста. Таким образом, каждая строка со значением TOKEN_TYPE=1 — это тема, сгенерированная компонентом interMedia Text в процессе лингвистического анализа. Наконец, нельзя не заметить отсутствия некоторых слов в этой таблице. Слова is и to не значатся среди лексем в таблице индекса, хотя и входили в исходные данные проиндексированной таблицы. Они являются стоп-словами (stopwords) и не включаются в индекс как лишняя информация. Эти слова часто многократно встречаются в большинстве текстов на английском и по сути являются "шумом". Слова is, to и около 120 других входят в стандартный список стоп-слов (stoplist) для английского языка, который и используется по умолчанию при создании индекса. Корпорация Oracle включила в состав компонента interMedia Text стандартные списки стоп-слов более чем для 40 языков. Помните, что список стоп-слов использовать не обязательно, можно создавать и использовать собственные специализированные списки. Хочу завершить этот раздел предупреждением. Хотя весьма интересно разобраться, как именно устроен компонент interMedia Text, особенно посмотреть, какие лексемы генерируются при создании индекса, не пытайтесь создавать другие объекты базы данных, использующие внутренние структуры индекса. В частности, не создавайте представления для таблицы DR$MYTEXT_IDX$I или триггеры по таблице DR$MYTEXT_IDX$K. Структура реализации может измениться и скорее всего изменится уже в следующих версиях компонента.

Оператор ABOUT С появлением оператора ABOUT в Oracle стало намного проще выполнять тематический анализ в запросах, да и точность результатов, выдаваемых по таким запросам, существенно увеличилась. Для текстов на английском языке оператор ABOUT обеспе Глава чивает поиск всех строк, соответствующих нормализованному представлению искомого понятия. Как я уже говорил, по умолчанию для текстов на английском языке информация о тематике включается в индекс. Эта информация о тематике текста в индексе будет использоваться для поиска других строк, в которых затрагиваются близкие понятия. Если почему-либо было решено не генерировать информацию о тематике текста при создании индекса, оператор ABOUT будет выполнять простой поиск соответствующих лексем. S L select id from mytext where contains(thetext, 'about(databases)') > 0;

Q> no rows selected Как и ожидалось, в таблице нет строк, посвященных понятию "databases". SQL> select id from mytext where contains(thetext,'about(simply)') > 0;

ID Есть одна строка, связанная с понятием "просто" (simply). Если точнее, есть одна строка, содержащая понятия-синонимы нормализованной версии слова simply. Чтобы показать это, выполним: SQL> select id from mytext where contains(thetext,'simply') > 0;

no rows selected При удалении оператора ABOUT из запроса ни одна строка не возвращается — в столбец thetext не входит слово simply. Имеется, однако, одна строка, в которой используемые понятия соответствуют нормализованному корню слова simply. Связанное со словом понятие — не то же самое, что лингвистический корень слова. Оператор получения основы (stem operator — $) компонента interMedia Text позволяет искать инфлекционные (inflectional) или производные формы слова. Таким образом, поиск по основе слова health может дать в результате документы, содержащие слово healthy. Поиск синонимов понятия health (здоровье) с помощью оператора ABOUT может также вернуть документы, содержащие слово wellness. Оператор ABOUT очень легко включить в приложения для использования всей мощи средств генерации тематической информации и лингвистического анализа. Оператор ABOUT позволяет обеспечить в приложении не только поиск введенных пользователем слов, но и поиск связанных с ними понятий. Это действительно мощное средство.

Поиск в разделах Последняя тема, которую я хочу подробно рассмотреть, — поиск в разделах. Разделы обеспечивают избирательный доступ запроса к документу и могут существенно повысить точность запросов. Раздел может представлять собой не что иное, как заданную разработчиком последовательность символов, отмечающую начало и конец логической единицы в документе. Популярность стандартных языков разметки, таких как HTML и XML, позволяет продемонстрировать всю мощь средств поиска в разделах, предлагаемых компонентом interMedia Text.

interMedia Типичный документ содержит общеупотребительные логические элементы, образующие его структуру. У большинства документов есть название, может быть заголовок, может быть шаблонная информация, основной текст, содержание, предметный указатель, приложения и т.д. Все это — логические единицы, образующие структуру документа. В качестве примера ситуации, когда необходим поиск в разделах документов, рассмотрим гипотетическое хранилище документов для Министерства обороны. Может понадобиться найти в хранилище документы, содержащие фразу "Ракета Hellfire". Но может быть еще важнее найти документы, содержащие фразу "Ракета Hellfire" в заголовке документа или, например, в предметном указателе. Компонент interMedia Text позволяет разработчику приложения задать последовательность символов, выделяющую эти логические разделы структуры документа. Кроме того, компонент interMedia Text поддерживает поиск текста в заданных таким образом логических разделах. В компоненте interMedia Text для задания логических единиц или группировки текста в документе используется понятие "разделы". Набор идентификаторов разделов образует группу разделов, и именно группу разделов можно указать при создании индекса interMedia Text. Язык разметки гипертекста (Hypertext Markup Language — HTML) тоже первоначально создавался как способ организации структуры документа, но быстро стал языком, частично описывающим структуру, а частично — внешний вид документа. Тем не менее в состав interMedia Text стандартно входят компоненты, позволяющие создать раздел логической структуры документа из каждого тега разметки, содержащегося в документе. Аналогично, поддержка языка XML тоже встроена в компонент interMedia Text, начиная с версии Oracle 8.1.6. Для XML-документа можно легко (при желании — автоматически) задать разделы, соответствующие каждому заданному в нем XML-элементу. Давайте сначала рассмотрим следующий пример документа на языке HTML: SQL> create table my_html_docs 2 (id number primary key, 3 html_text varchar2(4000)) 4/ Table created. SQL> insert into my_html_docs(id, html_text) 2 values(1, 3 ' 4 15 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с 5 This is about the wonderful marvels of 8i and 9i 6 ') 7 / 1 row created. SQL> commit;

Commit complete. SQL> create index my_html_idx on my_html_docs(html_text) 2 indextype is ctxsys.context Глава / Index created. Теперь можно искать строку по проиндексированному в HTML-документе слову. SQL> select id from my_html_docs 2 where contains(html_text, 'Oracle') > 0 3/ ID 1 SQL> select id from my_html_docs 2 where contains(html_text, 'html') > 0 3/ ID 1 Легко создать запрос для поиска всех строк, содержащих слово Oracle, но в полученном решении очевидны два недостатка. Во-первых, элементы разметки индексировать не надо, поскольку они встречаются постоянно и во всех документах и не являются частью содержимого документа. Во-вторых, мы, конечно, можем искать слова в HTMLдокументе, но без учета структурных элементов, в которых они содержатся. Мы знаем, что где-то в тексте документа есть строка, содержащая слово Oracle, но это может быть заголовок, основной текст, колонтитул и т.п. Предположим, в приложении необходимо обеспечить поиск в заголовках HTMLдокументов. Создадим для этого группу разделов с тегом TITLE, а затем удалим и заново создадим индекс: SQL> begin 2 ctx_ddl.create_section_group('my_section_group','BASIC_SECTI0N_GROUP');

3 ctx_ddl.add_field_section( 4 group_name => 'my_section_group', 5 section_name => 'Title', 6 tag => 'title', 7 visible => FALSE);

8 end;

9/ PL/SQL procedure successfully completed. SQL> drop index my_html_idx;

Index dropped. SQL> create index my_html_idx on my_html_docs(html_text) 2 indextype is ctxsys.context 3 parameters('section group my_section_group') 4/ Index created. Мы создали новую группу разделов, MY_SECTION_GROUP, и добавили в нее раздел с именем Title. Обратите внимание, что раздел соответствует тегу title и будет неви interMedia дим. Если раздел помечен как видимый, текст между соответствующими тегами считается частью документа. Если же раздел помечен как невидимый, текст между начальным и конечным тегами рассматривается отдельно от документа и будет доступен только при поиске в соответствующем разделе. Как и большинство современных языков разметки (например, XML, HTML, WML), начальный тег в interMedia Text начинается символом < и заканчивается символом >. Конечный тег начинается с последовательности символов . SQL> select id 2 from my_html_docs 3 where contains(html_text, 'Oracle') > 0 4/ no rows selected Запрос, прежде возвращавший одну строку, теперь строк не возвращает, а мы ведь всего лишь задали группу разделов для индекса interMedia Text. Вспомните, что раздел Title был задан как невидимый, поэтому текст в тегах title рассматривается как подчиненный документ. SQL> select id 2 from my_html_docs 3 where contains(html_text, 'Oracle within title') > 0 4/ ID 1 Теперь можно выполнить запрос, выполняющий поиск только в разделах title всех документов. Если же попытаться искать текст самого тега, окажется, что компонент interMedia Text тег не проиндексировал: SQL> select id 2 from my_html_docs 3 where contains( html_text, 'title' ) > 0 4/ no rows selected Хотя ранее я задал собственный тип группы разделов на основе группы разделов BASIC_SECTION_GROUP, в состав компонента interMedia входят также заранее заданные группы разделов со стандартными системными установками для языков HTML и XML (HTML_SECTION_GROUP и XML_SECTION_GROUP). Использование такой группы разделов не определяет автоматически разделы для всех возможных элементов HTML и XML. Это надо делать самому. Однако при использовании указанных групп разделов компонент interMedia Text сможет корректно преобразовать размеченный документ в обычный текст. Попробуем применить соответствующую заданную группу в рассмотренном примере: SQL> drop index my_html_idx;

Index dropped.

Глава SQL> create index my_html_idx on my_html_docs(html_text) 2 indextype is ctxsys.context 3 parameters('section group ctxsys.html_section_group') 4/ Index created. SQL> select id 2 from my_html_docs 3 where contains(html_text, 'html') > 0 4/ no rows selected Оказывается, задав системные установки, соответствующие группе разделов HTML_SECTION_GROUP, мы избежали индексирования строк, являющихся тегами разметки языка HTML. Это не только повышает точность запросов к документам, но и сокращает общий размер индекса interMedia Text. Предположим, необходимо найти слово title во всех хранящихся HTML-документах. Если не использовать для индекса interMedia Text группу HTML_SECTION_GROUP, в ответ на этот запрос могут быть выданы все HTML-документы, содержащие раздел title (речь идет о части HTML-документа, между тегами 15 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с). Игнорируя теги и ограничиваясь исключительно содержимым HTML-документов, можно существенно повысить точность поиска. Рассмотрение обработки XML-документов начнем с примера. Предположим, необходимо управлять подборкой XML-документов и обеспечить интерфейс для запросов к структурным элементам этих документов. Чтобы усложнить задачу, предположим, что не все собранные XML-документы соответствуют одному и тому же определению структуры, задаваемому определением типа документа (Document Type Definition — DTD). По аналогии с предыдущим примером можно подумать, что необходимо определить все элементы XML-документов, в которых может потребоваться поиск, а затем задать раздел interMedia Text для каждого из этих элементов. К счастью, компонент interMedia Text включает средства автоматического создания и индексирования разделов по имеющимся в документе тегам. Появившаяся в компоненте interMedia Text версии Oracle 8.1.6, группа разделов AUTO_SECTION_GROUP работает аналогично группе разделов XML_SECTION_GROUP, но снимает с разработчика приложений необходимость заранее определять все разделы. Группа разделов AUTO_SECTION_GROUP требует от компонента interMedia Text автоматически создать разделы для всех непустых тегов в документе. Хотя разработчик явно может сопоставить тегу любое имя раздела, при такой автоматической генерации имена разделов будут совпадать с соответствующими тегами. SQL> create table my_xml_docs 2 (id number primary key, 3 xmldoc varchar2(4000) 4) 5/ Table created. SQL> insert into my_xml_docs(id, xmldoc) 2 values(1, interMedia 3 ' 4 15 Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с 5 31-MAR-200K/start_date> 6 1100 7 Review projects for QK/notes> 8 9 Joel 10 Tom 11 12 ') 13 / 1 row created. SQL> commit;

Commit complete. SQL> create index my_xml_idx on my_xml_docs(xmldoc) 2 indextype is ctxsys.context 3 parameters('section group ctxsys.auto_section_group') 4/ Index created.

Таким образом, без всяких дополнительных действий со стороны разработчика приложения компонент interMedia Text автоматически создал разделы для всех тегов, содержащихся в индексируемых ХМL-документах. Так что, если необходимо найти документы, содержащие слово projects в элементе note, достаточно выполнить следующий оператор: SQL> select id 2 from my_xml_docs 3 where contains(xmldoc, 'projects within notes') > 0 4/ ID 1 В процессе автоматического создания разделов созданы специальные разделы зон (zone section). В предыдущих примерах определения разделов создавались исключительно разделы полей (field section). В отличие от разделов полей, разделы зон могут перекрываться и быть вложенными. Поскольку при использовании группы разделов AUTO_SECTION_GROUP компонент interMedia Text создает разделы зон для всех непустых тегов, можно выполнять запросы вида: SQL> select id 2 from my_xml_docs 3 where contains(xmldoc, 'projects within appointment') > 0 4/ ID Глава SQL> select id 2 from my_xml_docs 3 where contains(xmldoc, 'Joel within attendees') > 0 4/ ID 1 Раздел, указанный в предыдущих запросах, не содержит искомых терминов явно: они вложены в структурные элементы этого раздела. Использование разделов зон и автоматического создания разделов позволяет управлять областью поиска в XML-документе, расширяя или сужая ее в соответствии с необходимостью. Используя группы разделов, можно потребовать от компонента interMedia Text проиндексировать атрибуты тегов. При использовании группы разделов AUTO_SECTION_GROUP значения атрибутов разделов автоматически выбираются и индексируются. Итак, если необходимо найти все персональные задания, другими словами, XMLдокументы, содержащие строку personal как значение атрибута type тега appointment, можно выполнить следующий запрос: SQL> select id 2 from my_xml_docs 3 where contains(xmldoc, 'personal within appointinent@type') > 0 4/ ID 1 Как видите, задание и индексирование разделов — очень мощное средство компонента interMedia Text. Следует, однако, помнить, что группу AUTO_SECTION_GROUP можно использовать не всегда. Хотя и есть возможность потребовать от компонента interMedia Text при автоматическом создании разделов не индексировать определенные теги, в конечном итоге в разделы может быть выделено и проиндексировано слишком много элементов документа, что "загрязняет" индекс. Общий размер индекса чрезвычайно увеличивается, что может резко снизить производительность поиска. Автоматическое создание разделов — мощное средство, но его надо использовать осторожно.

Проблемы При использовании компонента interMedia Text следует учитывать возможность возникновения ряда проблем. Не все они очевидны или возникают достаточно часто, поэтому я опишу наиболее типичные, с которыми мне приходилось сталкиваться.

Компонент interMedia Text — это НЕ система документооборота Это не проблема, скорее следствие неверного представления о назначении компонента interMedia Text. Я слышал, как при упоминании компонента interMedia Text клиенты и сотрудники корпорации Oracle называли его системой документооборота (document interMedia management solution). Без сомнения, interMedia Text не является системой документооборота. Документооборот — отдельная полноценная наука. Система документооборота предлагает набор средств, поддерживающих весь жизненный цикл документов. Она должна обеспечивать регистрацию входящих и исходящих документов, их логическую структуризацию, хранение нескольких версий документа и списков контроля доступа, интерфейс для поиска текста, а также возможность публикации документов. Компонент interMedia Text, конечно, не является системой документооборота. Он может использоваться в полной системе документооборота и обеспечивать многие из необходимых функций. Корпорация Oracle тесно интегрировала средства interMedia Text в состав системы Oracle Internet File System, которую можно использовать для управления содержимым сайтов (content management), и которая обеспечивает базовые функции системы документооборота.

Синхронизация индекса Часто необходимо создать систему на базе средств interMedia Text и выполнять в фоновом режиме процесс ctxsrv для периодической синхронизации индекса interMedia Text, причем синхронизация должна происходить в реальном времени. Одна из проблем, которые могут возникнуть при многократной и частой синхронизации индекса interMedia Text для большого набора документов, связана с его разрастанием и фрагментацией. Нет простого правила, позволяющего определить, когда следует периодически синхронизировать индексы большими пакетами, а когда лучше синхронизировать их с помощью процесса ctxsrv сразу после фиксации изменений в документах. Во многом это зависит от сути приложения, частоты изменения текста документов, общего количества и размера документов. В качестве примера можно привести мой Web-сайт AskTom. На этом сайте пользователи Oracle задают технические вопросы о программных продуктах Oracle. Вопросы просматриваются, на них даются подробные (я надеюсь) ответы, и эти ответы публикуются. Опубликованные вопросы и ответы вставляются в таблицу, проиндексированную с помощью индекса interMedia Text. На Web-сайте AskTom есть страница поиска по этой таблице опубликованных вопросов и ответов. Общее количество строк в этой системе сравнительно невелико (менее 10000 на момент написания этой главы). Изменения в системе практически никогда не происходят;

после публикации вопросов и ответов они почти никогда не изменяются и не удаляются. Каждый день вставляется обычно не более 25 строк, и вставки эти выполняются в течение всего дня. Мы выполняем синхронизацию индекса с помощью процесса ctxsrv, работающего в фоновом режиме, что идеально подходит для данной системы, предназначенной прежде всего для поиска в условиях небольшого количества вставок и изменений. Если же предполагается загрузка в таблицу по миллиону документов в неделю, не стоит индексировать их с помощью процесса ctxsrv. В этом случае имеет смысл синхронизировать индекс interMedia Text пакетами максимально большого размера, при котором еще не требуется откачка страниц памяти на диск. Выстроив запросы индексирова Глава ния в очередь и выполняя их большими пакетами, можно получить в результате более компактный индекс. Независимо от выбранного метода синхронизации следует периодически оптимизировать индексы interMedia Text с помощью оператора ALTER INDEX REBUILD. Процесс оптимизации позволит получить не только более компактный индекс, но и очистит его от информации, оставшейся после прежних логических удалений.

Индексирование информации вне базы данных Компонент interMedia Text не требует помещать текстовые данные в базу данных. С его помощью можно индексировать данные, содержащиеся в документах в файловой системе сервера или даже доступных извне по адресу URL. Когда данные находятся в базе данных Oracle, все изменения в них автоматически обрабатываются компонентом interMedia Text. Когда же источник данных находится вне базы данных, синхронизацию индекса с изменившимися внешними данными должен обеспечить разработчик приложения. Изменить отдельную строку проще всего, изменив один из столбцов, по которому создан индекс interMedia Text. Например, если бы я использовал следующую таблицу и индекс для поддержки списка проиндексированных адресов URL: SQL> 2 3 4 / create table my_urls (id number primary key, theurl varchar2(4000) ) Table created. SQL> create index my_url_idx on my_urls(theurl) 2 indextype is ctxsys.context 3 parameters('datastore ctxsys.url_datastore') 4/ Index created. Я мог бы "обновить" индекс для конкретной строки, выполнив: SQL> update my_urls 2 set theurl = theurl 3 where id = 1 4/ 0 rows updated.

Службы обработки документов Под "службами обработки документов" я подразумеваю набор средств компонента interMedia Text для преобразования документа в текстовый вид или формат HTML, с возможным выделением слов, найденных в документе в результате поиска. Многие ошибочно считают, что компонент interMedia Text сохраняет всю необходимую информацию для полного воссоздания документа. Это ошибочное представление приводит к выводу, что после индексирования исходный текст документа можно уда interMedia лить. Это верно, если придется только выполнять запросы к проиндексированной информации, но не верно, если предполагается поддержка тех или иных служб обработки документов. Например, если создать индекс interMedia Text с помощью URL_DATASTORE и попытаться сгенерировать HTML-представление найденной строки с помощью процедуры CTX_DOC.FILTER, ее вызов завершится сообщением об ошибке, если документ с соответствующим адресом URL недоступен. Компоненту interMedia Text для выполнения этого действия необходим доступ к исходному документу. Это относится также к файлам, хранящимся в файловой системе вне базы данных и проиндексированным как источник данных FILE_DATASTORE.

Индекс-каталог Во многих ситуациях индекс interMedia Text предоставляет намного больше возможностей, чем требуется приложению. Использование индекса interMedia Text, кроме того, требует выполнения определенных действий по сопровождению, обеспечивающих его оптимизацию, синхронизацию и т.д. Для поддержки приложений, которым не нужны все функциональные возможности индекса interMedia Text, в версии компонента interMedia Text в СУБД Oracle 8.1.7 появился новый тип индекса — индекс-каталог, или, сокращенно, ctxcat. Обычно большая часть текстовых данных не хранится в базе данных в виде большого набора документов. Во многих приложениях баз данных текст обычно неформатирован, состоит из небольших фрагментов и не разбит на логические разделы, а размер текстовых фрагментов настолько мал, что их качественный лингвистический анализ невозможен. Кроме того, такого рода приложения баз данных часто запрашивают текстовые данные по условиям на другие столбцы., Рассмотрим, например, базу данных, в которой регистрируются поступившие сообщения о проблемах и предлагаемые способы решения этих проблем. В соответствующей таблице может использоваться, скажем, 80символьное поле произвольного текста — тема (суть проблемы) и большое текстовое поле с описанием проблемы и способов ее решения. Кроме того, в таблице могут быть и другие столбцы со структурированной информацией, например датой поступления сообщения о проблеме, кодом аналитика, который над ней работает, кодом программного продукта, с которым связана проблема, и т.п. Мы имеем сочетание текста (по определению не являющегося документом) и структурированных данных. К этой таблице будут часто выполняться запросы типа "найти все проблемы, связанные с СУБД (программный продукт) версии 8.1.6 (еще один атрибут), где упоминается ошибка ORA-01555 в поле темы (текстовый поиск)". Именно для таких приложений и был создан индекскаталог. Как и можно было ожидать от "урезанной" версии полного индекса, индекс ctxcat имеет ряд ограничений. Поддерживаемые этим индексом операторы запросов являются подмножеством операторов "полного" индекса interMedia Text. Данные, которые индексируются с помощью индекса ctxcat, должны содержаться в базе данных Oracle в текстовом виде. Более того, индекс ctxcat не допускает использование нескольких языков в одном индексе. Однако даже с учетом этих ограничений индекс-каталог обеспечивает отличную производительность для многих приложений.

Глава Одна из приятных особенностей индекса-каталога — его не надо поддерживать. Операторы ЯМД применяются к этому индексу в рамках транзакции. Поэтому не нужно периодически синхронизировать индекс или запускать процесс ctxsrv для синхронизации в фоновом режиме. Еще одна серьезная причина использовать индекс-каталог связанна с присущей ему поддержкой структурированных запросов. Разработчик приложений может создавать наборы индексирования (index sets), которые позволяют с помощью индекса-каталога эффективно поддерживать как текстовый поиск, так и запросы к другим структурированным данным. Набор индексирования позволяет компоненту interMedia сохранять в индексе структурированную реляционную информацию вместе с индексируемым текстом. Это позволяет компоненту interMedia одновременно использовать поиск текста и поиск структурированной информации для нахождения документов, удовлетворяющих специфическим критериям. Рассмотрим небольшой пример: SQL> 2 3 4 5 create table mynews (id number primary key, date_created date, news_text varchar2(4000)) / Table created. SQL> insert into mynews 2 values(1, '01-JAN-1990', 'Oracle is doing well') 3/ 1 row created. SQL> insert into mynews 2 values(2, '01-JAN-2001', 'I am looking forward to 9i') 3/ 1 row created. SQL> commit;

Commit complete. SQL> begin 2 ctx_ddl.create_index_set('news_index_set');

3 ctx_ddl.add_index('news_index_set', 'date_created');

4 end;

5/ SQL> create index news_idx on mynews(news_text) 2 indextype is ctxsys.ctxcat 3 parameters('index set news_index_set') 4/ Index created. Обратите внимание, что для создания индекса-каталога указан тип индекса CTXSYS.CTXCAT. Кроме того, я создал набор индексирования NEWS_INDEX_SET и добавил в него столбец DATE_CREATED. Это позволит компоненту interMedia Text эффективно обрабатывать запросы, содержащие условия для обоих столбцов: NEWS_TEXT и DATE_CREATED.

interMedia SQL> select id 2 from mynews 3 where catsearch(news_text, 'Oracle', null) > 0 4 and date_created < sysdate 5/ ID SQL> select id 2 from mynews 3 where catsearch(news_text, 'Oracle', 'date_created < sysdate') > 0 4/ ID 1 Здесь мы видим оба метода поиска строк, содержащих слово Oracle в тексте сообщения, дата внесения которого, DATE_CREATED, предшествует текущему дню. Первый запрос сначала использует interMedia для поиска всех строк, которые могут удовлетворять запросу, а затем просматривает их в поисках тех, у которых значение DATE_CREATED меньше, чем SYSDATE. Второй, более эффективный запрос, будет использовать индекс interMedia, включающий столбец DATE_CREATED, для поиска только тех строк, которые одновременно удовлетворяют критерию поиска текста и условию DATE_CREATED < SYSDATE. При поиске по индексу-каталогу вместо оператора CONTAINS используется оператор CATSEARCH. Поскольку ранее был создан набор индексирования, содержащий столбец DATE_CREATED, стало возможным задать структурированное условие запроса непосредственно в операторе CATSEARCH. С помощью этой информации сервер Oracle может очень эффективно проверить оба условия запроса. На условия, которые можно указывать в операторе CATSEARCH, налагается ряд ограничений, в частности поддерживаются только логические операторы AND, OR и NOT, но для множества приложений этот индекс не только подходит, но и является оптимальным.

Возможные ошибки Общаясь с разработчиками приложений и клиентами, использующими компонент interMedia Text, я постоянно слышу об одном и том же небольшом наборе проблем. Эти проблемы рассматриваются в данном разделе.

Устаревший индекс Часто ко мне обращались с вопросом, почему часть информации проиндексирована, но недавно добавленные строки индексом не учитываются. Чаще всего причина этого в том, что индекс interMedia Text не синхронизирован. Выполнив запрос к представлению CTX_USER_PENDING, легко определить, есть ли отложенные изменения, ожи Глава дающие индексирования. Если есть, синхронизируйте индекс с помощью одного из описанных ранее методов. Еще одна типичная причина отсутствия информации в индексе связана с ошибками, особенно в процессе фильтрования. При попытке индексирования документов с помощью фильтров Inso, если формат документа не поддерживается, возникают ошибки, и соответствующий документ не индексируется. В представлении CTX_USER_INDEX_ERRORS можно найти достаточно информации, чтобы выяснить причины возникновения проблем при индексировании.

Ошибки внешней процедуры В версии компонента interMedia Text, поставлявшейся в составе СУБД Oracle 8.1.5 и 8.1.6, фильтрование текста выполнялось с помощью фильтров Inso, подключаемых через внешние процедуры. Внешние процедуры — это написанные на языке С функции, хранящиеся в разделяемой библиотеке и вызываемые из PL/SQL. Эти внешние процедуры работают в отдельном адресном пространстве, а не в адресном пространстве сервера Oracle. Если необходимо фильтровать документы с помощью компонента interMedia Text, но поддержка внешних процедур не сконфигурирована на сервере надлежащим образом, возможно получение одного или нескольких сообщений об ошибках*: ORA-29855: error occurred in the execution of ODCIINDEXCREATE routine ORA-29855: возникла ошибка при выполнении программы ODCIINDEXCREATE ORA-20000: interMedia text error: DRG-50704: Net8 listener is not running or cannot start external procedures ORA-28575: unable to open RPC connection to external procedure agent ORA-28575: невозможно открыть соединение RPC с агентом внешней процедуры Исчерпывающую информацию о конфигурировании сервера для поддержки внешних процедур можно найти в руководстве Net8 Administrator's Guide, но вот очень простой и быстрый способ проверить, работают ли внешние процедуры. Организовав с сервера базы данных (или из окна командной строки, если сервер работает на платформе Microsoft Windows) сеанс telnet, выполните команду tnsping extproc_connection_data. Если в результате вы не получите ответ ОК, как показано ниже: oracle8i@cmh:/> tnsping extproc_connection_data TNS Ping Utility for Solaris: Version 8.1.7.0.0 - Production on 30-MAR2001 13:46:59 (c) Copyright 1997 Oracle Corporation. All rights reserved. Attempting to contact (ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)) OK (100 msec) значит, поддержка внешних процедур сконфигурирована неправильно. * Представлены также тексты сообщений об ошибках, выдаваемые на русском языке сервером Oracle 8.1.6.0.0. Прим. научн. ред.

interMedia Внешние процедуры не нужны для фильтрования с помощью interMedia Text в версии Oracle 8.1.7. Процесс фильтрования теперь поддерживается самим сервером.

Дальнейшее развитие С выходом Oracle 9i название компонента interMedia Text снова изменилось — теперь соответствующий компонент называется Oracle Text. Все функциональные возможности, которые были в версиях interMedia Text для Oracle 8i, будут поддерживаться и в Oracle Text, но в версию Oracle 9i добавлен ряд новых полезных возможностей. Одной из наиболее желательных возможностей для компонента interMedia Text была автоматическая классификация документов по содержанию. Компонент interMedia Text давал разработчику приложений все необходимые средства для тематического анализа и создания резюме документов, на основе которых можно было их классифицировать. В Oracle Text возможность классификации документов просто встроена. Она помогает создавать системы, позволяющие определить, каким запросам будет соответствовать документ. В компоненте Oracle Text также расширена встроенная поддержка XML-документов. Кроме собственно содержимого, в XML-документ входят структурированные метаданные. Между элементами XML-документа имеются неявные взаимосвязи, и для выражения этих взаимосвязей можно использовать структурированные метаданные. Спецификация XPath, рекомендованная консорциумом W3C (http://www.w3.org/TR/xpath), предлагает способ получения элементов XML-документа на основе содержимого и относительной структуры этих элементов. Компонент Oracle Text включает новый оператор ХРАТН, позволяющий создавать SQL-запросы на основе структурных элементов документа. Это лишь некоторые из новых возможностей, появившихся в компоненте interMedia Text/Oracle Text с выходом сервера версии Oracle 9i.

Резюме В этой главе мы рассмотрели богатый набор функциональных возможностей компонента interMedia Text, а также способы их использования в разнообразных приложениях. Хотя в этой главе описаны многие особенности и средства interMedia Text, гораздо больше осталось за рамками обсуждения. Можно использовать тезаурус, задавать специализированный лексический анализатор, генерировать HTML-представление для всех документов, независимо от их исходного формата, сохранять запросы для дальнейшего использования и даже создавать собственные списки стоп-слов. Компонент interMedia Text — обширная тема для обсуждения, его невозможно описать в одной главе. Наряду с богатством возможностей компонент interMedia Text отличается простой использования и понятностью. После прочтения этой главы у вас должно сложиться четкое понимание того, как реализован компонент interMedia Text и как его использовать в приложениях.

Внешние процедуры на языке С Вначале в качестве языка программирования сервера Oracle и клиентских приложений, работающих на самом сервере Oracle, использовался исключительно PL/SQL. В версии Oracle 8.0 появилась возможность создавать хранимые процедуры на других языках. Эта возможность — поддержка внешних процедур — распространяется на хранимые процедуры на языке С (и все процедуры, которые можно вызвать из языка С) и на языке Java. В этой главе мы сконцентрируемся исключительно на языке С, а следующая глава посвящена использованию Java. В этой главе внешние процедуры рассматриваются с точки зрения архитектуры и показано, как они были реализованы разработчиками ядра Oracle. Кроме того, мы разберемся, как сконфигурировать сервер для поддержки внешних процедур и как должен конфигурироваться сервер с учетом защиты. Я продемонстрирую, как написать внешнюю процедуру с помощью прекомпилятора Oracle Pro*C. Эта внешняя процедура будет использоваться для записи содержимого любого большого объекта в файловую систему сервера. Однако прежде чем перейти к этому примеру, рассмотрим базовый пример, демонстрирующий, как передавать значения основных типов данных из языка PL/SQL в функции на языке С и получать результаты. Этот базовый пример позволит также создать шаблон для быстрой разработки всех последующих внешних процедур на языке С. Вы узнаете, как создавать код на языке С с учетом возможности использования его для выполнения сервером Oracle. Мы также рассмотрим реализацию SQL-оболочки для внешней процедуры. Я расскажу, как обеспечить возможность ее вызова из языков SQL и PL/SQL и как программировать оболочку, чтобы ее легко было использовать тем, кому нужен соответствующий код. Наконец, мы рассмотрим преимущества и недостатки вне Глава шних процедур, а также различные ошибки сервера (ошибки ORA-XXXX), которые могут возникнуть при их использовании. В примере на языке Рго*С будет создаваться недостающее средство сервера. В составе сервера Oracle поставляется пакет DBMS_LOB для работы с большими объектами. В этом пакете есть процедура loadfromfile, позволяющая читать в большой объект базы данных содержимое любого файла в файловой системе сервера. Однако в этом пакете нет функции writetofile, которая могла бы записывать в файловую систему содержимое большого объекта, а такая потребность часто возникает. Мы решим эту задачу, создав собственный пакет LOB_IO. Этот пакет позволит записывать любой большой объект типа BLOB, CLOB или BFILE в отдельный файл вне базы данных (так что для объектов типа BFILE мы по сути создадим команду копирования, поскольку исходный объект BFILE уже находится вне базы данных).

Когда используются внешние процедуры?

Один язык или одна среда не может обеспечить средства и функции на все случаи жизни. У каждого языка есть недостатки: не все можно сделать с его помощью, недостает возможностей или средств, о которых разработчики не подумали. При разработке программ на языке С мне иногда приходится программировать на ассемблере. При программировании приложений для Oracle на Java иногда удобно использовать код на языке PL/SQL. Суть в том, что не нужно всегда использовать язык "низкого уровня" — иногда необходимо переходить на более высокий уровень. Внешние процедуры можно считать переходом на более "низкоуровневый" язык. Они обычно используются для интеграции существующего кода на языке С в виде библиотек функций (например, DLL — динамически компонуемых библиотек в Windows, созданных сторонним производителем, которые необходимо вызывать с сервера) или для расширения функциональных возможностей существующих пакетов, как в нашем случае. Именно эту технологию используют разработчики сервера Oracle для расширения его возможностей. Например, мы уже рассмотрели, как эта возможность использовалась в компоненте interMedia (в предыдущей главе) и в пакете DBMS_OLAP (в главе 13, посвященной материализованным представлениям). Первая хранимая процедура, которую я написал, представляла собой реализацию простого клиента TCP/IP. С ее помощью в версии сервера 8.0.3 я получил возможность в PL/SQL открывать сокет TCP/IP для подключения к существующему серверу и обмена сообщениями с ним. Я мог подключаться к серверу дискуссионных групп по протоколу Net News Transport Protocol (NNTP), к серверу электронной почты по протоколам Internet Message Access Protocol (IMAP), Simple Mail Transfer Protocol (SMTP) или Post Office Protocol (POP), к Web-серверу и т.д. "Научив" язык PL/SQL использовать сокеты, я открыл широкий спектр новых возможностей. Теперь я мог: • посылать сообщение электронной почты из триггера с помощью протокола SMTP;

• включать сообщения электронной почты в базу данных с помощью протокола POP;

Внешние процедуры на языке С • индексировать сообщения дискуссионных групп с помощью компонента interMedia Text и протокола NNTP;

• обращаться к любой доступной сетевой службе. Я стал использовать сервер Oracle также в качестве клиента прочих серверов. После включения полученных от них данных в свою базу я мог выполнять с ними множество действий (индексировать, выполнять поиск, представлять в другом виде и т.д.). Со временем это средство начали использовать настолько часто, что теперь оно стало интегрированной возможностью сервера. Начиная с версии 8.1.6 сервера Oracle, все возможности, которые обеспечивал простой клиент TCP/IP, теперь реализуются в пакете UTL_TCP. С тех пор я написал еще несколько внешних процедур. Одни — для получения времени с помощью системных часов с большей точностью, чем возвращает встроенная функция SYSDATE, другие — для выполнения команд операционной системы, определения часового пояса системы или для получения списка файлов в указанном каталоге. Последней мной написана функция для записи во внешний файл содержимого любого большого объекта: символьного (Character LOB — CLOB), двоичного (Binary LOB — BLOB) или хранящегося во внешнем файле (BFILE). Полезным побочным эффектом созданного при этом пакета является обеспечение для двоичных файлов возможностей, предоставляемых пакетом UTL_FILE (пакет UTL_FILE не позволяет создавать двоичные файлы). Поскольку сервер поддерживает временные большие объекты (Temporary LOB) и обеспечивает возможность записи (WRITE) во временный большой объект, этот новый пакет, который мы собираемся реализовать, даст возможность записывать из PL/SQL любой двоичный файл. Итак, этот пакет позволит: • экспортировать любой большой объект во внешний файл на сервере;

• записывать на сервере двоичный файл практически любого размера и с любыми данными (аналогично пакету UTL_FILE, который работает с текстовыми данными, но работу с произвольными двоичными данными не поддерживает). Из всего сказанного ясно, что причины использования внешних процедур могут быть многочисленны и разнообразны. Обычно они используются для: • реализации отсутствующих функциональных возможностей;

• интегрирования существующего кода, который проверяет корректность данных;

• ускорения обработки;

скомпилированный код на языке С всегда будет выполнять операции, требующие большого объема вычислений, быстрее, чем при реализации их на интерпретируемых языках PL/SQL или Java. Как обычно, решение использовать что-то вроде хранимых процедур требует определенных издержек. Дополнительные издержки связаны с разработкой кода на языке С, что, как мне кажется, сложнее, чем разработка на PL/SQL. Приходится также жертвовать переносимостью или даже идти на потенциальную невозможность переноса кода. Если разработана DLL-библиотека для Windows, нет гарантии, что написанный исходный код можно будет использовать на UNIX-машине, и наоборот. Я считаю, что внешнюю процедуру надо использовать лишь тогда, когда невозможно решить задачу с помощью языка PL/SQL.

Глава Как реализована поддержка внешних процедур?

Внешние процедуры выполняются процессом, физически отделенным от процессов сервера. Это сделано из соображений защиты. Хотя технически можно динамически загружать DLL-библиотеку (в Windows) или файл.so (Shared Object code — разделяемый объектный код, скажем, в Solaris) во время выполнения и в существующих процессах сервера, при этом сервер будет подвергаться неоправданному риску. Код библиотеки будет иметь доступ к тому же пространству памяти, что и процессы сервера, в том числе и к системной глобальной области Oracle (SGA). В результате этот посторонний код может случайно повредить базовые структуры данных СУБД, что приведет к потере данных или сбою экземпляра. Чтобы избежать этого, внешние процедуры выполняются отдельными процессами, не использующими разделяемые области памяти сервера. В большинстве случаев отдельный процесс будет работать от имени пользователя, не являющегося владельцем программного обеспечения Oracle. Причина та же, что и в случае выполнения внешних процедур отдельным процессом — безопасность. Пусть, например, мы собираемся создать внешнюю процедуру, которая сможет записывать файлы на диск (как это делает процедура, рассматриваемая далее). Предположим, сервер работает в среде UNIX, и внешняя процедура выполняется от имени владельца программного обеспечения Oracle. Пользователь вызывает новую функцию и "просит" ее записать объект BLOB в файл /d0l/oracle/data/system.dbf. Поскольку этот код выполняется от имени пользователя — владельца программного обеспечения Oracle, этот вызов сработает и непреднамеренно запишет содержимое какого-то большого двоичного объекта вместо системного табличного пространства. Мы можем этого даже и не заметить до момента остановки и перезапуска сервера (много дней спустя). Если бы внешняя процедура выполнялась от имени менее привилегированного пользователя, это не могло бы случиться (этот пользователь не имел бы права на запись файла system.dbf) Поэтому в разделе, посвященном конфигурированию сервера для поддержки внешних процедур, мы рассмотрим, как настроить безопасный процесс прослушивания EXTPROC (EXTemal PROCedure), работающий от имени другой учетной записи ОС. Причины такой настройки примерно те же, что и в случае запуска Web-серверов от имени пользователя nobody в UNIX или от имени учетной записи с минимальными привилегиями в Windows. Итак, при вызове внешней процедуры сервер Oracle будет автоматически создавать процесс ОС под названием EXTPROC. Для этого он связывается с процессом прослушивания Net8 (Net8 listener). Процесс прослушивания Net8 будет автоматически создавать процесс EXTPROC точно так же, как он порождает выделенные или разделяемые серверы. В среде Windows NT это можно увидеть с помощью утилиты tlist из набора NT Resource Toolkit, выдающей дерево процессов и подпроцессов. Например, я запустил сеанс, из которого обратился к внешней процедуре, а затем выполнил команду tlist -t и получил следующее: C:\bin>tlist -t System Process (0) System (8) smss.exe (140) csrss.exe (164) Внешние процедуры на языке С winlogon.exe (160) services.exe (212) svchost.exe (384) SPOOLSV.EXE (412) svchost.exe (444) regsvc.exe (512) stisvc.exe (600) ORACLE.EXE (1024) ORADIM.EXE (1264) TNSLSNR.EXE (1188) EXTPROC.EXE (972) lasee(2) ss.x 24 Это показывает, что процесс TNSLSNR.EXE является родительским для процесса EXTPROC.EXE. Процесс EXTPROC и процесс сервера теперь могут взаимодействовать. Что еще важнее, процесс EXTPROC может динамически загружать пользовательские DLL-библиотеки (или файлы.so/.sl/.a в ОС UNIX). Архитектурно это выглядит следующим образом:

Происходит следующее. 1. Пользовать подключается к СУБД. При этом либо запускается процесс выделенного сервера, либо используется один из разделяемых серверных процессов. 2. Пользователь вызывает внешнюю процедуру. Поскольку это первый вызов, серверный процесс связывается с процессом TNSLISTENER (процессом прослушивания Net8). 3. Процесс прослушивания Net8 запускает (или находит в пуле запущенных свободный) процесс выполнения внешних процедур для сеанса. Этот процесс загружает запрошенную DLL-библиотеку (или файл.so/.sl/.a в ОС UNIX). 4. Теперь можно взаимодействовать с процессом выполнения внешних процедур, который будет обеспечивать обмен данными между языками SQL и С.

Конфигурирование сервера Сейчас я опишу настройку реквизитов, которую необходимо провести, чтобы обеспечить выполнение внешних процедур. Для этого придется настраивать файлы Глава LISTENER.ORA и TNSNAMES.ORA на сервере, а не на клиентской машине. После полной установки эти файлы должны быть автоматически сконфигурированы для поддержки служб внешних процедур (EXTPROC). В этом случае конфигурационный файл LISTENER.ORA будет иметь примерно такой вид: # LISTENER.ORA. Network Configuration File: # С:\oracle\network\admin\LISTENER.ORA # Generated by Oracle configuration tools. LISTENER = (DESCRIPTION_LIST = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = tkyte-del)(TORT = 1521)) ) (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1)) ) ) ) SID_LIST_LISTENER = (SID_LIST = (SID_DESC (SID_NAME = PLSExtProc) (ORACLE_HOME = C:\oracle) (PROGRAM = extproc) ) (SID_DESC = (GLOBAL_DBNAME = tkyte816) (ORACLE_HOME = C:\oracle) (SID_NAME = tkyte816) ) ) Следующие установки в файле процесса прослушивания существенны для использования внешних процедур. • (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1)). Задает IPC-адрес. Запомните значение KEY. Это значение может быть произвольным, просто запомните его. В некоторых системах регистр символов в значении KEY существенен, что тоже необходимо учитывать. • (SID_DESC = (SID_NAME = PLSExtProc,). Запомните значение SID_NAME, PLSExtProc или что-то подобное. По умолчанию это значение SID будет равно PLSExtProc. Файл LISTENER.ORA можно сконфигурировать и вручную с помощью простого текстового редактора или с помощью программы Net8 Assistant. Настоятельно рекомендуется использовать программу Net8 Assistant, поскольку минимальная ошибка в конфигурационном файле, например незакрытая круглая скобка, сделает его бесполезным. При использовании Net8 Assistant следуйте процедуре, описанной в системе оператив Внешние процедуры на языке С ной справки в разделе NetAssistantHelp, Local, Listeners, How To..., и Configure External Procedures for the Listener. После изменения файла LISTENER.ORA не забудьте остановить и запустить процесс прослушивания с помощью команд lsnrctl stop и lsnrctl start из командной строки. Следующий конфигурационный файл — TNSNAMES.ORA. Этот файл должен находиться в каталоге, который сервер будет использовать при разрешении имен. Обычно файл TNSNAMES.ORA находится на клиенте и используется для поиска сервера. В данном случае сервер сам должен найти службу по имени. Файл TNSNAMES.ORA будет иметь примерно такой вид: # TNSNAMES.ORA Network Configuration # File:С:\oracle\network\admin\TNSNAMES.ORA # Generated by Oracle configuration t o o l s.

EXTPROC_CONNECTION_DATA = (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROCl)) ) (CONNECT_DATA = (SID = PLSExtProc) (PRESENTATION = RO) ) ) В этом конфигурационном файле существенно следующее. • EXTPROC_CONNECTION_DATA. Имя службы, которую будет искать сервер. Это имя использовать обязательно. Далее будет рассмотрена проблема, связанная с установкой параметра names.default_domain в конфигурационном файле SQLNET.ORA.

• (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1)). Этот параметр должен быть таким же, как в файле LISTENER.ORA. В частности, значения компонента KEY = должны совпадать. • (CONNECT_DATA =(SID = PLSExtProc). Значение после SID = должно соответствовать значению SID в конструкции (SID_DESC = (SID_NAME = PLSExtProc) в файле LISTENER.ORA.

С именем EXTPROC_CONNECTION_DATA связана следующая проблема. Если в файле SQLNET.ORA задан стандартный домен, он должен быть включен в запись TNSNAMES. Так что, если в файле SQLNET.ORA есть следующая установка: names.default_domain = world необходимо задать в файле TNSNAMES.ORA значение EXTPROC_CONNECTION_DATA.world, а не просто EXTPROC_CONNECTION_DATA. Любые ошибки в представленных выше конфигурационных файлах почти наверняка приведут к выдаче сообщения об ошибке ORA-28575, представленного ниже: dcae elr Глава ERROR at line 1: ORA-28575: unable to open RFC connection to external procedure agent ORA-06512: at "USERNAME.PROCEDURE_NAME", line 0 ORA-06512: at line 5 При получении этого сообщения об ошибке имеет смысл проверить следующее: • доступна ли программа extproc и является ли она выполняемой;

• правильно ли настроена среда сервера для использования внешних процедур;

Pages:     | 1 |   ...   | 13 | 14 || 16 | 17 |   ...   | 24 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.