Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
МУ к лабораторным работам №11 Пакеты.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
131.35 Кб
Скачать

Курсорные переменные и пакеты

Курсорная переменная обычно создается в два этапа. Сначала необходимо определить тип 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 мы рассмотрим, как использовать их для выдачи отладочной информации в реальном времени.