On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru






 

Динамический SQL в программах на PL/SQL. Использование пакет DBMS_SQL.

Стив Бобровски

Я хотел бы посвятить эту статью памяти Роберта Куи (Robert Kooi), моего друга и блестящего разработчика, внесшего значительный вклад в развитие сервера Oracle7, особенно в части PL/SQL.

Администраторы и разработчики часто пренебрегают теми преимуществами, которые можно получить от применения некоторых выдающихся, но скрытых драгоценных возможностей, которыми обладает сервер Oracle7. Я прежде всего имею в виду пакеты утилит DBMS, которые позволяют разработчикам приложений применять расширенные средства сервера базы данных, такие как сигналы (alerts), коммуникационные каналы (communication pipes) и управляемые сервером блокировки ресурсов (server-managed resource locking). В Oracle7 версии 7.1 имеется поистине фантастическое добавление к семейству пакетов DBMS: новый пакет DBMS_SQL обеспечивает разработчиков инструментарием для создания динамически формируемых предложений SQL в программах на PL/SQL.

Статические и динамические предложения SQL

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

(*) [Примечание автора: Когда разрабатывается сложное производственное приложение для работы в режиме клиент/сервер, обычно для этого приложения кодируется много SQL-предложений в хранимых процедурах, которые затем реагируют на вызовы приложения, чтобы обеспечить работу процедур с базой данных. Однако, поскольку скомпилированные приложения обладают лишь статическими вызовами процедур, а SQL-предложения в хранимых в базе данных процедурах также уже скомпилированы, то общее положение о том, что все такие приложения являются статическими, остается в силе.]

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

Используя метод динамически формируемых SQL-предложений, приложения строят их во время исполнения (runtine), и в этом смысле Вы можете создавать приложения, которые изменяются по выбору, определяемому пользователем. В качестве общедоступного интерфейса к динамически формируемым SQL-предложениям можно представить себе, например, инструментарий типа SQL*Plus, при помощи которого пользователи могут работать с SQL-предложениями любого типа.

Введение в динамически формируемые SQL-предложения

Основным положением в понимании использования динамических SQL-предложений является то, что если некоторое SQL-предложение является динамически формируемым, то программа должна построить правильное SQL-предложение, выполняя последовательность определенных шагов, формирующих это предложение; выполнить его, а в случае, если это SQL-предложение представляет собой запрос к базе данных [запросное и незапросные предложения - см. таблицу 1. Прим. переводчика], а также определить его колонки и выборку строк возвращаемого набора. Тип выборки и число шагов, требуемое для выполнения динамических SQL-предложений, изменяется в зависимости от типа SQL-предложения, что и вынуждает программу определенным образом определять и выполнять эти предложения. Различные возможности выделили четыре формальных метода формирования динамических SQL-предложений. Таблица 1 кратко излагает некоторые особенности каждого метода. В последней колонке таблицы 1 приводятся последовательности списков вызовов подпрограмм пакета DBMS_SQL, которые программа на PL/SQL должна выполнить, чтобы реализовать динамически формируемые SQL-предложения каждого типа.

Если Вы еще не очень свободно владеете механизмом формирования динамических SQL-предложений и хотели бы получить больше информации, рекомендую обратиться к главе о динамических SQL-предложениях в Руководстве "Programmer`s Guide to the Oracle Precompilers" (Руководство программиста по прекомпиляторам Oracle). Не дублируя сведений, которые можно получить из документации, эта статья фокусирует внимание читателей на использовании пакета DBMS_SQL, чтобы показать возможность применения динамически формируемых SQL-предложений в программах на PL/SQL.

Таблица 1.
Метод Тип предложения Требуемые вызовы пакета DBMS_SQL
1. Незапросные (nonqueries), нет базовых переменных (no host variables), выполняется однократно(execute once) открытие курсора (open cursor), разборка (parse), выполнение (execute),закрытие курсора (close cursor).
2. Незапросные, известное число базовых переменных (known nunber host variables), выполняются один или несколько раз (execute one or more times) открытие курсора, разборка, связывание переменных (bind variables), выполнение, закрытие курсора.
3. Запросные (queries), известное число операторов SELECT и базовых переменных (known nunber of SELECT list items and host variables) открытие курсора, разборка, связывание переменных, определение колонок (define columns), выполнение, выборка строк (fetch rows), получение значений колонок (get column values), обновление (refretch), ... закрытие курсора.
4. Запросные, неизвестное число операторов SELECT и базовых переменных колонок (unknown nunber of SELECT list items and column host variables) открытие курсора, разборка, связывание переменных, определение колонок, выполнение, выборка строк, получение значений колонок, обновление, ... закрытие курсора.

Роль пакета DBMS_SQL

Пакет DBMS_SQL включает много процедур и функций, которые обеспечивают процедурный интерфейс на PL/SQL для реализации различных этапов определения и выполнения динамически формируемых SQL и PL/SQL предложений в хранимых процедурах, функциях и пакетах. Вставка "Краткая сводка DBMS_SQL API" являет собой в сжатом концентрированном виде справочник (quick-reference) по наиболее общим и часто используемым процедурам и функциям программного интерфейса приложений (API - application programming interface), которым является пакет DBMS_SQL.

Для более полной характеристики конструкций, перечисленных во вставке "Краткая сводка DBMS_SQL API", рекомендую обратиться к Приложению к документации по Серверу Oracle7 (Oracle7 Server Documentation Addendum) или внимательно изучить скрипт, который Oracle7 выполняет, чтобы создать пакет DBMS_SQL (например, во всех UNIX-системах - это скрипт $ORACLE_HOME/rdbms/admin/dbmssql.sql).

Примеры простых и сложных процедур>

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

На листинге 1 приведены две простые процедуры, которые некоторое приложение может использовать, чтобы создавать и удалять временные таблицы, которые можно индивидуально применять в сессии конкретного пользователя. Отметим, на что следует особо обратить внимание в этом листинге:

# Процедуры показывают, как используя пакет DBMS_SQL, динамически сформировать в хранимой процедуре SQL-предложения, относящиеся к первому типу (таблица 1);

# Процедуры показывают, как пакет DBMS_SQL позволяет программам на PL/SQL выполнить SQL-предложения языка определения данных (DDL - data_difinition_language) такие, как CREATE TABLE и DROP TABLE (см. вставку "Несколько слов о PL/SQL и DDL SQL");

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

ЛИСТИНГ 1.

Использование пакета DBMS_SQL для формирования динамических DDL SQL-предложений внутри хранимой процедуры (по методу 1)

CREATE PROCEDURE create_temp_dept
       (tname IN OUT VARCHAR2)
AS
       cur INTEGER;       - хранит идентификатор (ID) курсора
       ret INTEGER;       - хранит возвращаемое по вызову значение
       str VARCHAR2(250); - хранит команды
BEGIN
-
-  генерация временной таблицы по имени DEPT, используя заранее
-  заданное (hard-coded) имя
-  и возврат значения  функции
DBMS_SESSION.UNIQUE_SESSION_ID
-
       tname :=  dept_t  || dbms_session.unique_session_id
-
-  генерация команды CREATE TABLE по заранее заданному тексту 
-  и переменной tname
-
    str := 'CREATE TABLE  '||tname
        || ' (deptno INTEGER,'
        || ' dname VARCHAR2(14),'
        || ' loc  VARCHAR2(13), '
        || 'TABLESPACE  temp  '
        || 'STORAGE ('
        || 'INITIAL 10K NEXT 10K MAXEXTENTS 2 )';
-
-  Динамически формируемое DDL SQL-предложение по методу 1
-
    cur := dbms_sql.open_cursor;
    dbms_sql.parse(cur, str, dbms_sql.v7);
    ret := dbms_sql.execute(cur);
    dbms_sql.close_cursor(cur);
END;

CREATE PROCEDURE drop_temp_dept
       (tname IN OUT VARCHAR2)
AS
       cur INTEGER;       - хранит идентификатор (ID) курсора
       ret INTEGER;       - хранит возвращаемое по вызову значение
       str VARCHAR2(250); - хранит команды
BEGIN
-
-  генерация временной таблицы по имени DEPT, используя заранее
-  заданное (hard-coded) имя 
-  и возврат значения  функции
DBMS_SESSION.UNIQUE_SESSION_ID
-
       tname :=  dept_t  || dbms_session.unique_session_id;
-
-  генерация команды DROP TABLE по заранее заданному тексту
-  и переменной tname
-
   str :=  'DROP TABLE  '||tname;
-
- Динамически формируемое DDL SQL-предложение по методу 1
-
    cur := dbms_sql.open_currsor;
    dbms_sql.parse(cur. str, dbms_sql.v7);
    ret := dbms_sql.execute(cur);
    dbms_sql.close_currsor(cur);
END;

Как можно увидеть из примера, приведенного на листинге 1, динамическое формирование SQL-предложения по методу 1 при помощи пакета DBMS_SQL требует всего несколько действий и очень просто в реализации. Приведенный ниже безымянный блок на PL/SQL служит простой иллюстрацией, как инструментальные средства SQL*Plus или SQL*DBA вызывают хранимые процедуры CREATE_TEMP_DEPT и DROP_TEMP_DEPT, чтобы создать или уничтожить временную таблицу DEPT в период сессии с базой данных. Перед выполнением этого блока не забудьте установить переменную среды SQL*Plus serveroutput в положение ON.

[Прим. переводчика: отображение вывода хранимых процедур (функция DBMS_OUTPUT.PUT_LINE) в SQL*Plus опредяется переменной

set serverout[put] {ON|OFF} [SIZE n]

где SIZE - количество байтов вывода, сколько буферируется сервером Oracle7. По умолчанию это значение равно 2000, но не может превышать 1,000,000. Вывод отображается после выполнения сервером Oracle7 блока на PL/SQL.]

DECLARE
     x VARCHAR2(50);
BEGIN
     create_temp_dept(x);
     dbms_output.put_line(x ||' table created') ;
     drop_temp_dept(x);
     dbms_output.put_line(x ||' table dropped') ;
END;
/

Теперь давайте рассмотрим несколько более сложный пример, который показывает, как представить динамически формируемое SQL-предложение для запроса в хранимой функции. Функция DEPT_LIST_BUILDER, приведенная на Листинге 2, показывает, как создать функцию, которая строит выходной буфер, содержащий список ограничений номеров отделов и имен клиентов приложения.

ЛИСТИНГ 2.

Использование пакета DBMS_SQL для формирования динамического SQL-предложения внутри хранимой функции DEPT_LIST_BUILDER (по методу 3)

CREATE FUNCTION dept_list_builder
     (loc_col IN CHAR DEFAULT  'Y' )
     RETURN LONG
AS
     stmt VARCHAR2(250);          - хранит команду
     select_list VARCHAR2(50);    - хранит список выборки
     deptid INTEGER;              - хранит список данных deptno
     deptnm VARCHAR2(14);         - хранит список данных dname
     deptlc VARCHAR2(13);         - хранит список данных loc
     cur    INTEGER ;             - хранит идентификатор курсора
     ret    INTEGER;              - хранит возвращаемое по вызову значение
     output LONG(32760);          - хранит список ограничений
BEGIN
-
-  Построение списка SELECT с использованием значения флажка loc_col  .
-  Список запросов SELECT всегда содержит колонки DETNO и DNAME .
-
     select_lst :=  ' deptno, dname';
     IF UPPER(loc_col)  =  'Y'    THEN
           select_list := select_list ||' ,loc';
     END IF;
-
-  Построение команды SELECT. Владелец процедуры должен иметь
-  привилегию SELECT для таблицы SCOTT.DEPT
-
     stmt :=  'SELECT ' || select_list
                   ||'  FROM scott.dept ORDER BY deptno';
-
-  Динамическое формирование SQL-предложения по методу 3
-
-  Открытие курсора и разборка запроса
-
     cur := dbms_sql.open_cursor;
     dbms_sql.parse(cur, str, dbms_sql.v7);
-
-  Определение колонок в запросе
-
     dbms_sql.define_column(cur,1,deptid);
     dbms_sql.define_column(cur,2,deptnm,14);
     IF UPPER(loc_col)  =  'Y'    THEN
         dbms_sql.define_column(cur,3,deptlc,13);
     END IF;
-
-  Выполнение запроса
-
     ret := dbms_sql.execute(cur);
-
-  Извлечение записей, разграничение и помещение списка
-  в выходной буфер
-  ',' - ограничитель указывает на конец поля .
-  ';' - ограничитель указывает на конец записи .
-
LOOP
     IF  dbms_sql.fetch_row(cur) > 0   THEN
         dbms_sql.column_value(cur,1,deptid);
         dbms_sql.column_value(cur,2,deptnm);
         output := output || deptid || ',' || deptnm;
     IF  UPPER(loc_col)  = 'Y'         THEN
         dbms_sql.column_value(cur,3,deptlc);
         output := output || ',' || deptlc;
     END IF;
         output := output ||':';
     ELSE
          EXIT;
     END IF;
END LOOP;
         dbms_sql.close_cursor(cur);
         RETURN output;
END dept_list_builder;

Отметим, что функция DEPT_LIST_BUILDER демонстрирует дополнительные действия, требуемые для исполнения запроса с динамически формируемым SQL-предложением:

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

Следующий ниже неимеющий названия блок на PL/SQL показывает, как вызвать функцию и получить возвращенное значение функции DEPT_LIST_BUILDER, применяя SQL*Plus или SQL*DBA. Перед выполнением этого блока не забудьте установить переменную serveroutput в положение ON.

DECLARE
     outputbuffer VARCHAR2(2000);
BEGIN
     outputbuffer := dept_list_builder('Y');
-
-  Попробуйте выполнить вышеприведенный вызов с значением 'N',
-  чтобы самим увидеть динамическое SQL-предложение в действии
-
     dbms_output.put_line(outputbuffer);
END;

Заключение

Реализация динамически формируемых SQL-предложений в программах на PL/SQL является достаточно простым действием, если только Вы поняли, как определять и выполнять различные типы динамически формируемых SQL-предложений, используя процедуры и функции пакета DBMS_SQL. Хотя эта статья не является руководством по динамическим SQL-предложениям и пакету DBMS_SQL, можно надеяться, что представленная здесь информация послужит расширению Ваших знаний в обоих направлениях, а результатом будет Ваша возможность лучше конструировать приложения, работающие с базами данных Oracle7.

                           Вставка 1.
                  "Краткая сводка DBMS_SQL API"
             (A Quick Reference to the DBMS_SQL API)
Эта таблица описывает функции пакета DBMS_SQL, которые используются наиболее часто. Вырвите (лучше отксерьте - Ред.) ее и поместите возле своего компьютера, чтобы использовать в качестве удобного справочника. ПРОЦЕДУРЫ, ФУНКЦИИ И ПРОЧЕЕ ОПИСАНИЕ V6 CONSTANT INTEGER := 0 Пакет DBMS_SQL содержит три глобальные NATIVE CONSTANT INTEGER := 1 константы: V6=0, NATIVE=1 и V7=2. V7 CONSTANT INTEGER := 2 Использование констант указывает на применение определенного языка, когда вызывается некоторая процедура из пакета DBMS_SQL. V6 и V7 определяют поведение SQL-предложения по правилам Oracle6 или Oracle7 соответственно. NATIVE определяет поведение предложения согласно той версии базы данных, с которой программа соединена в настоящий момент времени. BIND_VARIABLE Связывает значение c переменной (cursor IN INTEGER, в предложении, разбираемом в курсоре. variable IN VARCHAR2, Когда переменная является входной или value IN NUMBER|VARCHAR2| входно/выходной, связанное значение DATE|MLSLABEL должно быть правильно определено. [ , size IN INTEGER ] ) Если переменная - выходная, вызов игнорирует связанное значение. CLOSE_CURSOR Закрывает открытый курсор. (cursor IN OUT INTEGER) COLUMN_VALUE Получает значение колонки. Используйте (cursor IN INTEGER, эту процедуру для доступа к данным, possition IN INTEGER, предварительно выбранным посредством value OUT NUMBER| VARCHAR2| вызова функции FETCH_ROWS. DATE|MLSLABEL [, error OUT NUMBER, , length OUT INTEGER ] ) DEFINE_COLUMN Определяет колонку, указанную (cursor IN INTEGER, в курсоре. Эта процедура используется possition IN INTEGER, только в SELECT-курсорах. value IN NUMBER| VARCHAR2| DATE|MLSLABEL [ , size IN INTEGER ] ) EXECUTE Выполняет предложение, находящееся в (cursor IN INTEGER) курсоре и возвращает число обработанных RETURN INTEGER в процессе выполнения строк. EXECUTE_AND_FETCH Выполняет курсор и затем (cursor IN INTEGER, извлекает первую строку из курсора. exact IN BOOLEAN EXECUTE_AND_FETCH дублирует действия DEFAULT FAULSE) в последовательности вызовов EXECUTE и RETURN INTEGER одиночного FETCH_ROWS, но реализуется с меньшим обменом сообщениями между клиентом и сервером. Эта функция полезна, когда программа намеревается выполнить курсор и затем извлечь из него только одну запись. FETCH_ROWS Извлекает строку из курсора. После (cursor IN INTEGER) извлечения строки в локальный буфер, RETURN INTEGER применяя FETCH_ROWS, программа должна вызвать процедуру COLUMN_VALUE, чтобы прочитать извлеченную строку. Программа может неоднократно использовать функцию FETCH_ROWS, чтобы извлекать строки из курсора, до тех пор пока не будут исчерпаны все строки. IS_OPEN Проверяет открыт ли курсор. (cursor IN INTEGER) RETURN BOOLEAN LAST_ERROR_POSITION Вызывайте эту функцию после выполнения RETURN INTEGER операции курсора. Если имеет место ошибка во время выполнения операции, находящейся в курсоре, функция возвращает относительную позицию колонки в курсорном предложении, которая послужила причиной ошибки. LAST_ROW_COUNT Вызывайте эту функцию после выполнения RETURN INTEGER операции курсора. Функция возвращает суммарное количество строк, извлеченных до сих пор из курсора. LAST_ROW_ID Вызывайте эту функцию после выполнения RETURN ROWID операции курсора. Функция возвращает значение ROWID последней строки, обработанной в курсоре. LAST_SQL_FUNCTION_CODE Вызывайте эту функцию после выполнения RETURN INTEGER операции курсора. Функция возвращает код функции SQL-предложения. OPEN_CURSOR Открывает новый курсор. Когда больше не RETURN INTEGER требуется, необходимо закрыть открытый курсор, используя функцию CLOSE_CURSOR. PARSE Немедленная разборка предложения в (cursor IN INTEGER, курсоре. Если разбираемое предложение statement IN VARCHAR2, является DDL-предложением, процедура language IN INTEGER) также выполняет это DDL-предложение. VARIABLE_VALUE Получает одно или несколько (cursor IN INTEGER, значений переменной в курсоре. variable IN VARCHAR2, value OUT NUMBER|VARCHAR2| DATE|MLSLABEL)
Вставка 2.
"Несколько слов о PL/SQL и DDL SQL"
(A Word About PL/SQL and DDL SQL)

Примеры, приведенные на листинге 1, показывают, как обойти стороной ограниченность PL/SQL в Oracle7 версии 7.1, а именно, отсутствие поддержки DDL (data definition language - язык определения данных) SQL-предложений. Не удивительно ли Вам, почему PL/SQL непосредственно не поддерживает предложения DDL SQL? Для того, чтобы ответить на этот вопрос, рассмотрим, как реагирует Oracle7, когда Вы создаете программу на PL/SQL.

Когда компилируется программа PL/SQL. Oracle7 производит больше, чем просто проверку синтаксиса предложений - он также проверяет зависимости объектов (object dependencies) базы данных и проверку полномочий на право доступа (security auhorizations - авторизационную защиту), чтобы удостовериться, что программа на PL/SQL сделана правильно. Кроме того, для хранимых в базе данных PL/SQL-программ, таких как процедуры и триггеры, Oracle7 автоматически сохраняет путь по цепочкам объектных зависимостей (track of object-dependency chains), так что сервер может при необходимости автоматически сделать недействительными (invalidate) или переправить (revalidate) объекты, которые зависят друг от друга. Это встроенная в сервер возможность снимает неудобство от необходимости ручного сохранения пути объектных зависимостей и ручной проверки или рекомпиляции объектов, когда имеет место что-либо простое, как например, модификация таблицы. Когда же схемы сложных приложений имеют много зависимых между собой объектов, ручное управление объектной зависимостью может стать задачей исключительной значимости.

Теперь, помня об имеющемся в Oracle7 автоматическом механизме поддержания объектных зависимостей, рассмотрим, что случилось бы, если PL/SQL непосредственно поддерживал бы DDL SQL-предложения. При наличии такой возможности программа на PL/SQL могла бы, среди прочего, создавать объекто-подобные (database-objectlike) таблицы базы данных. Но этот сценарий содержит парадокс - Oracle7 не разрешает построения правильной программа на PL/SQL, которая зависела бы от еще не существующих объектов базы данных. Этот пример демонстрирует простую мысль, что чтобы что-либо получить, чаще всего приходится от чего-то отказываться. В случае с PL/SQL программисты Oracle7 обычно выбирают отказ от возможности применения DDL SQL-предложений и взамен получают автоматический механизм проверки объектных зависимостей и правильности программ.

В Oracle7 версии 7.1 пакет DBMS_SQL обеспечивает удобное средство, чтобы обойти это ограничение DDL в PL/SQL без подрыва Oracle7-механизма поддержки обеспечения объектных зависимостей. Поскольку предложения DDL SQL являются внутренними по отношению к программе PL/SQL, поскольку они, динамически формируемые, строятся во время выполнения, поэтому Oracle7 может допустить правильность построения программы. Следует, однако, понимать, что когда программа на PL/SQL использует пакет DBMS_SQL, чтобы построить предложения DDL SQL, программа должна брать на себя ответственность за возможные ошибки, которые могут быть результатом нарушения объектных зависимостей и прав доступа, которые Oracle7 не проверяет во время компиляции.



Литература по Oracle