ГЛАВА 4

        ----------------------------------------------------------------

        ВЗАИМОДЕЙСТВИЕ С ORACLE


                           Есть два сорта знания. Мы знаем предмет сами,
                               или мы знаем, где найти информацию о нем.
                                                          Сэмюел Джонсон

        Эта глава  помогает вам  использовать мощь  ORACLE.  Вы узнаете,
        как  PL/SQL  поддерживает  команды,  функции  и операторы SQL, с
        помощью которых ваша программа манипулирует данными ORACLE.   Вы
        узнаете  также,  как   обращаться  к  удаленным   базам  данных,
        управлять   курсорами,    использовать   советы    оптимизатору,
        обрабатывать транзакции  и поддерживать  целостность вашей  базы
        данных.













































                                            Взаимодействие с ORACLE  4-1


----------------

Поддержка SQL

        За  счет  расширения  языка  SQL,  PL/SQL  предлагает уникальную
        комбинацию мощи  и простоты  использования.  Вы  можете гибко  и
        безопасно  манипулировать  данными  ORACLE,  потому  что  PL/SQL
        поддерживает все команды манипулирования данными (за исключением
        EXPLAIN   PLAN),   команды   управления   транзакциями, функции,
        псевдостолбцы  и  операторы  SQL.  Однако PL/SQL НЕ поддерживает
        команд определения данных,  таких как CREATE,  команд управления
        сессией, таких как SET ROLE, и команду управления системой ALTER
        SYSTEM.


Манипулирование данными
-----------------------

        Для  манипулирования  данными  ORACLE  вы  используете   команды
        INSERT, UPDATE, DELETE, SELECT  и LOCK TABLE.  INSERT  вставляет
        новые строки данных в  таблицы базы данных; UPDATE  модифицирует
        строки; DELETE удаляет ненужные строки; SELECT выбирает  строки,
        удовлетворяющие вашим  поисковым условиям;  LOCK TABLE  временно
        ограничивает доступ к таблице.

Управление транзакциями
-----------------------

        ORACLE  ориентирован  на   транзакции;  иными  словами,   ORACLE
        использует транзакции,  чтобы обеспечивать  целостностьь данных.
        ТРАНЗАКЦИЯ -  это ряд  предложений манипулирования  данными SQL,
        которые  выполняют  логическую  единицу  работы.   Например, два
        предложения UPDATE могли бы  кредитовать один банковский счет  и
        дебитовать другой.   В один  момент времени  ORACLE либо  делает
        постоянными,  либо  отменяет   все  изменения  в   базе  данных,
        осуществленные  транзакцией.   Если  ваша  программа сбивается в
        середине  транзакции,  ORACLE  обнаруживает  ошибку  и выполняет
        отмену   (откат)   транзакции.    Следовательно,   база   данных
        автоматически возвращается в свое прошлое состояние.

        Для   управления   транзакциями   используются   команды COMMIT,
        ROLLBACK,   SAVEPOINT   и   SET   TRANSACTION.    COMMIT  делает
        постоянными все  изменения в  базе данных,  сделанные в  течение
        текущей  транзакции  ("подтверждает"  транзакцию).   До тех пор,
        пока вы  не подтвердите  свои изменения,  другие пользователи не
        могут  их  увидеть.   ROLLBACK  заканчивает текущую транзакцию и
        отменяет  все   изменения,  сделанные   с  момента   ее  начала.
        SAVEPOINT  отмечает  текущую   точку  в  обработке   транзакции.
        Совместно с ROLLBACK, команда SAVEPOINT позволяет отменить часть
        транзакции.   SET  TRANSACTION  устанавливает  режим  транзакции
        "только чтение".  Для дополнительной информации смотрите  раздел
        "Обработка транзакций" ниже в этой главе.











4-2  Руководство пользователя и справочник по PL/SQL


Функции SQL
-----------

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

            *  AVG
            *  COUNT
            *  MAX
            *  MIN
            *  STDDEV
            *  SUM
            *  VARIANCE

        Еще две групповых  функции, GLB и  LUB, доступны лишь  в Trusted
        ORACLE.  Эти функции возвращают соответственно наибольшую нижнюю
        границу и наименьшую верхнюю границу меток операционной системы.
        Для  дополнительной  информации  обратитесь  к документу Trusted
        ORACLE7 Server Administrator's Guide.

        Вы можете использовать групповые функции в предложениях SQL,  но
        НЕ в процедурных  предложениях.  Групповые функции  оперируют на
        целых столбцах, если вы не используете предложение SELECT  GROUP
        BY, чтобы отсортировать возвращаемые строки по подгруппам.  Если
        вы  опускаете  фразу  GROUP  BY,  групповая функция трактует все
        возвращаемые строки как единственную группу.

        Вы вызываете групповую функцию, используя синтаксис

        имя_функции([ALL | DISTINCT] выражение)

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

        SELECT STDDEV(comm) INTO comm_sigma FROM emp;

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

        SELECT COUNT(DISTINCT job) INTO job_count FROM emp;

        Функция  COUNT  позволяет  специфицровать  звездочку  (*),   что
        возвращает   число   строк   в   таблице.    Например, следующее
        предложение возвращает число сотрудников в таблице emp:

        SELECT COUNT(*) INTO emp_count FROM emp;

        За исключением COUNT(*), все групповые функции игнорируют пустые
        значения.







                                            Взаимодействие с ORACLE  4-3


Псевдостолбцы SQL
-----------------

        SQL распознает следующие  псевдостолбцы SQL, которые  возвращают
        специфические элементы данных:

            *  CURRVAL

            *  LEVEL

            *  NEXTVAL

            *  ROWID

            *  ROWNUM

        Например,     NEXTVAL     возвращает     очередное      значение
        последовательности  базы  данных.   Поскольку  эти  столбцы   не
        являются действительными столбцами  в какой-нибудь таблице,  они
        называюся   ПСЕВДОСТОЛБЦАМИ.    Однако   псевдостолбцы    должны
        извлекаться  из  таблицы,   ибо  они  трактуются   как  столбцы.
        Например, вы можете выбирать значение из псевдостолбца.   Однако
        нельзя применять операции INSERT, UPDATE и DELETE по отношению к
        псевдостолбцам.

        Вы можете использовать псевдостолбцы в предложениях SQL, но НЕ в
        процедурных     предложениях.       В     следующем      примере
        последовательность базы данных empno_seq и псевдостолбец NEXTVAL
        используются для вставки нового номера сотрудника в таблицу emp:

        INSERT INTO emp VALUES (empno_seq.NEXTVAL, new_ename, ...);

        Иногда  бывает  удобно  извлекать  псевдостолбцы  из   фиктивной
        таблицы, содержащей одну строку и один столбец, например:

        DECLARE
            new_empno  NUMBER(4);
            new_ename  CHAR(10);
            ...
        BEGIN
            ...
            SELECT empno_seq.NEXTVAL INTO new_empno FROM dual;
            INSERT INTO emp VALUES (new_empno, new_ename, ...);
        END;

        Ниже   приведены    краткие   описания    псевдостолбцов.    Для
        дополнительной информации обратитесь к документу ORACLE7  Server
        SQL Language Reference Manual.















4-4  Руководство пользователя и справочник по PL/SQL


CURRVAL и NEXTVAL

        ПОСЛЕДОВАТЕЛЬНОСТЬ (sequence) - это объект базы данных,  который
        генерирует порядковые  номера.  При  создании последовательности
        вы можете специфицировать ее начальное значение и приращение.

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

        имя_последовательности.CURRVAL
        имя_последовательности.NEXTVAL

        После создания последовательности вы можете использовать ее  для
        генерации  уникальных  порядковых  номеров  для  обработки ваших
        транзакций.   Однако  вы  можете  использовать CURRVAL и NEXTVAL
        только в списке  SELECT, фразе VALUES  и фразе SET.  В следующем
        примере последовательность используется для того, чтобы вставить
        один и тот же номер сотрудника в две таблицы:

        INSERT INTO emp VALUES (empno_seq.NEXTVAL, my_ename, ...);
        INSERT INTO sals VALUES (empno_seq.CURRVAL, my_sal, ...);

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

LEVEL

        Вы  используете  LEVEL  в  предложении  SELECT CONNECT BY, чтобы
        организовать  строки   таблицы  базы   данных  в   иерархическую
        структуру.  LEVEL возвращает номер уровня узла в этой структуре.
        Корень дерева имеет уровень 1, его дети - уровень 2, и т.д.

        Вы указываете направление обхода дерева (вниз от корня или вверх
        от  ветвей)  посредством  оператора  PRIOR.   В фразе START WITH
        специфицируется условие, которое идентифицирует корень дерева.

ROWID

        ROWID возвращает идентификатор строки (двоичный адрес строки)  в
        таблице базы  данных.  Вспомните,  что PL/SQL  предоставляет тип
        данных  с  таким  же  названием  ROWID.   Вы можете использовать
        переменные  типа  ROWID  для  хранения  идентификаторов  строк в
        читабельном формате.   Например, вы  могли бы  объявить для этой
        цели переменную с именем row_id:

        DECLARE
            row_id  ROWID;
            ...








                                            Взаимодействие с ORACLE  4-5


        Выбирая  или  извлекая  идентификатор  строки  в переменную типа
        ROWID,  вы  можете  использовать  функцию  ROWIDTOCHAR,  которая
        преобразует двоичное значение  в 18-байтовую символьную  строку.
        Затем, вы  можете сравнивать  переменную ROWID  с псевдостолбцом
        ROWID  в  фразе  WHERE  предложения  UPDATE  или  DELETE,  чтобы
        идентифицировать  последнюю  строку,  извлеченную  из   курсора.
        Более того, вы можете использовать атрибут %ROWTYPE с курсорами,
        ссылающимися на псевдостолбец ROWID.  Для примеров обратитесь  к
        разделу "Извлечения между commit'ами" ниже в этой главе.

ROWNUM

        ROWNUM возвращает порядковый номер строки согласно тому порядку,
        в котором строки выбираются из таблицы.  Первая выбранная строка
        имеет  ROWNUM  1,  вторая  -  2,  и т.д.  Если строки выбираются
        предложением  SELECT,  содержащим  фразу  ORDER  BY, то значения
        ROWNUM назначаются строкам ДО ТОГО, как они будут отсортированы.

        Вы  можете  использовать  ROWNUM  в  предложении  UPDATE,  чтобы
        присваивать  уникальное  значение  каждой  строке в таблице.  Вы
        также  можете  использовать  ROWNUM  в  фразе  WHERE предложения
        SELECT, чтобы лимитировать число извлекаемых строк, например:

        DECLARE
            CURSOR c1 IS SELECT empno, sal FROM emp
                WHERE sal > 2000 AND ROWNUM < 10;  -- возвратит 10 строк
            ...

        Значение ROWNUM возрастает лишь  при извлечении строки, так  что
        единственное осмысленное  использование ROWNUM  в фразе  WHERE -
        это условие вида

        ... WHERE ROWNUM < константа;

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

        ... WHERE ROWNUM = 10;


Столбец ROWLABEL
----------------

        PL/SQL также  распознает специальный  столбец ROWLABEL,  который
        Trusted ORACLE создает для каждой таблицы базы данных.  Как и  к
        прочим  столбцам,   к  столбцу   ROWLABEL  можно   обращаться  в
        предложениях  SQL.  Однако  в  стандартном  ORACLE  этот столбец
        всегда  возвращает  NULL.   В  Trusted  ORACLE  столбец ROWLABEL
        возвращает метку операционной системы для строки.

        Типичное  использование  ROWLABEL  -  это фильтрация результатов
        запроса.  Например, следующее  предложение подсчитывает лишь  те
        строки, у которых уровень секретности выше, чем "unclassified":

        SELECT COUNT(*) INTO head_count FROM emp
            WHERE ROWLABEL > 'UNCLASSIFIED';







4-6  Руководство пользователя и справочник по PL/SQL


Операторы SQL
-------------

        PL/SQL  позволяет  вам  использовать  в  предложениях  SQL   все
        операторы SQL  сравнений, множеств  и строк.   В этом подразделе
        кратко   описаны   некоторые    из   таких   операторов.     Для
        дополнительной информации обратитесь к документу ORACLE7  Server
        SQL Language Reference Manual.

Операторы сравнения

        Как правило,  вы используете  операторы сравнения  в фразе WHERE
        предложения манипулирования данными для формирования ПРЕДИКАТОВ,
        которые сравнивают одно выражение  с другим и всегда  возвращают
        TRUE,  FALSE  или  NULL.   Для  формирования  предикатов   можно
        использовать все операторы сравнения, перечисленные ниже.  Более
        того, вы  можете комбинировать  предикаты, используя  логические
        операторы AND, OR и NOT.

        ALL             Сравнивает значение с каждым значением в  списке
                        или  в  множестве,  возвращаемом  подзапросом, и
                        возвращает   TRUE,   если   все   индивидуальные
                        сравнения дают результат TRUE.
        ANY,SOME        Сравнивает значение с каждым значением в  списке
                        или  в  множестве,  возвращаемом  подзапросом, и
                        возвращает   TRUE,   если   хотя   бы   одно  из
                        индивидуальных сравнений дает результат TRUE.
        BETWEEN         Проверяет, лежит ли значение в интервале.
        EXISTS          Возвращает TRUE, если подзапрос возвращает  хотя
                        бы одну строку.
        IN              Проверяет на членство в множестве.
        IS NULL         Проверяет на пустоту.
        LIKE            Проверяет,  удовлетворяет  ли  символьная строка
                        заданному  образцу,   который  может   содержать
                        поисковые символы.

Операторы множеств

        Операторы множеств комбинируют  результаты двух запросов  в один
        результат.   Вы  можете  использовать  все  операторы  множеств,
        включая INTERSECT, MINUS, UNION и UNION ALL.

        INTERSECT  возвращает  все  различающиеся  строки,  возвращенные
        обоими запросами.   MINUS возвращает  все различающиеся  строки,
        возвращенные первым,  но не  вторым запросом.   UNION возвращает
        все различающиеся строки, возвращенные любым из запросов.  UNION
        ALL возвращает все строки, возвращенные любым из запросов, в том
        числе все повторяющиеся строки.

Операторы строк

        Операторы строк  возвращают конкретные  строки или  обращаются к
        ним.  Вы можете использовать  все операторы строк, включая  ALL,
        DISTINCT и PRIOR.

        ALL возвращает повторяющиеся строки  в результате запроса или  в
        агрегатном выражении.  DISTINCT устраняет повторяющиеся строки в
        результате запроса или в агрегатном выражении.  PRIOR  ссылается
        на    родительскую    строку    текущей    строки,  возвращенной
        иерархическим запросом.  Вы должны использовать этот оператор  в
        фразе  CONNECT  BY  такого  запроса,  чтобы определить отношение
        подчиненности в дереве.

                                            Взаимодействие с ORACLE  4-7


Советы оптимизатору
-------------------

        Для каждого предложения  SQL оптимизатор ORACLE  генерирует ПЛАН
        ИСПОЛНЕНИЯ,  представляющий  собой  ряд  шагов,  которые  ORACLE
        предпринимает  для  выполнения  этого  предложения.  В некоторых
        случаях вы  можете предложить  ORACLE нужный  способ оптимизации
        предложения SQL. Эти рекомендации, называемые СОВЕТАМИ  (hints),
        позволяют вам влиять на решения, принимаемые оптимизатором.

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

        Вы можете использовать советы, чтобы специфицировать

            *  подход к оптимизации предложения SQL

            *  путь доступа для каждой адресуемой таблицы

            *  порядок соединения для соединений

            *  метод, используемый для соединения таблиц

        Соответственно, советы распадаются на следующие категории:

            *  Подход к оптимизации

            *  Путь доступа

            *  Порядок соединения

            *  Способ соединения

        Например, два  возможных совета  "подход к  оптимизации", COST и
        NOCOST,  вызывают   соответственно  стоимостной   оптимизатор  и
        регулярный оптимизатор.

        Вы даете  советы оптимизатору,  помещая их  в форме  комментария
        непосредственно вслед за глаголом предложения SELECT, UPDATE или
        DELETE.    Например,   в   следующем   предложении рекомендуется
        использовать стоимостной подход к оптимизации:

        SELECT /*+ COST */ ename, job, sal INTO ...

        Для дополнительной информации о советах оптимизатору  обратитесь
        к документу ORACLE7 Server Applicaton Developer's Guide.














4-8  Руководство пользователя и справочник по PL/SQL


Поддержка национальных языков
-----------------------------

        Хотя широко используемые 7- или 8-битовые наборы символов  ASCII
        и  EBCDIC  достаточны  для  представления  латинского  алфавита,
        некоторые азиатские языки,  такие как японский,  содержат тысячи
        символов.    Эти   языки   требуют   16   бит   (два  байта) для
        представления каждого символа.  Как ORACLE справляется с  такими
        нетипичными языками?

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

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

        Параметр NLS            Специфицирует...
        ----------------------------------------------------------------
        NLS_LANGUAGE            соглашения, зависящие от языка
        NLS_TERRITORY           соглашения, зависящие от территории
        NLS_DATE_FORMAT         формат даты
        NLS_DATE_LANGUAGE       язык для названий дней и месяцев
        NLS_NUMERIC_CHARACTERS  десятичный и групповой разделители
        NLS_CURRENSY            локальное обозначение валюты
        NLS_ISO_CURRENCY        обозначение валюты в стандарте ISO
        NLS_SORT                последовательность сортировки

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

            *  язык для сообщений сервера ORACLE

            *  язык для названий дней и месяцев

            *  последовательность сортировки

















                                            Взаимодействие с ORACLE  4-9


        NLS_LANGUAGE  специфицирует  умалчиваемые  значения для свойств,
        зависящих от территории, к которым относятся

            *  формат даты

            *  десятичный разделитель

            *  групповой разделитель

            *  местный символ валюты

            *  символ валюты в стандарте ISO

        Вы можете управлять выбором  зависящих от языка свойств  NLS для
        сессии пользователя, специфицируя параметр NLS_LANG:

        NLS_LANG = <язык>_<территория>.<набор символов>

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

        Вы определяете параметр NLS_LANG через переменную окружения (или
        ее эквивалент  на вашей  системе).  Например,  в системе  UNIX с
        интерпретатором команд C shell, вы могли бы определить  NLS_LANG
        следующим образом:

        setenv NLS_LANG French_France.WE8ISO8859P1

        PL/SQL полностью  поддерживает все  средства NLS,  что позволяет
        вашим приложениям обрабатывать многоязычные данные, хранящиеся в
        вашей  базе  данных  ORACLE.   Например,  вы  можете   объявлять
        символьные  переменные  для  иностранных  языков и передавать их
        строковым функциям,  таким как  INSTRB, LENGTHB  и SUBSTRB.  Эти
        функции  имеют  тот  же  самый  синтаксис,  что и функции INSTR,
        LENGTH  и  SUBSTR  соответственно,  но  оперируют побайтно, а не
        посимвольно.

        Вы  можете   использовать  функции   NLS_INITCAP,  NLS_LOWER   и
        NLS_UPPER, чтобы обрабатывать специальные случаи  преобразования
        регистра  букв.   Кроме  того,  вы  можете  использовать функцию
        NLSSORT,  чтобы   специфицировать  в   фразе  WHERE   сравнения,
        основывающиеся   на   лингвистическом,   а   не   на   двоичном,
        упорядочении.  Вы можете даже передавать параметры NLS  функциям
        TO_CHAR, TO_DATE и  TO_NUMBER.  Для дополнительной  информации о
        NLS   обратитесь   к   документу   ORACLE7   Server  Application
        Developer's Guide.












4-10  Руководство пользователя и справочник по PL/SQL


----------------

Удаленный доступ

        PL/SQL позволяет  вам обращаться  к удаленным  базам данных.   В
        следующем примере вы выполняете запрос через SQL*Net по таблице,
        находящейся  в  удаленной  базе  данных  newyork.   Этот  запрос
        предоставляется   вашему    локальному   серверу    ORACLE,   но
        "проталкивается" для выполнения в удаленную базу данных.

        BEGIN
            SELECT ename, job INTO my_ename, my_job
                FROM emp@newyork
                WHERE empno = my_empno;
            ...

        Для  более  подробной  информации  о  доступе  к удаленным базам
        данных  обратитесь  к   документу  ORACLE7  Server   Application
        Developer's Guide.

        Вы можете использовать атрибут  %TYPE для получения типа  данных
        столбца в удаленной базе данных.  Аналогично, можно использовать
        атрибут   %ROWTYPE   для   объявления   записи,   которая  будет
        представлять строку в удаленной таблице.  Например:

        DECLARE
            emp_id    emp.empno@newyork%TYPE;
            dept_rec  dept@newyork%ROWTYPE;
            ...

        Вы даже  можете вызывать  как независимые,  так и пакетированные
        подпрограммы, хранящиеся  в удаленной  базе данных  ORACLE.  Для
        дополнительной информации см. раздел "Удаленный доступ" в  главе
        6.

Прозрачность местоположения
---------------------------

        Чтобы обеспечить прозрачность  адресации объектов удаленных  баз
        данных,  таких  как  таблицы  и  обзоры,  вы  можете   создавать
        синонимы, как показывает следующий пример:

        CREATE SYNONYM emp2 FOR emp@newyork;

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

        BEGIN
            SELECT ename, job INTO my_ename, my_job
                FROM emp2  -- синоним для emp@newyork
                WHERE empno = my_empno;
            ...

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





                                           Взаимодействие с ORACLE  4-11


Глобальные имена
----------------

        РАСПРЕДЕЛЕННАЯ БАЗА ДАННЫХ - это единая логическая база  данных,
        состоящая из нескольких физических баз данных, расположенных  на
        разных узлах.   В системе  распределенной базы  данных каждая из
        баз   данных   уникально   идентифицируется   ГЛОБАЛЬНЫМ ИМЕНЕМ,
        состоящим из двух компонент.   Первая компонента - это  имя базы
        данных  длиной  не  более  8  символов (например, sales); вторая
        компонента  -  это  сетевое  имя  домена.   Рис.4-1   изображает
        воображаемый   сетевой   домен,   удовлетворяющий    соглашениям
        Internet.  Internet - это всемирная система компьютерных  сетей,
        используемых  компаниями  и  учебными  заведениями  для   обмена
        информацией.

Рис.4-1
Сетевой домен

                             COM
                              ¦
            ------------------+-----------------¬
                              ¦
                            PRIMUS
                              ¦
          --------------------+------------------------------¬
          ¦                   ¦                              ¦
        ASIA               AMERICAS                        EUROPE
          ¦                   ¦                              ¦
  --------+-¬             ----+--------------¬          -----+-----¬
            ¦             ¦                  ¦          ¦          ¦
          JAPAN          US                MEXICO     FRANCE    GERMANY
            ¦             ¦                  ¦          ¦          ¦
            ¦          ---+-------¬          ¦          ¦          ¦
            ¦          ¦          ¦          ¦          ¦          ¦
       -----+---¬ -----+---¬ -----+---¬ -----+---¬ -----+---¬ -----+---¬
       ¦  MFG   ¦ ¦ SALES  ¦ ¦  MFG   ¦ ¦ SALES  ¦ ¦ SALES  ¦ ¦ SALES  ¦
       +--------+ +--------+ +--------+ +--------+ +--------+ +--------+
       ¦ База   ¦ ¦ База   ¦ ¦ База   ¦ ¦ База   ¦ ¦ База   ¦ ¦ База   ¦
       ¦ данных ¦ ¦ данных ¦ ¦ данных ¦ ¦ данных ¦ ¦ данных ¦ ¦ данных ¦
       ¦ ORACLE ¦ ¦ ORACLE ¦ ¦ ORACLE ¦ ¦ ORACLE ¦ ¦ ORACLE ¦ ¦ ORACLE ¦
       L--------- L--------- L--------- L--------- L--------- L---------

        Для   формирования   сетевых   имен   домена   вы    используете
        последовательное квалифицирование и продвигаетесь по иерархии от
        листа к корню  дерева.  Имя в  домене сообщает вам  название или
        местоположение  сетевого  хоста   и  тип  его   организации.   В
        следующем примере хвостовой домен com говорит вам, что Primus  -
        это компания или иная коммерческая организация:

        sales.mexico.americas.primus.com

        Каждый  объект  базы  данных  уникально  идентифицируется  своим
        ГЛОБАЛЬНЫМ ИМЕНЕМ ОБЪЕКТА.  В следующем примере вы осуществляете
        запрос по удаленной таблице emp:

        BEGIN
            SELECT ename, job INTO my_ename, my_job
                FROM emp@sales.france.europe.primus.com
                WHERE empno = my_empno;
            ...



4-12  Руководство пользователя и справочник по PL/SQL


----------------

Управление курсорами

        Вспомните из главы 1,  что PL/SQL использует два  типа курсоров:
        неявные и  явные.  PL/SQL  неявно объявляет  курсор для  каждого
        предложения  манипулирования  данными  SQL,  в  том  числе   для
        запросов, возвращающих ровно одну строку.  Однако, для запросов,
        возвращающих  больше  одной  строки,  вы  должны  объявить явный
        курсор или использовать курсорный цикл FOR.

Явные курсоры
-------------

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

        Вы  определяете  курсор  в  декларативной  части  блока  PL/SQL,
        подпрограммы   или   пакета   путем   задания   его   имени    и
        специфицирования запроса.  После этого вы манипулируете курсором
        при помощи трех команд: OPEN, FETCH и CLOSE.

        Прежде  всего  вы  инициализируете  курсор  предложением   OPEN,
        которое  идентифицирует  активное  множество.   Затем  с помощью
        предложения  FETCH  вы  извлекаете  первую  строку.   Вы  можете
        повторять  FETCH  неоднократно,  пока  не  будут  извлечены  все
        строки.  После обработки последней строки вы освобождаете курсор
        предложением CLOSE.

        Вы можете обрабатывать параллельно несколько запросов, объявив и
        открыв  несколько  курсоров.   Теперь  рассмотрим описанные шаги
        более внимательно.

Объявление курсора

        Ссылки вперед недопустимы в PL/SQL.  Поэтому вы должны  объявить
        курсор,  прежде   чем  сможете   ссылаться  на   него  в  других
        предложениях.  Объявляя курсор, вы даете ему имя и  ассоциируете
        его  с  конкретным  запросом.   В  следующем примере объявляется
        курсор с именем c1:

        DECLARE
            CURSOR c1 IS SELECT ename, deptno FROM emp
                WHERE sal > 2000;
            ...
        BEGIN
            ...

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







                                           Взаимодействие с ORACLE  4-13


Параметризованные курсоры

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

        CURSOR c1 (median IN NUMBER) IS
            SELECT job, ename FROM emp WHER sal > median;

        Для  объявления   формальных  параметров   курсора  используется
        синтаксис

        CURSOR имя [ (параметр [, параметр, ...]) ] IS
            SELECT ...

        где "параметр", в свою очередь, имеет следующий синтаксис:

        имя_переменной [IN] тип_данных [{:= | DEFAULT} значение]

        Формальные  параметры   курсора  должны   иметь  моду   IN.  Для
        обсуждения мод параметров обратитесь к разделу "Моды параметров"
        в главе 6.

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

        DECLARE
            CURSOR c1
                (low   INTEGER DEFAULT 0,
                 high  INTEGER DEFAULT 99) IS SELECT ...
            ...

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






















4-14  Руководство пользователя и справочник по PL/SQL


Открытие курсора

        Открытие курсора предложением OPEN исполняет предложение  SELECT
        и   идентифицирует   АКТИВНОЕ   МНОЖЕСТВО,   т.е.   все  строки,
        удовлетворяющие  поисковым  условиям  запроса.   Для   курсоров,
        объявленных  с  фразой   FOR  UPDATE,  предложение   OPEN  также
        осуществляет блокировку этих строк.  Пример предложения OPEN:

        OPEN c1;

        Предложение OPEN  не извлекает  строк активного  множества.  Для
        этого используется предложение FETCH.

Передача параметров

        Курсору могут быть  переданы параметры при  открытии.  Например,
        при объявлении курсора

        CURSOR c1 (my_ename CHAR, my_comm NUMBER) IS SELECT ...

        любое из следующих предложений открывает этот курсор:

        OPEN c1('ATTLEY', 300);
        OPEN c1(employee_name, 150);
        OPEN c1('THURSTON', my_comm);

        В последнем примере переменная, специфицированная в  предложении
        OPEN, имеет такое же имя,  что и параметр в объявлении  курсора.
        Когда идентификатор my_comm  используется в объявлении  курсора,
        он  обозначает  формальный  параметр  курсора.   Когда  этот  же
        идентификатор используется вне объявления курсора, он обозначает
        переменную  PL/SQL   с  этим   именем.   Однако,   для  ясности,
        рекомендуется использовать уникальные идентификаторы.

        Если  вы  не  хотите  принять  умалчиваемые  значения,   каждому
        формальному параметру в  объявлении курсора следует  сопоставить
        соответствующий   фактический   параметр   в   предложении OPEN.
        Формальные  параметры,  объявленные  с  умалчиваемым  значением,
        могут и не иметь  соответствующих им фактических параметров.   В
        этом случае они просто  принимают свое умалчиваемое значение  во
        время выполнения OPEN.   Не забывайте, что  формальные параметры
        курсора  должны  быть  параметрами  IN,  так  что  они  не могут
        возвращать значений фактическим параметрам.

        Чтобы  сопоставить  фактические  параметры  в  предложении  OPEN
        формальным   параметрам   в   объявлении   курсора,   вы  можете
        использовать  позиционную  или  именную  нотацию.   (См.  раздел
        "Позиционная и именная нотация" в главе 6.)  Каждый  фактический
        параметр должен  иметь тип  данных, совместимый  с типом  данных
        соответствующего формального параметра.













                                           Взаимодействие с ORACLE  4-15


Извлечение данных из курсора

        Предложение  FETCH  извлекает  очередную  строку  из   активного
        множества.  При  каждом выполнении  FETCH курсор  продвигается к
        следующей  строке  в  активном  множестве.   Пример  предложения
        FETCH:

        FETCH c1 INTO my_empno, my_ename, my_deptno;

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

        ...
        OPEN c1;
        LOOP
            FETCH c1 INTO my_record;
            EXIT WHEN c1%NOTFOUND;
            -- обработать извлеченные данные
        END LOOP;

        Любые  переменные  в  фразе  WHERE  запроса,  ассоциированного с
        курсором,  вычисляются  лишь  в  момент  открытия  курсора.  Как
        показывает   следующий   пример,   запрос   может   обращаться к
        переменным PL/SQL внутри своей сферы:

        DECLARE
            my_sal  emp.sal%TYPE;
            my_job  emp.job%TYPE;
            factor  INTEGER := 2;
            CURSOR c1 IS
                SELECT factor*sal FROM emp WHERE job = my_job;
        BEGIN
            ...
            OPEN c1;  -- здесь factor равен 2
            LOOP
                FETCH c1 INTO my_sal;
                EXIT WHEN c1%NOTFOUND;
                ...
                factor := factor + 1;  -- не окажет влияния на FETCH
            END LOOP;
        END;

        В этом примере каждое извлекаемое значение sal будет  умножаться
        на 2, несмотря  на то, что  значение factor увеличивается  после
        каждой операции  FETCH.  Чтобы  изменить активное  множество или
        значения  переменных  в  запросе,  вы  должны  закрыть  и заново
        открыть курсор с новыми значениями входных параметров.













4-16  Руководство пользователя и справочник по PL/SQL


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

        DECLARE
            CURSOR c1 IS SELECT ename FROM emp;
            name1  emp.ename%TYPE;
            name2  emp.ename%TYPE;
            name3  emp.ename%TYPE;
        BEGIN
            OPEN c1;
            FETCH c1 INTO name1;  -- извлекает первую строку
            FETCH c1 INTO name2;  -- извлекает вторую строку
            FETCH c1 INTO name3;  -- извлекает третью строку
            ...
            CLOSE c1;
        END;

        Если вы выдаете FETCH, но в активном множестве больше нет строк,
        то значения переменных в списке INTO не определены.

Закрытие курсора

        Предложение  CLOSE  деактивирует  курсор,  и  активное множество
        становится неопределенным.  Пример предложения CLOSE:

        CLOSE c1;

        После  того,  как  курсор  закрыт,  вы можете снова открыть его.
        Любая   иная   операция    на   закрытом   курсоре    возбуждает
        предопределенное     исключение     INVALID_CURSOR,      которое
        соответствует ошибке ORACLE с кодом ORA-01001.






























                                           Взаимодействие с ORACLE  4-17


Атрибуты явного курсора
-----------------------

        Каждый  курсор,  явно  объявленный  вами, имеет четыре атрибута:
        %NOTFOUND, %FOUND, %ROWCOUNT и %ISOPEN.  Атрибуты позволяют  вам
        получать   полезную   информацию   о   выполнении многострочного
        запроса.  Для обращения к атрибуту просто присоедините его имя к
        имени  курсора.   Атрибуты  явного  курсора можно использовать в
        процедурных предложениях, но не в предложениях SQL.


Использование %NOTFOUND

        Когда  курсор  открыт,  строки, удовлетворяющие ассоциированному
        запросу, идентифицированы  и образуют  активное множество.   Эти
        строки  извлекаются  операцией  FETCH  по  одной  за  раз.  Если
        последняя операция FETCH  вернула строку, %NOTFOUND  дает FALSE.
        Если последняя операция FETCH не смогла вернуть строку (так  как
        активное множество  исчерпано), %NOTFOUND  дает TRUE.   Операция
        FETCH должна  в конце  концов исчерпать  активное множество, так
        что, когда это происходит, никакого исключения не возбуждается.

        В следующем  примере вы  используете %NOTFOUND,  чтобы выйти  из
        цикла, когда FETCH не сможет вернуть строку:

        LOOP
            FETCH c1 INTO my_ename, my_deptno;
            EXIT WHEN c1%NOTFOUND;
            ...
        END LOOP;

        Перед  первой  операцией  FETCH  атрибут  %NOTFOUND  дает  NULL.
        Поэтому, если FETCH ни разу не выполнится успешно, вы никогда не
        выйдете из  этого цикла.   Причина в  том, что  предложение EXIT
        WHEN выполняется только  в том случае,  когда условие WHEN  дает
        TRUE.   Поэтому  для  безопасности  вы  можете предпочесть такой
        вариант предложения EXIT:

        EXIT WHEN c1%NOTFOUND OR c1%NOTFOUND IS NULL;

        Вы  можете  открыть  несколько  курсоров,  а  затем использовать
        %NOTFOUND, чтобы  проверять, в  каких курсорах  еще есть строки.
        Если  курсор  не  открыт,  обращение  к  нему  через   %NOTFOUND
        возбуждает предопределенное исключение INVALID_CURSOR.



















4-18  Руководство пользователя и справочник по PL/SQL


Использование %FOUND

        %FOUND  логически  противоположен  атрибуту  %NOTFOUND.    После
        открытия явного  курсора, но  до первой  операции FETCH,  %FOUND
        дает NULL.  Впоследствии он  дает TRUE, если последняя  операция
        FETCH вернула строку, или  FALSE, если последняя операция  FETCH
        не смогла извлечь строку, так как больше нет доступных строк.

        Следующий пример использует %FOUND,  чтобы выбрать одно из  двух
        альтернативных действий:

        LOOP
            FETCH c1 INTO my_ename, my_deptno;
            IF c1%FOUND THEN  -- извлечение успешно
                INSERT INTO temp VALUES (...);
            ELSE
                EXIT;
            ...
        END LOOP;

        Вы  можете  открыть  несколько  курсоров,  а  затем использовать
        %FOUND, чтобы проверять, в каких курсорах еще есть строки.  Если
        курсор  не  открыт,  обращение  к  нему  через %FOUND возбуждает
        предопределенное исключение INVALID_CURSOR.

Использование %ROWCOUNT

        Когда вы  открываете курсор,  его атрибут  %ROWCOUNT обнуляется.
        Перед   первой   операцией   FETCH   %ROWCOUNT   возвращает   0.
        Впоследствии,  %ROWCOUNT  возвращает  число  строк,  извлеченных
        операциями FETCH из активного  множества на данный момент.   Это
        число увеличивается, если последняя FETCH вернула строку.

        Следующий   пример   использует   %ROWCOUNT,   чтобы предпринять
        определенные действия, если выбрано более 10 строк:

        LOOP
            FETCH c1 INTO my_ename, my_deptno;
            IF c1%ROWCOUNT > 10 THEN
                -- выбрано больше 10 строк
                ...
            END IF;
        END LOOP;

        Вы  можете  открыть  несколько  курсоров,  а  затем использовать
        %ROWCOUNT, чтобы проверять,  сколько строк извлечено  из каждого
        курсора.   Если  курсор  не  открыт,  обращение  к  нему   через
        %ROWCOUNT возбуждает предопределенное исключение INVALID_CURSOR.

Использование %ISOPEN

        %ISOPEN дает TRUE, если явный курсор открыт, и FALSE в противном
        случае.   Следующий   пример  использует   %ISOPEN  для   выбора
        действия:

        IF с1%ISOPEN THEN  -- курсор открыт
            ...
        ELSE  -- курсор закрыт, открыть его
            OPEN c1;
        END IF;



                                           Взаимодействие с ORACLE  4-19


        Таблица 4-1 показывает значения, возвращаемые атрибутами  явного
        курсора перед и после  выполнения операций OPEN, FETCH  и CLOSE.
        Обратите  внимание,  что  перед  первой операцией FETCH атрибуты
        %NOTFOUND и %FOUND возвращают NULL на открытом курсоре.

Табл.4-1
Значения атрибутов курсора

                           ------------T--------T-----------T----------¬
                           ¦ %NOTFOUND ¦ %FOUND ¦ %ROWCOUNT ¦ %ISOPEN  ¦
        -----------T-------+-----------+--------+-----------+----------+
        ¦          ¦ Перед ¦ *         ¦ *      ¦ *         ¦ FALSE    ¦
        ¦ OPEN     +-------+-----------+--------+-----------+----------+
        ¦          ¦ После ¦ NULL      ¦ NULL   ¦ 0         ¦ TRUE     ¦
        +----------+-------+-----------+--------+-----------+----------+
        ¦ Первая   ¦ Перед ¦ NULL      ¦ NULL   ¦ 0         ¦ TRUE     ¦
        ¦ FETCH    +-------+-----------+--------+-----------+----------+
        ¦          ¦ После ¦ FALSE     ¦ TRUE   ¦ 1         ¦ TRUE     ¦
        +----------+-------+-----------+--------+-----------+----------+
        ¦ Промежу- ¦ Перед ¦ FALSE     ¦ TRUE   ¦ 1         ¦ TRUE     ¦
        ¦ точные   +-------+-----------+--------+-----------+----------+
        ¦ FETCH    ¦ После ¦ FALSE     ¦ TRUE   ¦ **        ¦ TRUE     ¦
        +----------+-------+-----------+--------+-----------+----------+
        ¦ Последняя¦ Перед ¦ FALSE     ¦ TRUE   ¦ **        ¦ TRUE     ¦
        ¦ FETCH    +-------+-----------+--------+-----------+----------+
        ¦          ¦ После ¦ TRUE      ¦ FALSE  ¦ **        ¦ TRUE     ¦
        +----------+-------+-----------+--------+-----------+----------+
        ¦          ¦ Перед ¦ TRUE      ¦ FALSE  ¦ **        ¦ TRUE     ¦
        ¦ CLOSE    +-------+-----------+--------+-----------+----------+
        ¦          ¦ После ¦ *         ¦ *      ¦ *         ¦ FALSE    ¦
        L----------+-------+-----------+--------+-----------+-----------

                *  Возбуждает предопределенное исключение INVALID_CURSOR
                ** Зависит от данных





























4-20  Руководство пользователя и справочник по PL/SQL


Несколько примеров
------------------

        Предположим, что в  таблице data_table хранятся  данные (числа),
        собранные  в  ходе  лабораторных  экспериментов,  и  вы   хотите
        проанализировать данные из эксперимента 1.  В следующем  примере
        вы вычисляете результаты и помещаете их в таблицу базы данных  с
        именем temp:

        -- доступен на диске в файле EXAMP5
        DECLARE
            num1    data_table.n1%TYPE;  -- Объявить переменные
            num2    data_table.n2%TYPE;  -- с теми же типами,
            num3    data_table.n3%TYPE;  -- что и столбцы таблицы
            result  temp.col1%TYPE;
            CURSOR c1 IS
                SELECT n1, n2, n3 FROM data_table
                    WHERE exper_num = 1;
        BEGIN
            OPEN c1;
            LOOP
                FETCH c1 INTO num1, num2, num3;
                EXIT WHEN c1%NOTFOUND;
                    -- условие c1%NOTFOUND будет равно TRUE,
                    -- когда FETCH не обнаружит больше строк
                    /* вычислить и сохранить результаты */
                result := num2/(num1 + num3);
                INSERT INTO temp VALUES (result, NULL, NULL);
            END LOOP;
            CLOSE c1;
            COMMIT;
        END;































                                           Взаимодействие с ORACLE  4-21


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

        -- доступен на диске в файле EXAMP6
        DECLARE
            CURSOR bin_cur(part_number NUMBER) IS SELECT amt_in_bin
                FROM bins
                WHERE part_num = part_number AND
                    amt_in_bin > 0
                    ORDER BY bin_num
                    FOR UPDATE OF amt_in_bin;
            bin_amt           bins.amt_in_bin%TYPE;
            total_so_far      NUMBER(5) := 0;
            amount_needed     CONSTANT NUMBER(5) := 1000;
            bins_looked_at    NUMBER(3) := 0;
        BEGIN
            OPEN bin_cur(5469);
            WHILE total_so_far < amount_needed LOOP
                FETCH bin_cur INTO bin_amt;
                EXIT WHEN bin_cur%NOTFOUND;
                    /* If we exit, there's not enough to *
                     * satisfy the order.                */
                bins_looked_at := bins_looked_at + 1;
                IF total_so_far + bin_amt < amount_needed THEN
                    UPDATE bins SET amt_in_bin = 0
                        WHERE CURRENT OF bin_cur;
                            -- take everything in the bin
                    total_so_far := total_so_far + bin_amt;
                ELSE        -- we finally have enough
                    UPDATE bins SET amt_in_bin = amt_in_bin
                        - (amount_needed - total_so_far)
                        WHERE CURRENT OF bin_cur;
                    total_so_far := amount_needed;
                END IF;
            END LOOP;
            CLOSE bin_cur;
            INSERT INTO temp VALUES (null, bins_looked_at, '<- bins
                looked at');
            COMMIT;
        END;






















4-22  Руководство пользователя и справочник по PL/SQL


Неявные курсоры
---------------

        ORACLE неявно открывает курсор для обработки каждого предложения
        SQL, не  ассоциированного с  явно объявленным  курсором.  PL/SQL
        позволяет вам обращаться к последнему открытому неявному курсору
        через  имя  "SQL".   Поэтому,  хотя  вы не можете манипулировать
        неявным курсором посредством предложений OPEN, FETCH и CLOSE, вы
        можете  опрашивать  атрибуты   этого  курсора,  чтобы   получить
        полезную информацию о последней выполненной операции SQL.

Атрибуты неявного курсора
-------------------------

        Курсор SQL имеет четыре атрибута: %NOTFOUND, %FOUND, %ROWCOUNT и
        %ISOPEN.  Эти  атрибуты, присоединяемые  к имени  курсора (SQL),
        дают вам  доступ к  информации о  выполнении предложений INSERT,
        UPDATE,  DELETE  и  однострочных  предложений  SELECT  INTO.  Вы
        можете  использовать  атрибуты  неявного  курсора  в процедурных
        предложениях, но не в предложениях SQL.

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

        UPDATE parts SET qty = qty - 1 WHERE partno = part_id;
        check_parts;  -- вызов процедуры
        IF SQL%NOTFOUND THEN  -- рискованно!
            ...
        END IF;

        В этом примере рискованно  полагаться на условие IF,  потому что
        процедура  ckeck_parts,  возможно,  изменила значение %NOTFOUND.
        Вы  можете  исправить  этот  код  следующим  образом,  объявив в
        декларативной части булевскую переменную sql_notfound:

        UPDATE parts SET qty = qty - 1 WHERE partno = part_id;
        sql_notfound := SQL%NOTFOUND;
        check_parts;
        IF sql_notfound THEN
            ...
        END IF;

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














                                           Взаимодействие с ORACLE  4-23


Использование %NOTFOUND

        %NOTFOUND  дает  TRUE,  если   INSERT,  UPDATE  или  DELETE   не
        обработала ни  одной строки,  или если  операция SELECT  INTO не
        возвратила ни одной строки.   В противном случае %NOTFOUND  дает
        FALSE.   В  следующем  примере  вы  используете %NOTFOUND, чтобы
        вставить новую строку, если операция обновления не нашла строки:

        UPDATE emp SET sal = sal * 1.05 WHERE empno = my_empno;
        IF SQL%NOTFOUND THEN  -- обновление не прошло
            INSERT INTO temp VALUES (...);
        END IF;

        Если  предложение  SELECT  INTO  не  возвращает ни одной строки,
        возбуждается    предопределенное    исключение    NO_DATA_FOUND,
        независимо  от  того,  проверяете  ли  вы  %NOTFOUND в следующей
        строке или нет.  Рассмотрим следующий пример:

        DECLARE
            my_sal    NUMBER(7,2);
            my_empno  NUMBER(4);
        BEGIN
            ...
            SELECT sal INTO my_sal FROM emp WHERE empno = my_empno;
            -- может возбудить исключение NO_DATA_FOUND
            IF SQL%NOTFOUND THEN  -- получит управление лишь при FALSE
                ...  -- эти действия никогда не будут выполнены
            END IF;
        EXCEPTION
            ...
        END;

        Здесь  проверка  бессмысленна,  потому  что  условие  IF   будет
        проверяться  лишь  тогда,  когда  %NOTFOUND  даст  FALSE.  Когда
        возбуждается  исключение  NO_DATA_FOUND,  нормальное  выполнение
        останавливается,  и  управление  передается  на  часть обработки
        исключений блока.  В такой ситуации атрибут %NOTFOUND полезен  в
        обработчике исключений OTHERS, как показывает следующий  пример.
        Вместо   написания   обработчика   исключения   NO_DATA_FOUND вы
        узнаете, что  это исключение  было возбуждено,  проверяя атрибут
        %NOTFOUND.

        DECLARE
            my_sal    NUMBER(7,2);
            my_empno  NUMBER(4);
        BEGIN
            ...
            SELECT sal INTO my_sal FROM emp WHERE empno = my_empno;
            -- может возбудить исключение NO_DATA_FOUND
        EXCEPTION
            WHEN OTHERS THEN
                IF SQL%NOTFOUND THEN  -- проверка на 'no data found'
                    ...
                END IF;
                ...
        END;







4-24  Руководство пользователя и справочник по PL/SQL


        Однако, предложение  SELECT INTO,  использующее вызов  групповой
        функции  SQL,  никогда  не  возбуждает исключение NO_DATA_FOUND.
        Дело в том,  что групповые функции  SQL, такие как  AVG, MAX или
        SUM, ВСЕГДА  возвращают значение,  даже если  это значение  есть
        NULL.  В таких случаях %NOTFOUND всегда дает FALSE.   Рассмотрим
        следующий пример:

        DECLARE
            my_sal     NUMBER(7,2);
            my_deptno  NUMBER(4);
        BEGIN
           ...
           SELECT MAX(sal) INTO my_sal FROM emp WHERE empno = my_deptno;
             -- никогда не возбудит NO_DATA_FOUND
           IF SQL%NOTFOUND THEN -- всегда равен FALSE
                ...  -- это действие никогда не будут выполнено
            END IF;
        EXCEPTION
            WHEN NO_DATA_FOUND THEN  -- никогда не вызывается
                ...
        END;

Использование %FOUND

        %FOUND  логически  противоположен  атрибуту  %NOTFOUND.    Перед
        выполнением  предложения  манипулирования  данными SQL SQL%FOUND
        дает NULL.  Впоследствии, этот атрибут дает TRUE, если  операция
        INSERT, UPDATE  или DELETE  затронула хотя  бы одну  строку, или
        если  операция  SELECT  INTO  вернула  хотя  бы  одну строку.  В
        противном случае SQL%FOUND дает  FALSE.  В следующем примере  вы
        используете %FOUND,  чтобы вставить  строку при  успехе операции
        удаления:

        DELETE FROM temp_emp WHER empno = my_empno;
        IF SQL%FOUND THEN  -- удаление успешно
            INSERT INTO emp VALUES (my_empno, my_empname, ...);
        END IF;

Использование %ROWCOUNT

        SQLROWCOUNT возвращает  число строк,  на которое  воздействовала
        операция   INSERT,   UPDATE   или   DELETE,   или   число строк,
        возвращенных операцией SELECT INTO. %ROWCOUNT возвращает 0, если
        операция INSERT, UPDATE или DELETE не затронула ни одной строки,
        или если  операция SELECT  INTO не  возвратила ни  одной строки.
        Следующий   пример   использует   %ROWCOUNT,   чтобы предпринять
        определенные действия, если было удалено более 10 строк:

        DELETE FROM emp WHERE ...
        IF SQL%ROWCOUNT > 10 THEN
            -- удалено больше 10 строк
            ...
        END IF;










                                           Взаимодействие с ORACLE  4-25


        Если  операция  SELECT  INTO  возвращает  более одной строки, то
        возбуждается   предопределенное   исключение   TOO_MANY_ROWS,  а
        атрибут %ROWCOUNT  устанавливается в  1, а  НЕ в  действительное
        число  строк,   удовлетворяющих  запросу.    В  такой   ситуации
        %ROWCOUNT   полезен   в   обработчике   исключений   OTHERS, как
        показывает  следующий  пример.   Вместо  написания   обработчика
        исключения  TOO_MANY_ROWS  вы  узнаете,  что это исключение было
        возбуждено, проверяя атрибут %ROWCOUNT.

        DECLARE
            my_sal    NUMBER(7,2);
            my_ename  CHAR(10);
        BEGIN
            ...
            SELECT sal INTO my_sal FROM emp WHERE ename = my_ename;
            -- может возбудить исключение TOO_MANY_ROWS
            ...
        EXCEPTION
            WHEN OTHERS THEN
                IF SQL%ROWCOUNT > 0 THEN  -- проверка на 'too many rows'
                    ...
                END IF;
                ...
        END;

Использование %ISOPEN

        ORACLE автоматически закрывает  неявный курсор после  выполнения
        связанной с ним операции SQL. Поэтому атрибут SQL%ISOPEN  всегда
        возввращает FALSE.


Пакетированные курсоры
----------------------

        При помещении  курсора в  пакет вы  можете отделить спецификацию
        курсора  от  его  тела,  используя  фразу RETURN, как показывает
        следующий пример:

        CREATE PACKAGE emp_actions AS
            /* Объявить спецификацию курсора. */
            CURSOR c1 RETURN emp%ROWTYPE;
            ...
        END emp_actions;

        CREATE PACKAGE BODY emp_actions AS
            /* Определить тело курсора. */
            CURSOR c1 RETURN emp%ROWTYPE
                SELECT * FROM emp WHERE sal > 3000;
            ...
        END emp_actions;












4-26  Руководство пользователя и справочник по PL/SQL


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

        CURSOR c1 RETURN emp%ROWTYPE
            SELECT * FROM emp WHERE deptno = 20;

        В спецификации  курсора отсутствует  предложение SELECT,  потому
        что  фразы  RETURN  достаточно,  чтобы  определить  тип   данных
        результирующего значения.  Вы можете использовать в фразе RETURN
        атрибут  %ROWTYPE,  чтобы  указать  тип  записи,  которая  будет
        представлять  строку  в  таблице  базы  данных.  Вы также можете
        использовать в  фразе RETURN  атрибут %TYPE,  чтобы указать  тип
        данных переменной, константы или столбца базы данных.

        Тело курсора должно содержать предложение SELECT, а также  точно
        такую  же  фразу  RETURN,  как  и в спецификации курсора.  Более
        того, количество  и типы  данных элементов  списка в предложении
        SELECT должны соответствовать фразе RETURN.


Курсорные циклы FOR
-------------------

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

        Рассмотрим следующий  блок PL/SQL,  который анализирует  данные,
        собранные  в   ходе  лабораторных   экспериментов,  и   помещает
        результаты во временную таблицу.  Переменная c1rec, используемая
        как индекс в курсорном цикле FOR, неявно объявляется как запись,
        хранящая все элементы данных, возвращаемые одной операцией FETCH
        для курсора c1.  Вы обращаетесь к элементам данных, хранящимся в
        полях записи, используя квалифицированные ссылки.

        -- доступен на диске в файле EXAMP7
        DECLARE
            result  temp.col1%TYPE;
            CURSOR c1 IS
                SELECT n1, n2, n3 FROM data_table
                    WHERE exper_num = 1;
        BEGIN
            FOR c1rec IN c1 LOOP
                /* вычислить и сохранить результаты */
                result := c1rec.n2 / (c1rec.n1 + c1rec.n3);
                INSERT INTO temp VALUES (result, NULL, NULL);
            END LOOP;
            COMMIT;
        END;









                                           Взаимодействие с ORACLE  4-27


        Перед каждой  итерацией курсорного  цикла FOR,  PL/SQL извлекает
        данные  в  неявно   объявленную  запись,  которая   эквивалентна
        следующей явно объявленной записи:

        c1rec  c1%ROWTYPE;

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

        BEGIN
            ...
            FOR c1rec IN c1 LOOP
                ...
            END LOOP;
            result := c1rec.n2 + 3;  -- незаконно
        END;

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

Использование алиасов

        Поля в неявно объявленной  записи курсорного цикла FOR  содержат
        значения  столбцов  из  последней  извлеченной строки.  Эти поля
        имеют такие  же имена,  что и  соответствующие столбцы  в списке
        SELECT запроса.  Однако что, если элемент списка SELECT является
        выражением?  Рассмотрим следующий пример:

        CURSOR c1 IS
            SELECT empno, sal+NVL(comm,0), job FROM ...

        В  таких  случаях  вы  должны  предоставлять  алиас для элемента
        списка SELECT.   В следующем  примере для  вычисляемого элемента
        sal+NVL(comm,0) используется алиас wages:

        CURSOR c1 IS
            SELECT empno, sal+NVL(comm,0) wages, job FROM ...

        При обращениях  к соответствующему  полю вы  используете алиас в
        квалифицированной ссылке, например:

        IF emp_rec.wages < 1000 THEN ...
















4-28  Руководство пользователя и справочник по PL/SQL


Передача параметров

        Вы  можете   передавать  параметры   курсору,  используемому   в
        курсорном  цикле  FOR.  В  следующем  примере  передается  номер
        отдела.   Здесь  вычисляется  общий  заработок  (жалованье  плюс
        комиссионные) для  сотрудников указанного  отдела.  Кроме  того,
        подсчитывается, сколько сотрудников имеют жалованье выше  $2000,
        и сколько имеют комиссионные, превышающие жалованье.

        -- доступен на диске в файле EXAMP8
        DECLARE
            CURSOR emp_cursor(dnum NUMBER) IS
                SELECT sal, comm FROM emp WHERE deptno = dnum;
            total_wages    NUMBER(11,2) := 0;
            high_paid      NUMBER(4) := 0;
            higher_comm    NUMBER(4) := 0;
        BEGIN
            /* Число итераций будет равно числу строк, *
             * возвращенных курсором emp_cursor.       */
            FOR emp_record IN emp_cursor(20) LOOP
                emp_record.comm := NVL(emp_record.comm, 0);
                total_wages := total_wages + emp_record.sal +
                    emp_record.comm;
                IF emp_record.sal > 2000.00 THEN
                    high_paid := high_paid + 1;
                END IF;
                IF emp_record.comm > emp_record.sal THEN
                    higher_comm := higher_comm + 1;
                END IF;
            END LOOP;
            INSERT INTO temp VALUES (high_paid, higher_comm,
                'Total Wages: ' || TO_CHAR(total_wages));
            COMMIT;
        END;

----------------

Обработка транзакций

        Первое предложение  SQL в  вашей программе  начинает транзакцию.
        Когда одна транзакция  заканчивается, очередное предложение  SQL
        автоматически  начинает  следующую  транзакцию.   Таким образом,
        каждое предложение SQL является частью некоторой транзакции.

        Предложения COMMIT и ROLLBACK  гарантируют, что все изменения  в
        базе  данных,  осуществленные  операциями  SQL,  либо становятся
        постоянными,  либо  отменяются  единовременно.   Все предложения
        SQL, выполненные после  последней операции COMMIT  или ROLLBACK,
        составляют текущую транзакцию.  Предложение SAVEPOINT отмечает и
        именует текущую точку в обработке транзакции.













                                           Взаимодействие с ORACLE  4-29


Распределенные транзакции
-------------------------

        РАСПРЕДЕЛЕННАЯ  ТРАНЗАКЦИЯ   включает  по   меньшей  мере   одно
        предложение   SQL,   изменяющее   данные   в   нескольких  узлах
        распределенной базы  данных.  Если  обновление затрагивает  лишь
        один узел, транзакция удаленная, но не распределенная.

        Если  часть  распределенной  транзакции  сбивается,  вы   должны
        выполнить откат  всей транзакции  или откат  к точке сохранения.
        ORACLE в такой ситуации выдает сообщение об ошибке.  Поэтому  вы
        должны  включать  проверки   на  ошибки  в   каждое  приложение,
        выполняющее  распределенные   транзакции.   Для   дополнительной
        информации  обратитесь  к  документу  ORACLE7 Server Application
        Developer's Guide.

Использование COMMIT
--------------------

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

        Рассмотрим  простую  транзакцию,  которая  осуществляет  перевод
        денег  с  одного  банковского  счета  на другой.  Эта транзакция
        требует двух операций обновления (UPDATE), потому что она должна
        дебитовать один счет  и кредитовать другой.   После кредитования
        второго  счета  вы  выдаете  команду  COMMIT,  делая   изменения
        постоянными.  Лишь после этого новое состояние счетов становится
        видимым другим пользователям.

        BEGIN
            ...
            UPDATE accts SET bal = my_bal - debit
                WHERE acctno = 7715;
            ...
            UPDATE accts SET bal = my_bal + credit
                WHERE acctno = 7720;
            COMMIT WORK;
        END;

        Необязательное ключевое  слово WORK  не имеет  никакого эффекта,
        помимо улучшения читабельности.

        Предложение COMMIT  освобождает все  блокировки таблиц  и строк.
        Оно  также  стирает  все  точки  сохранения  (обсуждаемые ниже),
        отмеченные после последней операции COMMIT или ROLLBACK.

        Фраза COMMENT позволяет вам специфицировать комментарий, который
        ассоциируется с  распределенной транзакцией.   Когда вы  выдаете
        COMMIT, постоянными становятся  изменения во всех  базах данных,
        затронутых распределенной  транзакцией.  Однако,  если во  время
        выполнения  COMMIT  произойдет  сбой  сети или машины, состояние
        распределенной  транзакции   может  оказаться   неизвестным  или
        СОМНИТЕЛЬНЫМ  (in  doubt).   В  таком  случае ORACLE сохраняет в
        словаре данных текст, специфицированный в фразе COMMENT,  вместе
        с  идентификатором  транзакции.   Текст  должен быть литералом в
        апострофах длиной не более 50 символов, например:

        COMMIT COMMENT 'Сомнительная транзакция; уведомите отдел D.E.');

4-30  Руководство пользователя и справочник по PL/SQL


Использование ROLLBACK
----------------------

        Предложение  ROLLBACK  противоположно  COMMIT.   Оно заканчивает
        текущую транзакцию и  отменяет все изменения,  осуществленные за
        время  этой  транзакции.   Предложение  ROLLBACK полезно по двум
        причинам.  Во-первых, если вы сделали ошибку, например,  удалили
        не ту строку из базы данных, вы можете использовать ROLLBACK для
        восстановления  первоначальных  данных.   Вариант  ROLLBACK   TO
        позволяет  вам  отменить  изменения  до  промежуточной  точки  в
        текущей  транзакции,  так  что  вы  не  обязаны стирать все ваши
        изменения.

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

        Рассмотрим следующий пример, в котором вы вставляете  информацию
        о  сотруднике  в  три  различных  таблицы  базы данных.  Все три
        таблицы   имеют   столбец,   содержащий   номер   сотрудника   и
        ограничиваемый  уникальным  индексом.   Если  предложение INSERT
        пытается вставить  повторяющийся номер  сотрудника, возбуждается
        предопределенное исключение DUP_VAL_ON_INDEX.  В этом случае вам
        необходимо отменить все изменения.  Поэтому вы выдаете  ROLLBACK
        в обработчике исключений.

        DECLARE
            emp_id  INTEGER;
            ...
        BEGIN
            SELECT empno, ... INTO emp_id, ... FROM new_emp WHERE ...
            ...
            INSERT INTO emp VALUES (emp_id, ...);
            INSERT INTO tax VALUES (emp_id, ...);
            INSERT INTO pay VALUES (emp_id, ...);
            ...
        EXCEPTION
            WHEN DUP_VAL_ON_INDEX THEN
                ROLLBACK;
            ...
        END;

        Фраза COMMENT позволяет вам специфицировать комментарий, который
        ассоциируется с  распределенной транзакцией.   Когда вы  выдаете
        ROLLBACK, отменяются изменения во всех базах данных,  затронутых
        распределенной транзакцией.   Однако, если  во время  выполнения
        ROLLBACK   произойдет   сбой   сети   или   машины,    состояние
        распределенной  транзакции   может  оказаться   неизвестным  или
        сомнительным.  В таком случае ORACLE сохраняет в словаре  данных
        текст,   специфицированный   в    фразе   COMMENT,   вместе    с
        идентификатором  транзакции.   Текст  должен  быть  литералом  в
        апострофах длиной не более 50 символов, например:

       ROLLBACK COMMENT 'In-doubt sales transaction; notify Sales Dept';
            ('Сомнительная транзакция; уведомите отдел продаж')





                                           Взаимодействие с ORACLE  4-31


Откаты на уровне предложений

        Прежде  чем  исполнять  предложение  SQL,  ORACLE выдает неявную
        точку сохранения.  Затем, если это предложение сбивается, ORACLE
        автоматически выполняет его  откат.  Например, если  предложение
        INSERT  пытается  вставить  повторяющееся  значение в уникальный
        индекс,  оно  откатывается.   При  этом  теряется  лишь  работа,
        начатая сбившимся  предложением SQL;  вся работа,  проделанная в
        текущей транзакции до этого момента, не затрагивается.

        ORACLE может также предпринимать откат одиночных предложений SQL
        с  целью  предотвратить  мертвые  захваты (взаимоблокировки).  В
        таких случаях ORACLE сигнализирует об ошибке одной из транзакций
        и выполняет откат текущего предложения SQL в этой транзакции.

        Прежде чем  исполнять предложение  SQL, ORACLE  должен выполнить
        его РАЗБОР (parse), т.е.  исследовать его, чтобы убедиться,  что
        оно  синтаксически  корректно  и  ссылается  на   действительные
        объекты  базы  данных.   Ошибки,  обнаруженные  во время разбора
        предложения (в отличие ошибок во время выполнения) не приводят к
        откату.

Использование SAVEPOINT

        SAVEPOINT отмечает и именует текущую точку (точку сохранения)  в
        процессе транзакции.   Такая точка,  используемая в  предложении
        ROLLBACK TO, позволяет  отменить часть транзакции.   В следующем
        примере вы отмечаете точку  сохранения перед тем, как  выполнять
        вставку  строки.   Если  предложение  INSERT попытается вставить
        повторяющееся    значение    в    столбец    empno,    возникнет
        предопределенное исключение DUP_VAL_ON_INDEX.  В этом случае  вы
        откатитесь к точке сохранения, отменив лишь вставку.

        DECLARE
            emp_id  emp.empno%TYPE;
        BEGIN
            ...
            UPDATE emp SET ... WHERE empno = emp_id;
            DELETE FROM emp WHERE ...
            ...
            SAVEPOINT do_insert;
            INSERT INTO emp VALUES (emp_id, ...);
        EXCEPTION
            WHEN DUP_VAL_ON_INDEX THEN
                ROLLBACK TO do_insert;
        END;

        При  выполнении  ROLLBACK  TO  все  точки сохранения, отмеченные
        после  указанной,  стираются,  а  все изменения, сделанные после
        этой точки, отменяются.  Однако сама точка сохранения, к которой
        вы   возвращаетесь,   не    удаляется.    Например,   если    вы
        последовательно отметите точки  сохранения A, B  C и D,  а затем
        выполните ROLLBACK TO к точке B, то будут стерты лишь C и D.

        ROLLBACK  без  аргументов,  как  и  COMMIT,  стирает  все  точки
        сохранения.

        Если вы отмечаете  точку сохранения в  рекурсивной подпрограмме,
        то на  каждом уровне  рекурсивного спуска  предложение SAVEPOINT
        будет  создавать  новые  экземпляры  точек  сохранения.   Однако
        ROLLBACK  TO  вернет  вас  лишь  к  самой  последней  из   точек
        сохранения с данным именем.

4-32  Руководство пользователя и справочник по PL/SQL


        Имена точек сохранения  - это необъявляемые  идентификаторы.  Их
        можно повторно использовать  внутри транзакции.  При  этом точка
        сохранения сдвигается со своей старой позиции в текущую точку  в
        транзакции.    Таким   образом,   откат   к   точке   сохранения
        воздействует лишь на текущую часть вашей транзакции.  Рассмотрим
        следующий пример:

        ...
        BEGIN
            ...
            SAVEPOINT my_point;
            UPDATE emp SET ... WHERE empno = emp_id;
            ...
            SAVEPOINT my_point;  -- перемещает my_point в текущую точку
            INSERT INTO emp BALUES (emp_id, ...);
            ...
        EXCEPTION
            WHEN OTHERS THEN
                ROLLBACK TO my_point;
        END;

        По умолчанию число активных точек сохранения на сессию не  может
        быть  больше  5.   АКТИВНАЯ   ТОЧКА  СОХРАНЕНИЯ  -  это   точка,
        отмеченная после последней операции COMMIT или ROLLBACK.  Вы или
        ваш  АБД  можете  поднять  этот  лимит (вплоть до 255), увеличив
        значение параметра инициализации ORACLE с именем SAVEPOINTS.


Неявные точки сохранения
------------------------

        Перед выполнением  каждого предложения  INSERT, UPDATE  и DELETE
        ORACLE создает неявную точку сохранения (недоступную вам).  Если
        предложение  сбивается,  то  выполняется  откат  к  этой неявной
        точке.  Обычно отменяется лишь  сбившееся предложение SQL, а  не
        вся  транзакция.    Однако,  если   это  предложение   возбудило
        необрабатываемое исключение,  то хост-окружение  определяет, что
        следует отменить.

        Если    хранимая     подпрограмма    сбилась     в    результате
        необрабатываемого исключения, то ORACLE выполняет неявный  откат
        всех  изменений,  сделанных  этой  подпрограммой.   Однако, если
        подпрограмма  выдала  COMMIT  до возникновения необрабатываемого
        исключения, то отменяется лишь неподтвержденная часть работы.



















                                           Взаимодействие с ORACLE  4-33


Завершение транзакций
---------------------

        Хорошей практикой программирования является явное  подтверждение
        или  явный  откат  каждой  транзакции.   Выдаете  ли вы COMMIT и
        ROLLBACK в вашей программе PL/SQL или в хост-окружении - зависит
        от логического потока вашего приложения.  Если вы  пренебрегаете
        явными  операциями   COMMIT  или   ROLLBACK,  то   окончательное
        состояние транзакции определяет хост-окружение.

        Например, в  среде SQL*Plus,  если ваш  блок PL/SQL  не содержит
        предложения COMMIT или  ROLLBACK, окончательное состояние  вашей
        транзакции  зависит  от  того,  что  вы делаете после выполнения
        этого блока.  Если вы выполняете предложение определения данных,
        предложение управления данными или предложение COMMIT, либо если
        вы выдаете команду EXIT,  DISCONNECT или QUIT, то  ORACLE неявно
        подтверждает  вашу  транзакцию.   Если  вы  выдаете  предложение
        ROLLBACK  или  аварийно  снимаете  сессию  SQL*Plus,  то  ORACLE
        выполняет откат транзакции.

        В   среде   прекомпилятора   ORACLE,   если   ваша  программа не
        завершается  нормально,   ORACLE  выполняет   откат  транзакции.
        Программа завершается нормально, если она явно выдает COMMIT или
        ROLLBACK и отсоединяется от ORACLE с помощью параметра  RELEASE,
        например:

        EXEC SQL COMMIT WORK RELEASE;

        В среде интерфейса вызовов  ORACLE (OCI), если вы  выдаете вызов
        OLOGOFF, ORACLE автоматически  подтверждает вашу транзакцию.   В
        противном случае ORACLE выполняет откат транзакции.
































4-34  Руководство пользователя и справочник по PL/SQL


Использование SET TRANSACTION
-----------------------------

        Умалчиваемым    режимом    для    всех    транзакций    является
        согласованность  данных  по  чтению  НА УРОВНЕ ПРЕДЛОЖЕНИЯ.  Это
        гарантирует, что запрос видит лишь то состояние данных,  которое
        было  подтверждено  перед  началом  его  выполнения,  плюс   все
        изменения, которые внесены  предыдущими предложениями в  текущей
        транзакции.  Если  во время  запроса другие  пользователи вносят
        изменения в эти же таблицы  базы данных, то эти изменения  будут
        видны лишь последующим, но не текущему, запросу.

        Однако вы можете, выдав предложение SET TRANSACTION,  установить
        режим  согласованности  данных  по  чтению НА УРОВНЕ ТРАНЗАКЦИИ.
        Это  гарантирует,  что  запрос  видит  лишь то состояние данных,
        которое было подтверждено перед началом всей транзакции;  однако
        при этом транзакция не  должна вносить изменений в  базу данных.
        Предложение   SET   TRANSACTION    READ   ONLY   не    принимает
        дополнительных параметров и имеет вид:

        SET TRANSACTION READ ONLY;

        Предложение SET TRANSACTION должно быть первым предложением  SQL
        в транзакции и может появиться лишь один раз на транзакцию.  Как
        уже сказано, в таком режиме транзакции все запросы, выдаваемые в
        ней, видят то состояние данных, которое было подтверждено  перед
        началом всей транзакции.   Режим READ ONLY  не влияет на  других
        пользователей или другие транзакции.

        В  транзакции  READ  ONLY  допускаются  лишь предложения SELECT,
        COMMIT  и  ROLLBACK.   Другие  предложения, например, INSERT или
        DELETE, приводят к возбуждению исключения.

        В  течение  транзакции  READ  ONLY  все  ее запросы обращаются к
        одному  и   тому  же   снимку  базы   данных,  что  обеспечивает
        многотабличное,   многозапросное,   согласованное   по    чтению
        представление данных для транзакции.  Другие пользователи  могут
        продолжать  опрашивать  или  обновлять  данные в обычном режиме.
        Транзакция READ ONLY завершается выдачей COMMIT или ROLLBACK.  В
        следующем  примере  вы,  как  управляющий  складом,  используете
        транзакцию READ ONLY, чтобы  собрать цифры по продажам  за день,
        прошедшую  неделю  и  прошедший  месяц.   На  эти цифры не могут
        повлиять другие пользователи,  обновляющие базу данных  во время
        транзакции.

        DECLARE
            daily_sales    REAL;
            weekly_sales   REAL;
            monthly_sales  REAL;
        BEGIN
            SET TRANSACTION READ ONLY;

            SELECT SUM(amt) INTO daily_sales FROM sales
                WHERE dte = SYSDATE;
            SELECT SUM(amt) INTO weekly_sales FROM sales
                WHERE dte > SYSDATE - 7;
            SELECT SUM(amt) INTO monthly_sales FROM sales
                WHERE dte > SYSDATE - 30;
            COMMIT;  -- это просто сигнал об окончании транзакции,
                     -- так как никаких изменений она не делает
            ...
        END;

                                           Взаимодействие с ORACLE  4-35


Переопределение умалчиваемой блокировки
---------------------------------------

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

Использование FOR UPDATE

        При объявлении курсора, к которому будет обращаться фраза  WHERE
        CURRENT OF предложения UPDATE или DELETE, вы должны использовать
        фразу   FOR   UPDATE,   чтобы   затребовать   для  этого курсора
        монопольные  блокировки  строк.   Фраза  FOR  UPDATE,  когда она
        присутствует, должна появляться в конце объявления курсора,  как
        показывает следующий пример:

        DECLARE
            CURSOR c1 IS SELECT empno, sal FROM emp
                WHERE job = 'SALESMAN' AND comm > sal FOR UPDATE;

        Фраза  FOR  UPDATE  указывает,  что строки, выбираемые запросом,
        будут  обновляться  или  удаляться,  и  блокирует  все  строки в
        активном множестве курсора.  Это полезно, когда вы хотите, чтобы
        обновление базировалось на существующих значениях строк.  В этом
        случае вам нужна гарантия,  что строка не будет  изменена другим
        пользователем, прежде чем вы обновите ее.  Все строки в активном
        множестве   блокируются   в    момент   открытия   курсора,    и
        разблокируются при выполнении  COMMIT.  Поэтому после  COMMIT вы
        не можете извлекать строк из курсора, объявленного FOR UPDATE.

        При запросах по нескольким таблицам вы можете использовать фразу
        FOR  UPDATE  OF,  чтобы  ограничить блокировку строк конкретными
        таблицами.   Строки  будут  блокироваться  лишь  в тех таблицах,
        которые  содержат  столбец,  указанный  в  фразе  FOR UPDATE OF.
        Например, следующий запрос блокирует строки в таблице emp, но не
        в таблице dept:

        DECLARE
            CURSOR c1 IS SELECT ename, dname FROM emp, dept
                WHERE emp.deptno = dept.deptno AND job = 'MANAGER'
                FOR UPDATE OF sal;

        Чтобы обратиться  к последней  строке, извлеченной  курсором, вы
        используете  фразу  WHERE  CURRENT  OF  предложения  UPDATE  или
        DELETE, как показывает следующий пример:

        DECLARE
            CURSOR c1 IS SELECT empno, job, sal FROM emp FOR UPDATE;
            ...
        BEGIN
            OPEN c1;
            LOOP
                FETCH c1 INTO ...
                ...
                UPDATE emp SET sal = new_sal WHERE CURRENT OF c1;
            END LOOP;
            ...



4-36  Руководство пользователя и справочник по PL/SQL


Использование LOCK TABLE

        Предложение  LOCK  TABLE  позволяет  вам  заблокировать одну или
        несколько  таблиц  в  указанном   режиме,  так  что  вы   можете
        регулировать  одновременный  доступ  к  таблицам, поддерживая их
        целостность.  Например, предложение, приведенное ниже, блокирует
        таблицу  emp  в  режиме   row  share.   Такой  режим   разрешает
        одновременный   доступ   к   таблице,   но   запрещает    другим
        пользователям   блокировать   всю   таблицу   для   монопольного
        использования.   Блокировка  таблицы  освобождается,  когда ваша
        транзакция выдает COMMIT или ROLLBACK.

        LOCK TABLE emp IN ROW SHARE MODE NOWAIT;

        Режим блокировки определяет, какие другие блокировки могут  быть
        применены к  таблице.  Например,  несколько пользователей  могут
        одновременно затребовать блокировки row share для одной и той же
        таблицы,  но  лишь  один  пользователь  за раз может затребовать
        МОНОПОЛЬНУЮ  (exclusive)  блокировку.   Пока  один  пользователь
        имеет  монопольную  блокировку  таблицы,  другие пользователи не
        могут изменять (INSERT, UPDATE или DELETE) строк в этой таблице.

        Необязательное ключевое слово NOWAIT указывает, что, если запрос
        LOCK TABLE  не может  быть удовлетворен  (возможно, потому,  что
        таблица уже заблокирована  другим пользователем), то  LOCK TABLE
        вернет  управление  пользователю,   вместо  того,  чтобы   ждать
        удовлетворения запроса.  Если вы опустите ключевое слово NOWAIT,
        то  ORACLE  будет  ждать  освобождения  таблицы; это ожидание не
        имеет устанавливаемого предела.

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

Извлечения между COMMIT'ами

        Как говорилось  выше, фраза  FOR UPDATE  запрашивает монопольную
        блокировку строк.  Все  строки в активном  множестве блокируются
        во время  открытия курсора,  а не  во время  их извлечения.  Все
        строки  освобождаются  при  завершении  транзакции  (COMMIT  или
        ROLLBACK).  Поэтому  вы не  можете извлекать  строки из курсора,
        объявленного  FOR  UPDATE,  после  COMMIT.   Если вы попытаетесь
        сделать это,  возникнет исключение.   Рассмотрим следующий  цикл
        FOR, который собъется после десятой вставки:

        DECLARE
            CURSOR c1 IS SELECT ename FROM emp FOR UPDATE OF sal;
            ctr  NUMBER := 0;
        BEGIN
            FOR emp_rec IN c1 LOOP  -- неявные операции FETCH
                ...
                ctr := ctr + 1;
                INSERT INTO temp VALUES (ctr, 'еще работает');
                IF ctr >= 10 THEN
                    COMMIT;  -- освобождает блокировки
                END IF;
            END LOOP;
        END;



                                           Взаимодействие с ORACLE  4-37


        Если  вы   собираетесь  выполнять   COMMIT,  не   закончив  всех
        извлечений, то не  используйте фразу FOR  UPDATE или FOR  UPDATE
        OF.  Вместо  этого,  чтобы  имитировать  поведение  фразы  WHERE
        CURRENT OF, выбирайте (SELECT) ROWID для каждой строки, а  затем
        используйте полученное значение  в последующих операциях  UPDATE
        или DELETE, как показывает следующий пример:

        DECLARE
            CURSOR c1 IS SELECT ename, job, rowid FROM emp;
            my_ename  emp.ename%TYPE;
            my_job    emp.job%TYPE;
            my_rowid  ROWID;
        BEGIN
            OPEN c1;
            LOOP
                FETCH c1 INTO my_ename, my_job, my_rowid;
                EXIT WHEN c1%NOTFOUND;
                UPDATE emp SET sal = sal * 1.05 WHERE rowid = my_rowid;
                  -- это имитирует WHERE CURRENT OF c1
                COMMIT;
            END LOOP;
            CLOSE c1;
        END;

        Заметьте,  однако,  что  извлекаемые  строки НЕ БЛОКИРОВАНЫ, ибо
        фраза  FOR  UPDATE  OF  не  использовалась.   Поэтому  вы можете
        получить  несогласованные  результаты,  если другой пользователь
        изменит строку после того, как вы прочитали ее, но до того,  как
        вы  модифицировали   ее.   Кроме   того,  курсор   должен  иметь
        согласованное по чтению представление данных, и потому  сегменты
        отката, используемые для ваших обновлений, не будут освобождены,
        пока курсор  не будет  закрыт.  Это  может замедлить  обработку,
        если обновляется много строк.

        Следующий пример показывает, что вы можете использовать  атрибут
        %ROWTYPE с курсорами, обращающимися к псевдостолбцу ROWID:

        DECLARE
            CURSOR c1 IS SELECT ename, sal, rowid FROM emp;
            emp_rec  c1%ROWTYPE;
        BEGIN
            OPEN c1;
            LOOP
                FETCH c1 INTO emp_rec;
                EXIT WHEN c1%NOTFOUND;
                ...
                IF ... THEN
                    DELETE FROM emp WHERE rowid = emp_rec.rowid;
                END IF;
            END LOOP;
            CLOSE c1;
        END;











4-38  Руководство пользователя и справочник по PL/SQL


Как справляться с ограничениями на размер программы
---------------------------------------------------

        PL/SQL был спроектирован прежде всего для обработки  транзакций.
        Одним из  следствий такого  проекта является  то, что компилятор
        PL/SQL накладывает ограничение на размер программы.  Этот  лимит
        зависит  от   смеси  предложений   в  вашей   программе  PL/SQL.
        Программы, превышающие этот лимит, вызывают сообщение об  ошибке
        "программа слишком велика".

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

        Другие решение -  разбить вашу программу  на два меньших  блока.
        Прежде чем завершиться, первый блок должен вставить все  данные,
        необходимые второму блоку, в таблицу базы данных (скажем, temp).
        Начав  выполнение,  второй  блок  должен  выбрать  все данные из
        таблицы  temp.   Это  напоминает  передачу  параметров  от одной
        процедуры    другой.     Следующий    пример    показывает   два
        взаимодействующих через "параметры" блока PL/SQL в одном скрипте
        SQL*Plus:

        ...
        DECLARE
            mode    NUMBER;
            median  NUMBER;
            ...
        BEGIN
            ...
            INSERT INTO temp (col1, col2, col3)
                VALUES (mode, median, 'blockA');
        END;
        /
        ...
        DECLARE
            mode    NUMBER;
            median  NUMBER;
            ...
        BEGIN
            ...
            SELECT col1, col2 INTO mode, median FROM temp
                WHERE col3 = 'blockA';
        END;
        /
        ...
















                                           Взаимодействие с ORACLE  4-39


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

        Другое  решение  избегает  этих  ограничений.   Если вы встроите
        PL/SQL в хост-язык  третьего поколения, такой  как C, COBOL  или
        FORTRAN, то вы сможете повторно исполнять первый блок при помощи
        команд передачи  управления.  Кроме  того, вместо  использования
        временной таблицы  в базе  данных, вы  можете передавать  данные
        через  глобальные  хост-переменные,  как  показывает   следующий
        пример на Pro*C:

        EXEC SQL BEGIN DECLARE SECTION;
            int    my_empno;
            float  my_sal, my_comm;
            short  comm_ind;
            ...
        EXEC SQL END DECLARE SECTION;
        ...
        EXEC SQL EXECUTE
            BEGIN
               ...
               SELECT sal, comm INTO :my_sal, :my_comm:comm_ind FROM emp
                    WHERE empno = :my_empno;
               IF :my_comm:comm_ind IS NULL THEN
                    ...
               END IF;
            END;
        END-EXEC;
        ...
        EXEC SQL EXECUTE
            BEGIN
               ...
               IF :my_comm:com_ind > 1000 THEN
                   :my_sal := :my_sal * 1.10;
                   UPDATE emp SET sal = :my_sal WHERE empno = :my_empno;
               END IF;
            END;
        END-EXEC;
        ...






















4-40  Руководство пользователя и справочник по PL/SQL


----------------

Триггеры базы данных

        ТРИГГЕР БАЗЫ ДАННЫХ -  это хранимая программная единица  PL/SQL,
        ассоциированная  с  конкретной  таблицей  базы  данных.   ORACLE
        исполняет (возбуждает) триггер бады данных автоматически  каждый
        раз,  когда  данная  операция  SQL  воздействует на эту таблицу.
        Итак, в отличие от подпрограмм, которые должны вызываться  явно,
        триггер базы данных вызывается неявно.  Среди прочего, вы можете
        использовать триггеры базы данных для

            *  аудита (отслеживания) модификаций данных

            *  прозрачной журнализации (регистрации) событий

            *  реализации комплексных организационных правил

            *  автоматического вычисления значений столбцов

            *  осуществления сложных процедур защиты

            *  поддержки дублированных таблиц

        С  каждой  таблицей  можно  ассоциировать  до  12 триггеров базы
        данных.   Для  создания  триггера  базы  данных  вы должны иметь
        привилегии CREATE TRIGGER, а также либо владеть  ассоциированной
        таблицей,  либо  иметь  привилегии  ALTER  для   ассоциированной
        таблицы, либо иметь привилегии ALTER ANY TABLE.

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


























                                           Взаимодействие с ORACLE  4-41


        Следующий пример  иллюстрирует прозрачную  журнализацию событий.
        Триггер базы  данных с  именем reorder  обеспечивает, что  товар
        заказывается заново  каждый раз,  когда его  имеющееся на складе
        количество (qty_on_hand) падает ниже пороговой точки.

        CREATE TRIGGER reorder
            /* событие триггера */
            AFTER UPDATE OF qty_on_hand ON inventory  -- таблица
            FOR EACH ROW
            /* ограничение триггера */
            WHEN (new.reorderable = 'T')
        BEGIN
            /* действие триггера */
            IF :new.qty_on_hand < :new.reorder_point THEN
                INSERT INTO pending_orders
                    VALUES (:new.part_no, :new.reorder_qty, SYSDATE);
            END IF;
        END;

        Имя   в   фразе   ON   идентифицирует   таблицу   базы   данных,
        ассоциированную  с  триггером  базы  данных.   Событие  триггера
        специфицирует предложение  манипулирования данными  SQL, которое
        воздействует  на  таблицу.   В  данном  случае  это  предложение
        UPDATE.  Если предложение триггера сбивается, оно  откатывается.
        Ключевое  слово  AFTER   указывает,  что  триггер   базы  данных
        возбуждается после того, как обновление выполнено.

        По умолчанию, триггер базы  данных возбуждается один раз  на всю
        таблицу.   Необязательная  фраза  FOR  EACH  ROW  указывает, что
        триггер  должен  возбуждаться  один  раз  на каждую строку.  Для
        того,  чтобы  триггер   возбудился,  однако,  требуется,   чтобы
        булевское выражение в фразе WHEN давало значение TRUE.

        Префикс  :new  представляет  собой  КОРРЕЛИРУЮЩЕЕ  ИМЯ,  которое
        отсылает к вновь измененному значению столбца.  Внутри  триггера
        базы данных вы можете обращаться  как к новому, так и  к старому
        (:old) значениям столбцов в измененных строках.  Заметьте, что в
        фразе WHEN  двоеточие не  используется.  Вы  можете использовать
        фразу REFERENCING  (здесь не  показана), чтобы  заменить :new  и
        :old другими коррелирующими именами.

        За исключением  предложений управления  транзакциями, таких  как
        COMMIT и  ROLLBACK, в  блоке BEGIN..END  может появляться  любое
        предложение  SQL  или  процедурное  предложение,  включая вызовы
        подпрограмм.   Триггер  базы  данных  может  также  иметь секции
        DECLARE и EXCEPTION.

















4-42  Руководство пользователя и справочник по PL/SQL


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

        CREATE TRIGGER check_salary
            BEFORE INSERT OR UPDATE OF sal, job ON emp
            FOR EACH ROW
            WHEN (new.job != 'PRESIDENT')
        DECLARE
            minsal  NUMBER;
            maxsal  NUMBER;
        BEGIN
         /* Дать интервал окладов для данной должности из справочника */
            SELECT losal, hisal INTO minsal, maxsal FROM sals
                WHERE job = :new.job;
            /* Если оклад вне диапазона, прибавка отрицательна, *
            /* или прибавка выше 10%, возбудить исключение.     */
            IF (:new.sal < minsal OR :new.sal > maxsal) THEN
                raise_application_error(-20225, 'Salary out of range');
            ELSIF (:new.sal < :old.sal) THEN
                raise_application_error(-20230, 'Negative increase');
            ELSIF (:new.sal > 1.1 * :old.sal) THEN
                raise_application_error(-20235, 'Increase exceeds 10%');
            END IF;
        END;

        Подробнее  о  процедуре  raise_application_error  см.  в разделе
        "Пакеты,  специфичные  для  продуктов"  в  главе 7.  Для полного
        обсуждения триггеров баз  данных обратитесь к  документу ORACLE7
        Server Application Developer's Guide.