Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва Х Санкт-Петербург Х Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...
-- [ Страница 21 ] --PL/SQL procedure successfully completed. Итак, я загрузил два файла. Один из них Ч сценарий, над которым я сейчас работаю, clean.sql. Другой Ч файл экспорта expdat.dmp, подвернувшийся под руку. Теперь я собираюсь написать функцию, которую можно будет вызывать в SQL-операторах и позволяющую просматривать любой 4000-байтовый фрагмент данных типа BLOB в среде SQL*Plus. Просматривать можно не более 4000 байт, поскольку именно такое ограничение в SQL налагается на размер данных типа VARCHAR2. Представленная ниже функция CLEAN работает аналогично функции SUBSTR для обычной строки, но принимает параметр типа BLOB и необязательные параметры FROM_BYTE и FOR_BYTES. Это позволяет выбирать и выдавать подстроку объекта типа BLOB. Обратите внимание, как используется функция UTL_RAW.CAST_TO_VARCHAR2 для преобразования типа RAW в тип VARCHAR2. Если не использовать эту функцию, байты данных типа RAW перед помещением в переменную типа VARCHAR2 будут преобразовываться в шестнадцатеричное представление. С помощью этой функции мы просто меняем тип данных с RAW на VARCHAR2, не выполняя никаких преобразований:
Приложение А scott@DEV816> create or replace 2 function clean(p_raw in blob, 3 p_from_byte in number default 1, 4 p_for_bytes in number default 4000) 5 return varchar2 6 as 7 l_tmp varchar2(8192) default 8 utl_raw.cast_to_varchar2( 9 dbms_lob.substr(p_raw,p_for_bytes,p_from_byte) 10 );
11 l_char char(1);
12 l_return varchar2(16384);
13 l_whitespace varchar2(25) default 14 chr(13) || chr(10) || chr(9);
15 l_ws_char varchar2(50) default 16 'rnt';
17 18 begin 19 for i in 1.. length(l_tmp) 20 loop 21 l_char := substr(l_tmp, i, 1 ) ;
22 23 Ч Если символ Ч "печатный" (а не управляющий), ничего с ним 24 Ч делать не надо. Если это \, добавить еще один символ 25 Ч \, поскольку мы будем заменять символы новой строки и 26 Ч табуляции последовательностями \n, \t и т.д., поэтому 27 Ч надо различать в файле текст \n и символ новой строки. 28 29 if (ascii(l_char) between 32 and 127) 30 then 31 l_return := l_return || l_char;
32 if (l_char = '\' ) then 33 l_return := l_return || '\';
34 end if;
35 36 Ч Если символ Ч пробельный, заменить его 37 Ч специальным символом типа \r, \n, \t 38 39 elsif (instr(l_whitespace, l_char) > 0) 40 then 41 l_return := l_return || 42 '\' || 43 substr(l_ws_char, instr(l_whitespace,l_char), 1);
44 45 Ч Вместо всех остальных непечатных символов 46 Ч просто выдать точку ('.'). 47 48 else 49 l_return := l_return || '.';
50 end if;
51 end loop;
Пакет DBMS_LOB 53 Ч Теперь надо вернуть первые 4000 байт, поскольку больше 54 Ч язык SQL все равно не позволит увидеть. После обработки в 55 Ч строке может оказаться более 4000 символов, поскольку CHR(10) 56 Ч превратится в \n (используется два байта) и т.д., т.е. это 57 Ч необходимо. 58 return substr(l_return,l,4000);
59 end;
60 / Function created. scott@DEV816> select id, 2 dbms_lob.getlength(theBlob) len, 3 clean(theBlob,30,40) piece, 4 dbms_lob.substr(theBlob,40,30) raw_data 5 from demo;
ID LEN PIECE RAW_DATA 1 3498 \ndrop sequence 0A64726F702073657175656E636520 blob_seq;
\n\ncreate 626C6F625F7365713B0A0A63726561 table d 7465207461626C652064 2 2048 TE\nRTABLES\nl024\nO 54450A525441424C45530A31303234 \n28\n4000\n 0A300A32380A343030300A0001001F 00010001000000000000 Как видите, теперь можно просматривать текстовые части данных типа BLOB в среде SQL*Plus, как обычный текст, воспользовавшись функцией CLEAN. Если использовать функцию DBMS_LOB.SUBSTR, возвращающую значение типа RAW, мы получим результат в шестнадцатиричном виде. Просматривая шестнадцатиричное представление, можно убедиться, что первый байт первого объекта типа BLOB имеет значение 0A, или CHR(10) Ч это символ новой строки. В текстовом представлении большого объекта можно увидеть, что функция CLEAN преобразовала 0A в \n (символ новой строки). Это подтверждает, что функция выполнена, как предполагалось. Во втором объекте типа BLOB мы видим много двоичных нулей (значений 00 в шестнадцатиричном представлении) в обычном представлении содержимого файла expdat.dmp. В функции CLEAN, как видите, они преобразуются в точки, поскольку подобные специальные символы, при выдаче непосредственно на терминал, будут выдаваться в нераспознаваемом виде (как мусор). Помимо функции CAST_TO_VARCHAR2 пакет UTL_RAW содержит функцию CAST_TO_RAW. Как было показано ранее, в объект типа BLOB можно поместить обычный текст. Если для изменения этих данных надо использовать строки, пришлось бы преобразовывать их в шестнадцатиричный вид. Например, следующий оператор: scott6DEV816> update demo 2 set theBlob = 'Hello World' 3 where id - 1 4/ set theBlob = 'Hello World' * ERROR at line 2: ORA-01465: invalid hex number Приложение А не работает. При неявном преобразовании данных из типа VARCHAR2 в RAW предполагается, что строка Hello World состоит из шестнадцатиричных цифр. Сервер Oracle берет первые два байта, преобразует их из шестнадцатиричного в десятичный вид и присваивает полученное значение первому байту данных типа RAW, и т.д. Надо либо преобразовать строку Hello World в шестнадцатиричный вид, либо изменить тип данных с VARCHAR2 на RAW Ч изменить только тип данных, не меняя сами байты данных. Например: scott@DEV816> update demo 2 set theBlob = utl_raw.cast_to_raw('Hello World') 3 where id = 1 4 / 1 row updated. scott@DEV816> commit;
Commit complete. scott@DEV816> select id, 2 dbms_lob.getlength(theBlob) len, 3 clean(theBlob) piece, 4 dbms_lob.substr(theBlob,40,l) raw_data 5 from demo 6 where id =1;
ID LEN PIECE RAW_DATA 1 11 Hello World 48656C6C6F20576F726C Использование UTL_RAW.CAST_TO_RAW('Hello World') обычно намного проще преобразования строки Hello World в шестнадцатиричное представление 48656C6C6F20576F726C64.
Преобразование данных типа LONG/LONG RAW в большой объект Преобразовать данные типа LONG или LONG RAW в большой объект очень просто. Стандартная функция TO_LOB языка SQL позволяет это сделать. Использование функции TO_LOB, однако, весьма ограничено. Ее можно применять исключительно в операторах INSERT или CREATE TABLE AS SELECT и только в языке SQL (но не в PL/ SQL). В результате первого ограничения нельзя выполнять операторы, подобные следующему: alter table t add column clob_column;
update t set clob_column = to_lob(long_column);
alter table t drop column long_column;
При попытке выполнения UPDATE будет получено сообщение об ошибке: ORA-00932: inconsistent datatypes Пакет DBMS_LOB Для множественного преобразования типа в существующих таблицах со столбцами LONG/LONG RAW придется создавать новую таблицу. В большинстве случаев это вполне допустимо, поскольку данные типа LONG и LONG RAW хранятся в самой строке (inline) вместе с остальными данными таблицы. Если преобразовать их в большие объекты, а затем удалить столбец типа LONG, таблица окажется не в лучшем виде: будет много выделенного и неиспользуемого пространства. Такие таблицы лучше пересоздавать. Второе ограничение означает, что функцию TO_LOB нельзя использовать в PL/SQLблоке. Чтобы использовать TO_LOB в PL/SQL, придется прибегнуть к динамическому SQL. Вскоре я это продемонстрирую. В следующих примерах мы рассмотрим два способа использования функции TO_LOB. Один из них Ч использование функции TO_LOB в операторе CREATE TABLE AS SELECT или INSERT INTO. Другой способ пригодится, когда данные должны остаться в столбце типа LONG или LONG RAW. Например, старому приложению нужен именно тип LONG. Хотелось бы предоставить другим приложениям возможность работать с этим столбцом как с большим объектом, чтобы можно было обрабатывать его значение в PL/SQL по частям с помощью функций пакета DBMS_LOB, например READ и SUBSTR. Начнем с создания данных типа LONG и LONG RAW: ops$tkyte@DEV816> c r e a t e t a b l e long_table 2 (id int primary key, 3 data long 4) 5/ Table created. ops$tkyte@DEV816> create table long_raw_table 2 (id int primary key, 3 data long raw 4) 5/ Table created. ops$tkyte@DEV816> declare 2 l_tmp long := 'Hello World';
3 l_raw long raw;
4 begin 5 while(length(l_tmp) < 32000) 6 loop 7 l_tmp := l_tmp || ' Hello World';
8 end loop;
9 10 insert into long_table 11 (id, data) values 12 (1, l_tmp);
13 14 l_raw := utl_raw.cast_to_raw(l_tmp);
15 16 insert into long_raw_table 17 (id, data) values Приложение А 18 (1, l_raw);
19 20 dbms_output.put_line('created long with length = ' || 21 length(l_tmp));
22 end;
23 / created long with length = 32003 PL/SQL procedure successfully completed.
Пример множественного однократного преобразования типа Итак, имеется две таблицы с одной строкой и столбцом типа LONG или LONG RAW. Преобразование типа данных из LONG в CLOB легко выполнить с помощью следующего оператора CREATE TABLE AS SELECT: ops$tkyte@DEV816> create table clob_table 2 as 3 select id, to_lob(data) data 4 from long_table;
Table created. Кроме того, мы могли создать таблицы ранее и использовать для наполнения ее данными разновидность оператора INSERT INTO: ops$tkyte@DEV816> insert into clob_table 2 select id, to_lob(data) 3 from long_table;
1 row created. Следующий пример показывает, что функция TO_LOB не работает в PL/SQL-блоке, как и следовало ожидать: ops$tkyte@DEV816> begin 2 insert into clob_table 3 select id, to_lob(data) 4 from long_table;
5 end;
6/ begin * ERROR at line 1: ORA-06550: line 3, column 16: PLS-00201: identifier 'TO_LOB' must be declared ORA-06550: line 2, column 5: PL/SQL: SQL Statement ignored Это ограничение легко обойти с помощью динамического SQL (придется выполнять оператор INSERT динамически, а не статически, как в примере выше). Теперь, разобравшись, как преобразовывать данные типа LONG или LONG RAW в тип CLOB или BLOB, рассмотрим производительность такого преобразования. Обычно таблицы со Пакет DBMS LOB столбцами типа LONG и LONG RAW Ч большого размера. Они большие по определению, поскольку используются для хранения очень больших объектов. Во многих случаях их размер достигает многих гигабайт. Вопрос в том, можно ли выполнить множественное преобразование за допустимое время? Рекомендую использовать следующие возможности: Х невосстановимые действия, такие как непосредственная вставка и опция NOLOGGING;
Х распараллеливание операторов ЯМД (в частности, параллельные вставки);
Х параллельные запросы. Ниже представлен пример использования этих возможностей. У меня есть большая таблица IMAGE, содержащая многие сотни загруженных из Web файлов. Таблица содержит столбцы NAME (название документа), MIME_TYPE (например, application/MSWord), IMG_SIZE (размер документа в байтах) и, наконец, сам документ в столбце типа and LONG RAW. Преобразуем эту таблицу так, чтобы документ хранился в столбце типа BLOB. Можно начать с создания новой таблицы: scott@DEV816> CREATE TABLE "SCOTT"."T" 2 ("NAME" VARCHAR2(255), 3 "MIME_TYPE" VARCHAR2(255), 4 "IMG_SIZE" NUMBER, 5 "IMAGE" BLOB) 6 PCTFREE 0 PCTUSED 40 7 INITRANS 8 MAXTRANS 2 5 5 9 NOLOGGING 10 11 12 13 14 15 16 TABLESPACE "USERS" LOB ("IMAGE") STORE AS (TABLESPACE "USERS" DISABLE STORAGE IN ROW CHUNK 32768 PCTVERSION 10 NOCACHE NOLOGGING );
Table created. Обратите внимание, что таблица и большой объект создаются с опцией NOLOGGING Ч это важно. Можно не создавать их так сразу, а применить оператор ALTER. Теперь, чтобы преобразовать данные из существующей таблицы IMAGE, выполним следующее: scott@DEV816> ALTER SESSION ENABLE PARALLEL DML;
Session a l t e r e d. scott@DEV816> INSERT /*+ APPEND PARALLEL(t,5) */ INTO t 2 SELECT /*+ PARALLEL(long_raw,5) */ 3 name, mime_type, img_size, to_lob(image) 4 F O long_raw;
RM В результате выполняется непосредственная параллельная вставка в объекты типа BLOB без журнализации. Для сравнения я выполнил INSERT INTO c включенной и Приложение А отключенной журнализацией и получил следующие результаты (на подмножестве преобразуемых строк): scott@DEV816> create table t 2 as 3 select name, mime_type, img_size, to_lob(image) image 4 from image where l=0;
Таblе created. scott@DEV816> set autotrace on scott@DEV816> insert into t 2 select name, mime_type, img_size, to_lob(image) image 3 from image;
99 rows created. Execution Plan 0 1 0 INSERT STATEMENT Optimizer=CHOOSE TABLE ACCESS (FULL) OF 'IMAGE' Statistics 1242 36057 12843 7870 34393500 1006 861 4 2 0 recursive calls db block gets consistent gets physical reads redo size bytes sent via SQL*Net to client bytes received via SQL*Net from client SQL*Net roundtrips to/from client sorts (memory) sorts (disk) 99 rows processed Обратите внимание, что в результате было сгенерировано 34 Мбайт данных повторного выполнения (если суммировать размеры 99 изображений, получится 32 Мбайт данных). Если таблица T, как было показано ранее, создается с опцией NOLOGGING и используется непосредственная вставка, получим: scott@DEV816> INSERT /*+ APPEND */ INTO t 2 SELECT name, mime_type, img_size, to_lob(image) 3 FROM image;
99 rows created. Execution Plan 0 1 0 INSERT STATEMENT Optimizer=CHOOSE TABLE ACCESS (FULL) OF 'IMAGE' Statistics 1242 recursive calls 36474 db block gets 13079 consistent gets 6487 physical reads Пакет DBMS_LOB 1355104 1013 871 4 2 0 redo size bytes sent via SQL*Net to client bytes received via SQL*Net from client SQL*Net roundtrips to/from client sorts (memory) sorts (disk) rows processed Сгенерировано лишь около 1 Мбайт информации в журнал. Это преобразование выполняется существенно быстрее, при этом генерируется намного меньше данных в журналы повторного выполнения. Конечно, как и для всех невосстанавливаемых операций, необходимо обеспечить резервное копирование базы данных как можно раньше, чтобы новые объекты можно было восстановить. Иначе в случае сбоя диска преобразование данных придется выполнять заново. Представленный выше пример нельзя повторить непосредственно. У меня случайно под рукой оказалась таблица IMAGE, содержащая около 200 Мбайт данных. Она использовалась для демонстрации множественных однократных преобразований и влияния опции NOLOGGING на объем генерируемых при этом данных повторного выполнения.
Оперативное преобразование типа данных Во многих случаях необходимо читать данные типа LONG или LONG RAW в различных средах, но оказывается, что это не получается. Например, при использовании языка PL/SQL, если объем данных типа LONG RAW превышает 32 Кбайт, их практически невозможно прочитать. В других языках и интерфейсах тоже есть проблемы с данными типа LONG и LONG RAW. C помощью функции TO_LOB и временной таблицы, однако, можно оперативно преобразовать данные типа LONG или LONG RAW в тип CLOB или BLOB. Это очень удобно, например, при использовании средств загрузки файлов в OAS4.x или WebDB. Эти средства загружают документы по сети (через Web) в таблицу базы данных, но, к сожалению, загружают они их в столбец типа LONG RAW. Это делает практически невозможной работу с документами в PL/SQL. Представленные ниже функции показывают, как обеспечить прозрачный доступ к таким данным через промежуточный BLOB-объект. Начнем с создания временной таблицы для хранения преобразованного объекта типа CLOB/BLOB и последовательности, идентифицирующей строку: ops$tkyte@DEV816> create global temporary table lob_temp 2 (id int primary key, 3 c_lob clob, 4 b_lob blob 5) 6/ Table created. ops$tkyte@DEV816> create sequence lob_temp_seq;
Sequence created.
Приложение А Теперь создадим функции TO_BLOB и TO_CLOB. Эти функции используют для оперативного преобразования данных типа LONG или LONG RAW следующий подход. Х Пользователь выбирает идентификатор строки из таблицы со столбцом типа LONG или LONG RAW, а не значение столбца LONG или LONG RAW в этой строке. Функции передается имя столбца типа LONG, имя таблицы и идентификатор нужной строки. Х Функция получает последовательный номер, идентифицирующий строку, которая будет создаваться во временной таблице. Х С помощью динамического SQL к указанному столбцу типа LONG или LONG RAW применяется функция TO_LOB. Использование динамического SQL не только делает функцию универсальной (она может работать со столбцом типа LONG в любой таблице), но и позволяет непосредственно вызывать функцию TO_LOB в языке PLSQL. Х Функция считывает из временной значение созданного объекта типа BLOB или CLOB таблицы и возвращает вызывающему. Вот код для функций TO_BLOB и TO_CLOB: ops$tkyte@DEV816> create or replace 2 function to_blob(p_cname in varchar2, 3 p_tname in varchar2, 4 p_rowid in rowid) return blob 5 as 6 l_blob blob;
7 l_id int;
8 begin 9 select lob_temp_seq.nextval into l_id from dual;
10 11 execute immediate 12 'insert into lob_temp (id,b_lob) 13 select :id, to_lob(' || p_cname || ') 14 from ' || p_tname || 15 ' where rowid = :rid ' 16 using IN l_id, IN p_rowid;
17 18 select b_lob into l_blob from lob_temp where id - l_id ;
19 20 return l_blob;
21 end;
22 / Function created. ops$tkyte@DEV816> create or replace 2 function to_clob(p_cname in varchar2, 3 p_tname in varchar2, 4 p_rowid in rowid) return clob 5 as 6 l_clob clob;
7 l_id int;
Пакет DBMS_LOB 8 begin 9 select lob_temp_seq.nextval into l_id from dual;
10 11 execute immediate 12 'insert into lob_temp (id,c_lob) 13 select :id, to_lob(' || p_cname || ') 14 from ' || p_tname || 15 ' where rowid = :rid ' 16 using IN l_id, IN p_rowid;
17 18 select c_lob into l_clob from lob_temp where id = l_id ;
19 20 return l_clob;
21 end;
22 / Function created. Теперь можно продемонстрировать использование этих функций с помощью простого PL/SQL-блока. Данные типа LONG RAW в BLOB будут преобразованы, и выдана длина полученного объекта и небольшая часть его данных: ops$tkyte@DEV816> declare 2 l_blob blob;
3 l_rowid rowid;
4 begin 5 select rowid into l_rowid from long_raw_table;
6 l_blob :=to_blob('data', 'long_raw_table', l_rowid);
7 dbms_output.put_line(dbms_lob.getlength(l_blob));
8 dbms_output.put_line( 9 utl_raw.cast_to_varchar2( 10 dbms_lob.substr(l_blob,41/l) 11 ) 12 );
13 end;
14 / 32003 Hello World Hello World Hello World Hello PL/SQL procedure successfully completed. Для тестирования функции TO_CLOB применяется практически такой же код, но использовать средства пакета UTL_RAW не нужно: ops$tkyte@DEV816> declare 2 l_clob clob;
3 l_rowid rowid;
4 begin 5 select rowid into l_rowid from long_table;
6 l_clob :=to_clob('data', 'long_table', l_rowid);
7 dbms_output.put_line(dbms_lob.getlength(l_clob));
8 dbms_output.put_line(dbms_lob.substr(l_clob,41,1));
9 end;
10 / Приложение А 32003 Hello World Hello World Hello World Hello PL/SQL procedure successfully completed.
Запись значений объекта типа BLOB/CLOB на диск Этой возможности в пакете DBMS_LOB недостает. Пакет предоставляет средства загрузки больших объектов из файлов, но не создания файла, содержащего большой объект. Решение этой проблемы предложено в главах 18 и 19. Там приведен код на языке С и Java для внешней процедуры, записывающей значение столбца типа BLOB, CLOB в базе данных или временного большого объекта в файл файловой системы сервера. Обе реализации выполняют одну и ту же функцию, просто использованы разные языки. Применяйте ту из них, которая больше подходит для вашего сервера (например, если на сервере не установлена поддержка языка Java, но есть прекомпилятор Pro*C и компилятор языка С, то внешняя процедура на языке С подойдет больше).
Выдача большого объекта на Web-странице с помощью PL/SQL Представленный ниже пример предполагает, что в системе установлены и работают следующие компоненты: Х компонент прослушивания (lightweight listener) WebDB;
Х сервер приложений OAS 2.x, З.х или 4.x с PL/SQL-картриджем;
Х сервер iAS с модулем mod_plsql. При отсутствии любого из этих компонентов пример выполнить не получится. В нем используется набор инструментальных средств PL/SQL Web Toolkit (речь идет о широко известных функциях HTP), а также PL/SQL-картридж или модуль. Предполагается также, что наборы символов (кодировки) на Web-сервере (клиенте сервера базы данных) и в базе данных совпадают. Дело в том, что PL/SQL-картридж или модуль использует для генерации страниц из базы данных тип VARCHAR2. Если набор символов у клиента (в данном случае клиентом является Web-сервер) отличается от набора символов в базе данных, будет выполнено преобразование. При этом обычно повреждаются данные типа BLOB. Предположим, Web-сервер работает на платформе Windows NT. Обычно для клиента на платформе Windows NT используется набор символов WE8ISO8859P1 Ч западноевропейская 8-битовая кодовая страница. А сервер баз данных работает на платформе Solaris. Стандартной и наиболее типичной кодовой страницей на этой платформе является 7-битовая US7ASCII. При попытке передачи значения BLOB через интерфейс VARCHAR2 в случае использования такой пары кодовых страниц окажется, что старший бит данных из базы сброшен. Данные изменятся. Только если кодировки на клиенте (Web-сервере) и сервере базы данных совпадают, данные передаются без искажений.
Пакет DBMS LOB Итак, предполагая, что все предварительные условия выполнены, можно рассмотреть использование средств PL/SQL Web Toolkit для выдачи значения BLOB на Webстранице. Продолжим один из предыдущих примеров преобразования, в котором была создана таблица DEMO. Загрузим в нее еще один файл: ops$tkyte@DEV816> exec load_a_file('MY_FILES', 'demo.gif');
PL/SQL procedure successfully completed. Это будет GIF-файл. Теперь необходим пакет, который сможет выбрать это изображение в формате GIF и выдать его на Web-странице. Он может иметь следующий вид: ops$tkyte@DEV816> create or replace package image_get 2 as 3 Ч Можно задать соответствующее имя процедуры 4 Ч для каждого типа отображаемых документов, 5 Ч например: 6 Ч procedure pdf 7 Ч procedure doc 8 Ч procedure txt 9 Ч и т.д. Некоторые браузеры (MS IE, например) при обработке 10 Ч документов используют расширения имен файлов, 11 Ч а не mime-типы 12 procedure gif(p_id in demo.id%type);
13 end;
14 / Package created. ops$tkyte@DEV816> create or replace package body image_get 2 as 3 4 procedure gif(p_id in demo.id%type) 5 is 6 l_lob blob;
7 l_amt number default 32000;
8 l_off number default 1;
9 l_raw raw(32000);
10 begin 11 12 Ч Получить LOB-локатор для 13 Ч нашего документа. 14 select theBlob into l_lob 15 from demo 16 where id = p_id;
17 18 Ч Выдать mime-заголовок для 19 Ч документа этого типа. 20 owa_util.mime_header('image/gif');
21 22 begin 23 loop 24 dbms_lob.read(l_lob, l_amt, l_off, l_raw);
Приложение А 26 Ч Важно использовать вызов htp.PRN, чтобы избежать 27 Ч добавления в документ ненужных символов 28 Ч перевода строки. 29 htp.prn(utl_raw.cast_to_varchar2(l_raw));
30 l_off := l_off+l_amt;
31 l_amt := 32000;
32 end loop;
33 exception 34 when no_data_found then 35 NULL;
36 end;
37 end;
38 39 end;
40 / Package body created. При наличии DAD (Database Access Descriptor Ч дескриптор доступа к базе данных, который обычно создается при настройке PL/SQL-картриджа или модуля) с именем mydata можно использовать адрес URL для получения изображения. Аргумент P_ID=3 передается процедуре image_get.gif, требующий от нее выдать локатор большого объекта, который хранится в строке со значением id=3. Это изображение можно включить в страницу с помощью тэга IMG: