Oracle вернуть курсор из функции

Обновлено: 07.07.2024

Церковь была отворена, за оградой стояло несколько саней;
по паперти ходили люди.
«Сюда! сюда!» — закричало несколько голосов .

А. С. Пушкин, «Метель»

Аннотация

Рассматриваются ссылки на курсор, представляющие собой инструмент продвинутого программирования в Oracle. Приводятся примеры работы через ссылки на курсор в программах на PL/SQL и Java и в SQL*Plus.

Ссылки на курсор

В пору моего обучения в техническом ВУЗе люди, как и сечас, были склонны называть себя как угодно, и в народе бытовал критерий настоящего математика. Право им назваться отдавалось тому, кто знал, что такое поле Галуа. Таки не ставши математиком, не берусь судить о корректности этого критерия (сдается, он ребяческий), однако занимаясь Oracle, могу предложить другой критерий профессионализма: для программиста этой СУБД.

В диалекте SQL, придуманном фирмой Oracle, есть много разных конструкций, не всякому известных; например, для аналитические функции, или же средства для работы с рукотворными объектами. Но это вещи специфические, не каждому программисту потребные, а вот ссылки на курсор – явление безусловно общего характера.

  • Они позволяют перенести программную логику на сервер. Клиентские приложения оказываются не так жестко привязаны к конкретным запросам; одни и те же запросы могут вызываться с одинаковым эффектом из программ на PL/SQL, C или Java.
  • Они позволяют перенести вычислительную нагрузку на сервер.

Поскольку принципиально для программирования ссылки на курсоры не нужны, большинство разработчиков обходится обычным способом общения с СУБД. Выгоды же извлекают программисты, которых и можно в данном контексте признать профессионалами.

Ссылки на курсор реализованы в Oracle программно и в SQL. В программе они реализуются в виде специальной переменной, и именно этот вариант показан в этой статье. Клиентскими средами будут выступать PL/SQL, SQL*Plus и Java.

Описание ссылки на курсор и использование в PL/SQL

На каком бы языке вы не общались с БД посредством ссылки на курсор, без программирования на PL/SQL не обойтись. Формальная сторона работы со ссылкой на курсор в PL/SQL обставлена просто.

Во-первых, чтобы завести в PL/SQL переменную-ссылку на курсор, нужно сначала описать ее тип. Это делается в разделе описания с помощью предложения TYPE:

Если конструкция RETURN присутствует, ссылка на курсор называется строгой; если нет – нестрогой. Нестрогая может ссылаться на любой курсор (запрос), а строгая – только на тот, что возвращает результат указанного типа.

Пример описания обоих типов ссылки на курсор:

Открытие курсора с помощью переменной-ссылки на курсор:

Команды FETCH и CLOSE используются как обычно, толькот вместо имени курсора указываем имя ссылки на курсор.

Во-вторых, для удобства программирования поддерживается «системный» тип SYS_REFCURSOR нестрогой ссылки на курсор. Так, в блоке выше, в разделе описания можно было бы не приводить предложение TYPE, а сразу сказать:

Тип SYS_REFCURSOR сокращает текст программы, а иногда позволяет и обойтись без создания служебного пакета (пример чего в этой статье не рассматривается).

Создание пакета в PL/SQL

Приводимые ниже примеры в каждой из трех сред программирования будут использовать для доступа к БД через ссылку на курсор один и тот же пакет. В реальной жизни именно на подобный пакет и ляжет описание требуемой программной логики. Тут же он воимя наглядности устроен максимально просто, (почти) безо всякой программно-прикладной логики, но это обстоятельство и обеспечивает ему универсальность.

Выдадим в SQL*Plus:

В реальной практике такому выхолощенному пакету вполне может найтись свое место, однако часто будут использоваться и другие, общие с приведенным выше только тем, что

(а) в теле будет обязательно присутствовать предложение OPEN

(б) среди параметров должен присутствовать выходной типа ссылки на курсор (возможно строгой).

Остальное регламентируется исключительно логикой приложения и организации программы.

Пример программирования в PL/SQL

В этом примере с помощью нашего пакета открываем курсор и передаем ссылку на курсор в программу. Извлечение результатов предложений SELECT выполняется отдельными процедурами, причем для результатов с разной структурой мы вынуждены предусмотреть разные процедуры извлечения. Это – плата за минимализм и общность пакета.

Выдадим в SQL*Plus:

(Чтобы не усложнять пример, результат на экране почти не оформляется).

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

Пример программирования в SQL*Plus

SQL*Plus позволяет заводить собственные переменные, в том числе и типа нестрогой ссылки на курсор. Открывается курсор, как и в примере выше, нашим пакетом, а вот извлечение возможно обычной командой PRINT. Эта команда умеет распознавать структуру фактического курсора, что очень удобно для работы.

Выдадим в SQL*Plus:

В отличие от предыдущего примера команда PRINT закрывает курсор, так что вторичная выдача

приведет к ошибке.

Пример программирования в Java

В клиентской программе на Java обращаться к БД через ссылку на курсор можно с помощью собственных расширений, сделанных фирмой Oracle в реализации ею драйвера JDBC. В программе ниже предполагается имя СУБД MYDB. Обратите внимание, что текст с запросом SQL передается нашему пакету объектом класса CallableStatement, а извлечение в программу ссылки на курсор делается после приведения этого объекта к сугубо Oracle’овскому классу OracleCallableStatement.

Получение в программу ссылки на курсор соответствует формированию объекта класса ResultSet, обработка которого делается стандартно.

Выдача второго запроса в программе ниже демонстрирует возможность использования одного и того же пакета для получения результата разной структуры. Очевидно, по своей гибкости эта техника находится посередине между тем, что имеется в PL/SQL и в SQL*Plus.

Курсор это средство извлечения данных из БД.
Курсоры содержат определения столбцов и объектов.
Курсоры используются для получения строк, возвращаемых запросом.
Для передачи строк в курсор используется запрос, после чего вы можете
выбирать строки из курсора по одной за один раз.


create table t1(id, type, text)
as
select object_id, object_type, object_name
from all_objects;

create table t1
as
select object_id id, object_type type, object_name text
from all_objects;


select id, type, text from t1
where />
17367 SCHEDULE FILE_WATCHER_SCHEDULE

select id, type, text from t1
where type = 'SCHEDULE';

17364 SCHEDULE DAILY_PURGE_SCHEDULE
17367 SCHEDULE FILE_WATCHER_SCHEDULE
17372 SCHEDULE PMO_DEFERRED_GIDX_MAINT_SCHED
18172 SCHEDULE BSLN_MAINTAIN_STATS_SCHED

Неявные курсоры определяются в момент выполнения:

DECLARE
v_text t1.text%TYPE;

BEGIN
SELECT text INTO v_text
FROM t1
WHERE /> DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
END;
/

В ходе выполнения кода создается курсор для выборки значения text.

Явный курсор определяется до начала выполнения:

DECLARE
CURSOR c_get_text
IS
SELECT text
FROM t1
WHERE />
v_text t1.text%TYPE;

BEGIN
OPEN c_get_text;
FETCH c_get_text INTO v_text;
DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
CLOSE c_get_text;
END;
/

Преимущество явного курсора заключается в наличии у него атрибутов,
облегчающих применение условных операторов.


CREATE OR REPLACE PROCEDURE proc1
AS
CURSOR c_get_text
IS
SELECT text
FROM t1
WHERE />
v_text t1.text%TYPE;

BEGIN
OPEN c_get_text;
FETCH c_get_text INTO v_text;
IF c_get_text%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
ELSE
DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
END IF;
CLOSE c_get_text;
END;
/


А как подобное сделать с неявным курсором:


CREATE OR REPLACE PROCEDURE proc2
AS
v_text t1.text%TYPE;
v_bool BOOLEAN := TRUE;

BEGIN
BEGIN
SELECT text INTO v_text
FROM t1
WHERE />
EXCEPTION
WHEN no_data_found THEN
v_bool := FALSE;
WHEN others THEN
RAISE;
END;

IF NOT v_bool THEN
DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
ELSE
DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
END IF;
END;
/


Пришлось заниматься перехватом исключений, чтобы определить, была ли найдена запись.


Параметризация курсоров помогает повысить степень их повторного использования.

курсор с параметром:

DECLARE
CURSOR c_get_text(par1 NUMBER)
IS
SELECT text
FROM t1
WHERE />
v_text t1.text%TYPE;

BEGIN
OPEN c_get_text(17367);
FETCH c_get_text INTO v_text;
DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
CLOSE c_get_text;
END;
/

Переменные типа REF CURSOR могут ссылаться на любые реальные курсоры.
Программа, использующая тип REF CURSOR, может работать с курсорами,
не заботясь о том, какие конкретно данные будут извлечены ими во время выполнения.


CREATE OR REPLACE PROCEDURE proc_ref
AS
v_curs SYS_REFCURSOR;
v_text t1.text%TYPE;

BEGIN
OPEN v_curs
FOR
'SELECT text '
|| 'FROM t1 '
|| 'WHERE />
FETCH v_curs INTO v_text;

DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );


Во время компиляции Oracle не знает, каким будет тексе запроса, - он видит строковую переменную.
Но наличие типа REF CURSOR говорит ему о том, что надо будет обеспечить некую работу с курсором.


Например я могу создать функцию, которая принимает некий входной параметр, создает курсор и возвращает тип REF CURSOR :


CREATE OR REPLACE FUNCTION func1(par1 NUMBER)
RETURN SYS_REFCURSOR
IS
v_curs SYS_REFCURSOR;

BEGIN
OPEN v_curs
FOR
'SELECT text '
|| 'FROM t1 '
|| 'WHERE /> || par1;


Другой пользователь может воспользоваться этой функцией так:

v_curs SYS_REFCURSOR;
v_text t1.text%TYPE;

BEGIN
v_curs := func1(17367);

FETCH v_curs INTO v_text;

IF v_curs%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
ELSE
DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );
END IF;

Для пользователя, вызывающего функцию func1(), она для него представляет черный ящик, возвращающий курсор.

Сильнотипизированный и слаботипизированный REF CURSOR.

TYPE имя_типа_курсора IS REF CURSOR [ RETURN возвращаемый_тип ];


TYPE refcursor IS REF CURSOR RETURN table1%ROWTYPE;

TYPE refcursor IS REF CURSOR;

Первая форма REF CURSOR называется сильно типизированной, поскольку тип структуры,
возвращаемый курсорной переменной, задается в момент объявления
(непосредственно или путем привязки к типу строки таблицы).

Вторая форма (без предложения RETURN) называется слаботипизированной.
Тип возвращаемой структуры данных для нее не задается.
Такая курсорная переменная обладает большей гибкостью, поскольку для нее можно задавать любые запросы
с любой структурой возвращаемых данных.

В Oracle 9i появился предопределенный слабый тип REF CURSOR с именем SYS_REFCURSOR,
теперь можно не определять собственный слабый тип, достаточно использовать стандартный тип Oracle:

DECLARE
my_cursor SYS_REFCURSOR;


Пример сильнотипизированного курсора:

TYPE my_type_rec IS RECORD (text t1.text%TYPE);
TYPE my_type_cur IS REF CURSOR RETURN my_type_rec;
v_curs my_type_cur;
v_text t1.text%TYPE;

BEGIN
OPEN v_curs
FOR
SELECT text
FROM t1
WHERE />
FETCH v_curs INTO v_text;

DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

TYPE my_type_cur IS REF CURSOR RETURN t1%ROWTYPE;
v_curs my_type_cur;
v_var t1%ROWTYPE;

BEGIN
OPEN v_curs
FOR
SELECT *
FROM t1
WHERE />
FETCH v_curs INTO v_var;

DBMS_OUTPUT.PUT_LINE( 'id = ' || v_var.id || ', type = ' || v_var.type || ', text = ' || v_var.text );


id = 17367, type = SCHEDULE, text = FILE_WATCHER_SCHEDULE

Пример слаботипизированного курсора:

TYPE my_type_cur IS REF CURSOR;
v_curs my_type_cur;
v_text t1.text%TYPE;

BEGIN
OPEN v_curs
FOR
SELECT text
FROM t1
WHERE />
FETCH v_curs INTO v_text;

DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

v_curs SYS_REFCURSOR;
v_text t1.text%TYPE;

BEGIN
OPEN v_curs
FOR
SELECT text
FROM t1
WHERE />
FETCH v_curs INTO v_text;

DBMS_OUTPUT.PUT_LINE( 'text = ' || v_text );

Курсор можно передавать в качестве параметра:

1. Функция принимающая курсор

CREATE OR REPLACE FUNCTION get_cursor(p_curs SYS_REFCURSOR)
RETURN VARCHAR2
IS
v_text t1.text%TYPE;

FETCH p_curs INTO v_text;

IF p_curs%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE( 'Данные не найдены. ' );
ELSE
DBMS_OUTPUT.PUT_LINE( 'Данные найдены. ' );
END IF;


2. Процедура принимающая текст SQL

CREATE OR REPLACE PROCEDURE get_sql (p_sql VARCHAR2)
IS
v_curs SYS_REFCURSOR;
v_res VARCHAR2(50);
BEGIN
IF v_curs%ISOPEN THEN
CLOSE v_curs;
END IF;
BEGIN
OPEN v_curs FOR p_sql;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20000, 'Unable to open cursor');
END;
v_res := get_cursor(v_curs);
CLOSE v_curs;
DBMS_OUTPUT.PUT_LINE(v_res);
END;
/

BEGIN
get_sql( 'SELECT text FROM t1 WHERE );
END;
/

Данные найдены.
FILE_WATCHER_SCHEDULE


Ещё примеры:

SET SERVEROUTPUT ON

var1 tab.col1%TYPE;
var2 tab.col2%TYPE;
var3 tab.col3%TYPE;

CURSOR cur IS
SELECT col1, col1, col3
FROM tab
ORDER BY col1;

BEGIN
-- Открываем курсор

LOOP
-- Выбираем из курсора строки
FETCH cur
INTO var1, var2, var3;

EXIT WHEN cur%NOTFOUND;

-- Выводим значения переменных
DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var1 || ', col2 = ' || var2 || ', col3 = ' || var3 );
END LOOP;

-- Закрываем курсор
CLOSE cur;
END;
/

Курсоры и цикл FOR

Для получения доступа к строкам из курсора можно использовать цикл FOR.
При использовании цикла FOR не нужно явно открывать курсор - цикл FOR сделает это автоматически.


SET SERVEROUTPUT ON

CURSOR cur IS
SELECT col1, col1, col3
FROM tab
ORDER BY col1;

BEGIN
FOR var IN cur LOOP
DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var.col1 || ', col2 = ' || var.col2 || ', col3 = ' || var.col3 );
END LOOP;
END;
/

Выражение OPEN - FOR

С курсором можно использовать выражение OPEN - FOR, которое добавляет еще больше гибкости при обработке курсоров,
поскольку вы можете назначить курсор для другого запроса.
Запрос может быть любым корректным выражением SELECT.
Это означает что вы можете повторно использовать курсор и назначить курсору позже в коде другой запрос.

SET SERVEROUTPUT ON

-- Определим тип REF CURSOR
TYPE t_cur IS
REF CURSOR RETURN tab%ROWTYPE;


-- Определим объект типа t_cur
cur t_cur;

-- Определим объект для хранения столбцов из таблицы tab
var tab%ROWTYPE;

BEGIN
-- назначим запрос для объекта cur и откроем его
OPEN cur FOR
SELECT * FROM tab WHERE col1 < 5;

-- Выбираем строки из cur в var
LOOP
FETCH cur INTO var;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var.col1 || ', col2 = ' || var.col2 || ', col3 = ' || var.col3 );
END LOOP;

-- Закрываем объект cur
CLOSE cur;
END;
/


Все ранее рассмотренные курсоры имели конкретный возвращаемый тип, который должен совпадать
со столбцами в запросе исполняемом курсором.
Можно определить курсор, который не имеет возвращаемого типа и может исполнять любой запрос.

SET SERVEROUTPUT ON

DECLARE
-- Определим тип REF CURSOR
TYPE t_cur IS REF CURSOR;

-- Определим объект типа t_cur
cur t_cur;

-- Определим объект для хранения столбцов из таблицы tab1
var1 tab1%ROWTYPE;

-- Определим объект для хранения столбцов из таблицы tab2
var2 tab2%ROWTYPE;

BEGIN
-- назначим запрос для объекта cur и откроем его
OPEN cur FOR
SELECT * FROM tab1 WHERE col1 < 5;

-- Выбираем строки из cur в var1
LOOP
FETCH cur INTO var1;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( 'col1 = ' || var1.col1 || ', col2 = ' || var1.col2 || ', col3 = ' || var1.col3 );
END LOOP;

-- назначим новый запрос для объекта cur и откроем его
OPEN cur FOR
SELECT * FROM tab2 WHERE col1 < 3;

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

В PL/SQL поддерживаются два типа курсоров : явные и неявные.

Явный курсор объявляется разработчиком, а неявный курсор не требует объявления.

Курсор может возвращать одну строку, несколько строк или ни одной строки.

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

Курсор может быть объявлен в секциях объявлений любого блока PL/SQL, подпрограммы или пакета.

Для управления явным курсором используются операторы CURSOR , OPEN , FETCH и CLOSE .

Оператор CURSOR выполняет объявление явного курсора .

Оператор OPEN открывает курсор , создавая новый результирующий набор на базе указанного запроса.

Оператор FETCH выполняет последовательное извлечение строк из результирующего набора от начала до конца.

Оператор CLOSE закрывает курсор и освобождает занимаемые им ресурсы.

Для объявления явного курсора используется оператор CURSOR , который может иметь следующее формальное описание:

Каждый параметр parameter определяется как:

Параметр return_type определяет запись или строку таблицы базы данных, используемую для возвращаемых значений. Тип возвращаемого значения должен соответствовать столбцам, перечисленным в операторе SELECT . Список параметров определяет параметры курсора , передаваемые на сервер каждый раз при выполнении оператора OPEN .

Одновременно с созданием результирующего набора можно выполнить блокировку выбираемых строк. Для этого в операторе SELECT следует указать фразу FOR UPDATE .

Для задания параметров курсора подходит как позиционная, так и именованная нотация .

Для работы с курсором можно использовать следующие атрибуты, указываемые после имени курсора :

  • %ISOPEN - возвращает значение TRUE , если курсор открыт.
  • %FOUND - определяет, найдена ли строка, удовлетворяющая условию.
  • %NOTFOUND - возвращает TRUE , если строка не найдена.
  • %ROWCOUNT - возвращает номер текущей строки.

Оператор FETCH может быть выполнен в цикле LOOP-END LOOP . Это позволяет последовательно просматривать весь результирующий набор, который был открыт оператором OPEN .

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

При выполнении SQL-оператора, для которого не был объявлен явный курсор , Oracle автоматически открывает неявный курсор .

При применении неявного курсора нельзя использовать операторы управления курсором OPEN , FETCH и CLOSE .

Если при неявном курсоре в результирующий набор записывается более одной строки, то Oracle инициирует исключение TOO_MANY_ROWS .

Если курсор создается в пакете, то его объявление и спецификация могут быть разделены: объявление курсора указывается в секции объявлений пакета, а спецификация курсора - в теле пакета.

Объявление курсора при создании пакета может иметь следующее формальное описание:

Табличные функции используются для возврата PL/SQL-коллекций, которые имитируют таблицы. Они могут быть запрошены как обычные таблицы с помощью функцию TABLE во фразе FROM. Обычные табличные функции требуют, чтобы коллекции перед возвращением были полностью наполнены (населены). Так как коллекции хранятся в памяти, это может стать проблемой, поскольку на большие коллекции впустую тратится много памяти и времени в ожидании возвращения первой строки. Эти узкие возможности делают обычные табличные функции непригодными в случаях масштабных ETL-операций (ETL — Extraction Transformation Load — Извлечение-Преобразование-Загрузка). Обычные табличные функции требуют создания именованной строки и табличных типов как объектов базы данных.

Заметим, что в этом листинге строки перечислены в обратном порядке, поскольку запрос содержит фразу упорядоченности по убыванию.

Конвейерные табличные функции)
(Pipelined Table Functions

Конвейерная обработка отменяет надобность в создании огромных наборов, передавая строки по каналу из функции по мере их создания, сохраняя память и позволяя запустить последующую обработку еще до окончания генерации всех строк.

Конвейерные табличные функции включают фразу PIPELINED и используют вызов PIPE ROW, чтобы вытолкнуть строки из функции, как только они создадутся, вместо построения табличной коллекции. Заметим, что вызов RETURN пустой, поскольку нет никакой коллекции, возвращаемой из функции.

Когда ETL-операции проводятся на большом хранилище данных, наблюдается существенное повышение производительности, поскольку загрузка данных из внешних таблиц производится табличными функциями непосредственно в таблицы хранилища, избегая промежуточного размещения данных.

Исключение NO_DATA_NEEDED
(NO_DATA_NEEDED Exception)

Конвейерная табличная функция может создать больше данных, чем необходимо запросившему её процессу. Когда такое происходит, конвейерная табличная функция останавливает выполнение, порождая исключение NO_DATA_NEEDED. Оно не должно явно обрабатываться, если только в процедуру не включен обработчик исключений OTHERS.

Приведенная ниже функция возвращает 10 строк, но запрос потребовал от нее только первые 5 строк. В этом случае функция прекращает выполнение, вызывая исключение NO_DATA_NEEDED.

Если имеется обработчик исключений OTHERS, то он захватит исключение NO_DATA_NEEDED и выполнит некоторый код обработки ошибок, что не нужно.

Если вы планируете использовать обработчик исключений OTHERS, то для исключения NO_DATA_NEEDED необходимо задействовать специальное прерывание.

Исключение NO_DATA_NEEDED может быть также использовано для выполнения операций очистки (cleanup).

Сравнение использования памяти
(Memory Usage Comparison)

Следующая функция возвращает текущее значение определенной статистики. Она позволит нам сравнивать память, используемую обычными и конвейерными табличными функциями.

Сначала мы протестируем обычную табличную функцию, создав новое соединение и запросив большую коллекцию. Проверяя выделение памяти PGA как до, так и после, тест позволит нам увидеть, сколько памяти было выделено в результате проведения теста.

Затем мы повторим тест для конвейерной табличной функции.

Сокращение памяти, используемой конвейерной табличной функцией, обусловлено тем, что она не требует разместить целую коллекцию в памяти.

Кардинальность
(Cardinality)

Oracle оценивает кардинальность (мощность, количество элементов) конвейерной табличной функции, базируясь на размере блока базы данных. Когда используется размер блока по умолчанию, оптимизатор всегда предполагает, что кардинальность - 8168 строк.

Это прекрасно, если вы запрашиваете только конвейерную табличную функцию, но если планируется использовать ее в соединении, это может оказать негативное влияние на план выполнения.

  • хинт CARDINALITY (9i+): Недокументирован (Undocumented)
  • хинт OPT_ESTIMATE (10g+): Недокументирован (Undocumented)
  • хинт DYNAMIC_SAMPLING (11gR1+): Вызывает полное сканирование конвейерной табличной функции для оценки количества элементов в самом запросе перед его запуском. Это очень расточительно.
  • Расширенный Оптимизатор (Extensible Optimizer) (9i+): возможность расширения оптимизатора позволяет нам сообщить ему, что нужно поддерживать кардинальность.
  • Обратная связь по количеству элементов (Cardinality Feedback): в Oracle Database 11gR2 оптимизатор замечает, отличается ли фактическое количество элементов от ожидаемой кардинальности. Последующие запросы будут нести свою кардинальность, скорректированную на основе этой обратной связи. Если предложение выбирается из разделяемого пула или экземпляр стартует заново, эта обратная связь по количеству элементов теряется. В Oracle Database 12c обратная кардинальность по количеству элементов сохраняется в табличной области SYSAUX.

Чтобы использовать расширяемый оптимизатор, в конвейерные табличные функции нужно вручную добавить параметр, который укажет оптимизатору использовать кардинальность.

Заметим, что параметр p_cardinality нигде не используется непосредственно в функции.

Затем мы строим тип и тело типа, чтобы установить кардинальность (количество элементов) вручную. Обратите внимание на ссылку на параметр p_cardinality в типе.

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

Мы знаем, что функция возвращает 10 строк, но оптимизатор этого не знает. Независимо от числа строк, возвращенных функцией, оптимизатор использует значение параметра p_cardinality как оценку количества элементов (кардинальности).

Неявные (теневые) типы
(Implicit (Shadow) Types)

В отличие от обычных табличных функций, конвейерные табличные функции могут быть определены с использованием типов "table" и "record", определенных в спецификации пакета.

Это представляется более правильным решением, чем построение всех типов базы данных вручную, а Oracle по умолчанию строит теневые объектные типы неявно.

Как можно видеть, Oracle фактически создал три теневых объектных типа с системно сгенерированными именами для поддержки типов, требуемых конвейерной табличной функцией. По этой причине я всегда строю именованные объектные типы базы данных, вместо того, чтобы полагаться на неявные типы.

Конвейерные табличные функции, запускаемые параллельно
(Parallel Enabled Pipelined Table Functions)

Чтобы включить параллельные конвейерные табличные функции, должны быть выполнены следующие условия.

  • Должна быть включена фраза PARALLEL_ENABLE.
  • Должен присутствовать один или более входных параметров REF CURSOR.
  • Должна иметь место фраза PARTITION BY, чтобы указать метод секционирования рабочей нагрузки. Слабо связанные ref-курсоры (weakly typed ref cursors) могут использовать только фразу PARTITION BY ANY, которая определяет секционирование рабочей нагрузки случайный образом.

Основной синтаксис показан ниже.

Чтобы увидеть это в действии, сначала надо создать и населить тестовую таблицу.

Следующий пакет определяет включенные параллельные конвейерные табличные функции, которые принимают ref-курсоры по запросу из тестовой таблицы и возвращают те же самые строки вместе с SID (системный идентификатор) сессии, которая их обработала. Можно было бы использовать слабо связанный ref-курсор, подобный SYS_REFCURSOR, но тогда мы были бы ограничены только методом секционирования SYS_REFCURSOR. Следующие три функции представляют три метода секционирования.

Следующий запрос использует функцию CURSOR, чтобы преобразовать запрос к тестовой таблице в ref-курсор, который передан табличной функции в качестве параметра. Результаты группируются по SID сессий, которые обрабатывают строки. Отметим, что все строки обработаны одной и той же сессией. Почему? Потому что, хотя эта функция включена как параллельная, мы не указали ей работать параллельно.

Следующие запросы включают хинт параллельности и вызывают все эти функции.

Уровень параллелизма (DOP — degree of parallelism) может быть ниже чем тот, который указан в хинте.

Дополнительная фраза управления выходным потоком быть использована для упорядочения или кластеризации (объединение в группы) — order or cluster — данных, основанной на списке столбцов, в процессе серверной обработки. Это может быть необходимым, если существуют зависимости в данных. Например, нужно секционировать по определенному столбцу, но также и требовать, чтобы строки были обработаны в определенном порядке в рамках этого же секционирования. Расширенный синтаксис такого случая показан ниже.

Можно сделать нечто подобное:

Трансформация конвейеров
(Transformation Pipelines)

В традиционных ETL-процессах необходимо сначала загрузить данные в промежуточную область, затем сделать по ней несколько проходов, чтобы преобразовать и переместить данные в область, откуда они будут загружены в схему назначения. Прохождение данных через промежуточные таблицы может потребовать значительного количества операций дискового ввода/вывода, как для загружаемых данных, так и для данных redo-журнала. Альтернативой должно стать выполнение преобразования конвейерными табличными функциями, поскольку данные читаются из внешней таблицы и вставляются непосредственно в таблицу назначения, сокращая большую часть операций дискового ввода/вывода.

В этой секции мы увидим и проэкзаменуем с использованием обсуждавшихся ранее методов трансформацию конвейера.

Сначала в виде плоского файла нужно выкачать из файловой системы сервера базы данных какие-либо тестовые данные.

Создаем объект "directory", где указывается местоположение этого файла, создаем внешнюю таблицу, чтобы прочитать файл, и создаем таблицу назначения.

Заметим, что в таблице назначения по сравнению с внешней таблицей есть два дополнительных столбца. Каждый из этих столбцов представляет шаг преобразования. Фактические преобразования в этом примере тривиальны, но следует представить, что они могут быть сложными и невыполнимыми одним SQL-предложением. Следовательно, имеет место потребность в табличных функциях.

Пакет ниже определяет два шага процесса преобразования и процедуры для его запуска.

Вставка внутри процедуры LOAD_DATA полностью выполняет загрузку данных, включая преобразования. Предложение выглядит довольно сложно, но оно состоит из следующих простых шагов.

  • Строки запрашиваются из внешней таблицы.
  • Строки конвертируются в ref-курсор с помощью функции CURSOR.
  • Этот ref-курсор передается на первом этапе преобразования (STEP_1).
  • Возвращаемая на шаге STEP_1 коллекция запрашивается, когда используется функция TABLE.
  • Вывод этого запроса преобразуется в ref-курсор с помощью функции CURSOR.
  • Этот ref-курсор передается на второй этап преобразования (STEP_2).
  • Возвращаемая на шаге STEP_2 коллекция запрашивается, когда используется функция TABLE.
  • Этот запрос используется для выполнения вставки в результирующую таблицу.

Применяя процедуру LOAD_DATA, можно как преобразовывать, так и загружать данные.

Заметим, что этот пример не содержит процедуры обработки ошибок и что в нем нет хинтов параллельности, чтобы упростить запрос в процедуре LOAD_DATA.

Читайте также: