- •Лабораторная работа № 11. Пакеты oracle pl/sql
- •Цель работы
- •2 Методические указания
- •3 Задания к лабораторной работе № 11
- •4. Теоретический материал Создание пакетов
- •Правила построения пакетов
- •Спецификация пакета
- •Указания по разработке пакетов:
- •Правила вызова элементов пакета
- •Удаление пакетов
- •Перегрузка пакетов
- •Порядок разрешения вызова
- •Предварительное объявление
- •Одноразовые процедуры
- •Побочные эффекты
- •Ограничение функции в правах обращения
- •Курсорные переменные и пакеты
Курсорные переменные и пакеты
Курсорная переменная обычно создается в два этапа. Сначала необходимо определить тип REF CURSOR, а затем объявить курсорную переменную этого типа. Определить тип REF CURSOR можно в любом блоке PL/SQL, подпрограмме или пакете. Но объявить курсорную переменную в пакете нельзя. В отличие от пакетной переменной курсорная переменная не имеет устойчивого состояния. (Курсорная переменная создает указатель на элемент вместо самого элемента).
CREATE OR REPLACE PACKAGE emp_data AS -- спецификация
. . .
TYPE emp_cur_typ IS REF CURSOR
RETURN EMPLOYEES%ROWTYPE;
-- определение типа
PROCEDURE use_emp_cv(emp_cv IN OUT emp_cur_typ);
END emp_data;
-- --------------- Тело пакета ------
CREATE OR REPLACE PACKAGE BODY emp_data AS
. . .
PROCEDURE use_emp_cv(emp_cv IN OUT emp_cur_typ)
IS
BEGIN
OPEN emp_cv FOR SELECT * FROM EMPLOYEES;
END use_emp_cv;
END emp_data;
В этом примере открывается курсорная переменная путем передачи ее в пакетную процедуру, которая объявляет курсорную переменную как один из своих формальных параметров. Следует отметить, что формальный параметр имеет тип IN OUT, т.к. это позволяет подпрограмме передать открытый курсор обратно в вызывающую программу.
Другой способ - использовать автономную процедуру. В этом случае тип REF CURSOR должен быть определён в отдельном пакете, а затем нужно обратиться к этому типу в автономной процедуре и открыть курсорную переменную.
Пример: Для упрощения примера мы будем решать задачу, которую можно (и нужно) решать спомощью стандартного SQL — вообще без PL/SQL. Можете предполагать, хотя это и непоказано в примере, что выполняется серьезная обработка, которая требует использовать PL/SQL!
Давайте рассмотрим решение без использования конвейерных функций. Есть три PL/SQL-процесса:
производитель, читающий строки из таблицы ЕМР,
обработчик для изменения зарплаты и даты приема на работу,
потребитель, загружающий измененные строки в таблицу ЕМР2.
Сначала создадим таблицу ЕМР2 ДЛЯ хранения результатов.
SQL> create table EMP2 as
2 select EMPLOYEE_ID as empno, FIRST_NAME as ename, HIRE_DATE as hiredate, SALARY as sal, DEPARTMENT_ID as deptno
3 from EMPLOYEES
4 where rownum = 0;
Table created.
Затем создадим пакет, PKG, который будет содержать процессы потребитель, обработчик и поставщик. Для передачи информации между процессами мы определим набор EMP_LIST для хранения записей о сотрудниках.
SQL> create or replace
2 package PKG is
3 type emp_list is
4 table of emp2%rowtype;
5
6 function CONSUMER return sys_refcursor;
7 function MANIPULATOR(src_rows sys_refcursor) return emp_list;
8 procedure PRODUCER(changed_rows emp_list);
9
10 end;
11 /
Package created.
Реализация очень проста. Функция CONSUMER запрашивает таблицу ЕМР и возвращает курсорную переменную (указатель) для результирующего множества. Функция MANIPULATOR принимает результирующее множество на входе, выбирает записи, изменяет дату приема на работу и зарплату, а затем возвращает измененную переменную типа набора. Функция PRODUCER проходит в цикле по записям в наборе и добавляет эти строки в таблицу ЕМР2.
SQL> create or replace
2 package body PKG is
3
4 function CONSUMER return sys_refcursor is
5 src_rows sys_refcursor;
6 begin
7 open src rows for
8 select EMPLOYEE_ID, FIRST_NAME, HIRE_DATE, SALARY, DEPARTMENT_ID
9 from EMPLOYEES;
10 return src_rows;
11 end;
12
13 function MANIPULATOR (src_rows sys_refcursor) return emp_list is
14 changed_rows emp_list;
15 begin
16 fetch src_rows
17 bulk collect into changed_rows;
18 close src_rows;
19 for i in 1 .. changed_rows.count
loop
20 changed_rows(i).sal := changed_rows(i).sal+10;
21 changed_rows(i).hiredate := changed_rows(i).hiredate + 1;
22 end loop;
23 return changed_rows;
24 end;
25
26 procedure PRODUCER(changed_rows emp_list) is
27 begin
28 forall i in 1 .. changed_rows.count
29 insert into emp2 values changed_rows(1);
30 commit;
31 end;
32
33 end;
34 /
Package body created.
С помощью вызова трех подпрограмм пакета можно получить в итоге полное преобразование данных, при котором мы выбираем данные из таблицы ЕМР, изменяем дату приема на работу и зарплату и сохраняем измененные данные в таблице
ЕМР2.
SQL> set timing on
SQL> exec pkg.producer(pkg.manipulator(pkg.consumer));
PL/SQL procedure successfully completed.
Elapsed: 00:00:08.02
SQL> select count(*) from emp2;
COUNT(*)
Мы обработали и передали 100000 строк из таблицы ЕМР В таблицу ЕМР2 за 8 секунд. Однако цена решения — объем используемой памяти, который можно оценить, обратившись к представлению V$MYSTATS.
SQL> select * from v$mystats
2 where name like '%pga%'
3 /
NAME VALUE
___________________________________
session pga memory 55893380
session pga memory max 61660548
Шестьдесят один мегабайт! Проблема этого подхода очевидна. В соответствии с нашим принципом демонстрируемости давайте посмотрим, что произойдет, когда в таблице ЕМР будет 1000000 строк.
SQL> exec pkg.producer(pkg.manipulator(pkg.consumer));
ERROR at line 1:
ORA-04030: out of process memory when trying to allocate 16396 bytes
(koh-kghu call ,pl/sql vc2)
Сеанс работы с базой данных завершился сбоем (а вскоре после этого то же самое произошло и с ноутбуком), поскольку для хранения набора из 1000000 записей о сотрудниках не хватило оперативной памяти. Самое печальное, что сохранять записи в памяти нам вообще не нужно — нам надо только преобразовывать их "по пути" из ЕМР в ЕМР2.
Вот тут и могут помочь конвейерные функции. Мы изменим процесс так, чтобы использовались конвейерные функции. Конвейерные функции не могут работать с записями PL/SQL — только с объектными типами, так что придется создать обьектные типы-аналоги типа EMP_LIST В решении без конвейерных функций.
SQL>
2 create or replace
3 type emp2rec is object
4 empno number(10),
5 ename varchar2(20) ,
6 hiredate date,
7 sal number(10,2),
8 deptno number(6) );
/
Type created.
SQL> create or replace
2 type emp21ist as table of emp2rec;
3 /
Type created.
Спецификация пакета практически не изменилась. Мы просто указываем, что функция MANIPULATOR — конвейерная. Вместо сбора всего множества записей она может передавать каждую преобразованную запись на следующую стадию обработ ки (в нашем случае — процедуре PRODUCER), как только преобразование закончено
SQL> create or replace
2 package PKG2 is
3 function CONSUMER return sys_refcursor;
4 function MANIPULATOR(src_rows sys_refcursor) return emp21ist
pipelined;
5 procedure PRODUCER;
6
7 end;
8 /
Package created.
Тело пакета немного отличается, поскольку здесь главное — не передавать данные между различными стадиями. Но наша функция CONSUMER не меняется, просто открывая курсорную переменную для исходных данных.
SQL> create or replace
2 package body PKG2 is
4 function CONSUMER return sys_refcursor is
5 src_rows sys_refcursor;
6 begin
7 open src_rows for
8 select EMPLOYEE_ID, FIRST_NAME, HIRE_DATE, SALARY, DEPARTMENT_ID
9 from EMPLOYEES;
10 return src_rows;
11 end;
12
Функция MANIPULATION тоже не сильно отличается от неконвейерной версии, за исключением конструкции PIPE, — измененные строки возвращаются в вызывающую среду в цикле по курсору. Как только строка изменяется, она передается вызывающему.
13 function MANIPULATOR(src_rows sys_refcursor)
14 return emp2 1ist pipelined is
15 r emp2rec;
16 e emp2%rowtype;
17 begin
18 loop
19 fetch src rows into e;
20 exit when src rows%notfound;
21 e.sal := e.sal+10;
22 e.hiredate := e.hiredate + 1;
23 pipe row (emp2rec(e.empno, e.ename, e.hiredate, e.sal, e.deptno ));
24 end loop;
25 close src rows;
26 return;
27 end;
Процедуре PRODUCER надо только открыть курсор на источник данных, передать соответствующий указатель (курсорную переменную) процедуре преобразования, а затем, по мере возврата результатов по конвейеру, загружать их в таблицу ЕМР2.
29 procedure PRODUCER is
30 rc sys_refcursor := consumer;
31 begin
32 insert into emp2
33 select * from table(cast(manipulator(rc) as emp21ist ));
34 end;
35
36 end;
37 /
Package body created.
Конечный результат тот же, поскольку требуемые действия мы не меняли, а раз мы просто передаем элементы набора по мере их получения, а не запоминаем большие наборы, то памяти потребуется намного меньше. Давайте запустим процедуру PRODUCER, а затем снова проверим статистическую информацию об использовании памяти (надо отключиться и подключиться снова для очистки статистической информации уровня сеанса перед выполнением второго теста).
SQL> exec pkg2.producer;
PL/SQL procedure successfully completed.
Elapsed: 00:00:22.02
SQL> select * from v$mystats
2 where name like '%pga%'
3 /
NAME VALUE
--------------------------------------------------------
session pga memory 434676
session pga memory max 434676
Обратите внимание, что памяти требуется намного меньше, и этот показатель не изменится при увеличении количества обрабатываемых строк. Но время выполнения несколько разочаровывает, если вспомнить, что неконвейерная версия процедуры выполнялась всего за 8 секунд. Но теперь мы полностью может контролировать баланс между производительностью и использованием памяти. Например, ничто не мешает использовать средства обработки массивом в функции MANIPULATOR — мы можем использовать немного больше памяти, чтобы повысить производительность.
Вместо выборки и отправки по конвейеру одной строки мы будем выбирать 100 строк перед передачей их потребителю.
13 function MANIPULATOR (src_rows sys_refcursor)
14 return emp2 1ist pipelined is
15 type elist is table of emp2%rowtype;
16 г emp2rec;
17 е elist;
18 begin
19 loop
20 fetch src_rows bulk collect into e limit 100;
21 for i in 1 .. e.count loop
22 e(i).sal := e(i).sal+10;
23 e(i).hiredate := e(i).hiredate + 1;
24 pipe row (emp2rec(
25 e(i).empno,e(i).ename,
26 e(i).hiredate,e(i).sal,e(i).deptno ));
21 end loop;
28 exit when src_rows%notfound;
29 end loop;
30 close src_rows;
31 return;
32 end;
33
SQL> exec pkg2.producer;
PL/SQL procedure successfully completed.
Elapsed: 00:00:09.08
SQL> select * from v$mystats
2 where name like '%pga%'
3 /
NAME VALUE
----------------------------------------------------
session pga memory 1483788
session pga memory max 1549324
Поэтому, просто применяя контролируемую множественную обработку, мы получаем почти такую же производительность, как и у не конвейерной версии, но используем при этом в 50 раз меньше памяти. Давайте увеличим количество строк в таблице ЕМР ДО 1 миллиона и увидим, что ранее существовавшие проблемы с памятью теперь решены.
SQL> exec pkg2.producer;
PL/SQL procedure successfully completed.
SQL> select * from v$mystats
2 where name like '%pga%'
3 /
SQL> select count(*) from emp2;
COUNT(*)
========
999999
На этот раз сеанс не завершился сбоем, но определенный рост объема используемой памяти наблюдается. На момент написания этой главы я не мог определить, с чем он связан.
Еще одно преимущество связано с тем, что, поскольку процедура MANIPULATOR принимает на входе параметр типа REF CURSOR И просто отправляет строки по конвейеру в качестве результата, процедуры PRODUCER И CONSUMER становятся лишними.
Мы можем включить процесс преобразования в стандартный SQL-оператор. Получим код следующего вида:
SQL> insert into emp2
2 select *
3 from table(
4 cast (
5 pkg2.manipulator(
6 cursor( select EMPLOYEE_ID, FIRST_NAME, HIRE_DATE, SALARY, DEPARTMENT_ID from EMPLOYEES))
7 as emp21ist ));
100000 rows created.
Вероятно, самое сложное при работе с конвейерными функциями — найти возможности для их использования в приложении. Любой процесс, предполагающий выдачу потока результатов из функции, можно усовершенствовать с помощью конвейерной функции. В главе 1 мы рассмотрели использование конвейерных функций для эффективного возврата любого количества строк без дополнительных расходов ресурсов на их размещение в реляционной таблице. В главе 10 мы рассмотрим, как использовать их для выдачи отладочной информации в реальном времени.
