Главная страница
Навигация по странице:

  • Предикаты INSERTING

  • 18.4.2. Изменение описания триггера

  • 18.4.3. Удаление описания триггера DROP TRIGGER имя_триггера ; 18.4.4. Использование триггера

  • 18.4.5. Изменяющиеся (мутирующие) таблицы

  • 18.5. Пакеты (модули) 18.5.1. Модули

  • 18.5.2. Создание описания пакета Описание спецификации (заголовка) пакета

  • Область действия пакетов и их объектов

  • 18.5.3. Изменение описания пакета

  • 18.6. Встроенные пакеты PL/SQL

  • DBMS_STANDARD и STANDARD

  • ЧАСТЬ V I I ПРИМЕР СОЗДАНИЯ БАЗЫ ДАННЫХ "UCHEB"

  • Кириллов В.В., Громов Г.Ю. - Введение в реляционные базы данных. Литература для вузов isbn 9785941577705 в книге рассматриваются основные понятия баз данных и систем управления ими


    Скачать 11.62 Mb.
    НазваниеЛитература для вузов isbn 9785941577705 в книге рассматриваются основные понятия баз данных и систем управления ими
    АнкорКириллов В.В., Громов Г.Ю. - Введение в реляционные базы данных.pdf
    Дата16.04.2018
    Размер11.62 Mb.
    Формат файлаpdf
    Имя файлаКириллов В.В., Громов Г.Ю. - Введение в реляционные базы данных.pdf
    ТипЛитература
    #18127
    страница22 из 28
    1   ...   18   19   20   21   22   23   24   25   ...   28
    Последовательность выполнения нескольких триггеров
    одной таблицы
    Активация триггеров DML происходит при выполнении предложений
    INSERT
    ,
    UPDATE
    или
    DELETE
    . Для каждого из этих предложений может быть создано четыре триггера: табличный
    BEFORE
    , табличный
    AFTER
    , строковый
    BEFORE
    и строковый
    AFTER
    . При наличии всех этих триггеров порядок их ак- тивации таков:
    1.
    Табличный триггер
    BEFORE
    2.
    Строковый триггер
    BEFORE
    3.
    Строковый триггер
    AFTER
    4.
    Табличный триггер
    AFTER

    Глава 18. Хранимые процедуры
    327
    Предикаты
    INSERTING, UPDATING
    и
    DELETING
    В одном описании можно объединить несколько триггеров, если они отно- сятся к одному уровню (строка или предложение) и принадлежат к одной таблице. Если триггеры объединяются в одно описание, то для разделения логики на отдельные сегменты можно использовать в блоке кода PL/SQL три
    логические функции (предикаты), определяющие тип выполняемой операции.

    INSERTING
    — принимает значение
    TRUE
    , если активизирующий оператор
    INSERT
    ;
    FALSE
    в противном случае.

    UPDATING
    — принимает значение
    TRUE
    , если активизирующий оператор
    UPDATE
    ;
    FALSE
    в противном случае.

    DELETING
    — принимает значение
    TRUE
    , если активизирующий оператор
    DELETE
    ;
    FALSE
    в противном случае.
    18.4.2. Изменение описания триггера
    Синтаксис инструкции
    ALTER TRIGGER
    , которая позволяет переименовывать, включать или отключать триггер без его yдaления и повторного создания, имеет вид:
    ALTER TRIGGER имя_триггера
    { {ENABLE | DISABLE} | RENAME TO новое_имя |
    COMPILE [дирекгивы_компилятора][DEBUG] [REUSE SETTINGS]}
    Здесь:
    ENABLE
    Включает ранее деактивированный триггер. В качестве альтернативы можно использовать предложение
    ALTER TABLE имя_таблицы ENABLE ALL TRIGGERS
    DISABLE
    Отключает работающий триггер. В качестве альтернативы можно использо- вать предложение
    ALTER TABLE имя_таблицы DISABLE ALL TRIGGERS
    RENAME TO новое_имя
    Триггер переименовывается в
    новое_имя
    , но егосостояние остается неизмен- ным.
    COMPILE [DEBUG] [REUSE SETTINGS]
    Триггер компилируется независимо от того, является он допустимым или нет, а также компилируются все объекты, от которых он зависит. Если любой из объектов неработоспособен, триггер будет неработоспособным. Если все объекты работоспособны, в том числе
    блок_кода
    триггера, то триггер стано-

    Часть
    VI.
    Создание приложений на
    SQL
    328
    вится работоспособным. У предложения
    COMPILE
    есть несколько дополни- тельных предложений.
    DEBUG
    Компилятор PL/SQL будет генерировать и записывать дополнительную ин- формацию, которую сможет использовать отладчик PL/SQL.
    REUSE SETTINGS
    Система Oracle сохранит все настройки компилятора, что поможет сэконо- мить много времени при компиляции.
    директивы_компилятора
    Указывается специальное значение, используемое компилятором PL/SQL в формате
    директива
    =
    'значение'
    . Существуют следующие директивы:
    PLSQL_OPTIMIZE_LEVEL
    ,
    PLSQL_CODE_TYPE
    ,
    PLSQL_DEBUG
    ,
    PLSQL
    _
    WARNINGS
    и
    PLSQL
    _
    NLSLENGTHSEMANTICS
    . Для каждой из них в инструкции можно ука- зать значение один раз. Директива действует только в рамках компилируемо- го модуля.
    Примеры создания триггеров были рассмотрены в разд. 12.3 (листин- ги 12.1—12.7). В разд. 18.4.4 будут рассмотрены более сложные примеры.
    18.4.3. Удаление описания триггера
    DROP TRIGGER имя_триггера;
    18.4.4. Использование триггера
    Приводимые далее примеры созданы для объектов базы данных "UCHEB", подробно рассматриваемой в части VII.
    Пример 18.1. Создадим триггер для формирования номера нового человека в момент ввода данных о нем в таблицу
    Н_ЛЮДИ
    , а также для исправления некорректных значений его фамилии и имени с помощью рассмотренной ра- нее функции fio
    (см. разд. 18.3.4). Этот триггер запускается не только при выполнении вставки новых строк (
    INSERT
    ) в таблицу
    Н_ЛЮДИ
    , но и при изме- нении ее строк (
    UPDATE
    ). Для того чтобы он создавал номер человека только при вставке строк, используется логическая функция
    INSERTING
    CREATE OR REPLACE TRIGGER люди_biur
    BEFORE INSERT OR UPDATE ON н_люди
    FOR EACH ROW
    DECLARE

    Глава 18. Хранимые процедуры
    329
    err_fam EXCEPTION; err_im EXCEPTION;
    BEGIN
    :new.Фамилия := fio(:new.Фамилия); -- фамилия замещается результатом
    -- работы функции fio
    :new.Имя := fio(:new.Имя); -- имя замещается результатом работы
    -- функции fio
    IF :new.Фамилия = '0' THEN RAISE err_fam;
    END IF; -- выход по ошибке при неправильном написании фамилии
    IF :new.Имя = '0' THEN RAISE err_im;
    END IF; -- выход по ошибке при неправильном написании имени
    IF INSERTING THEN
    -- Формирование нового номера человека
    SELECT н_люди_посл.NEXTVAL INTO :new.ид FROM dual;
    END IF;
    EXCEPTION -- начало обработчика исключений основной программы
    WHEN err_fam THEN RAISE_APPLICATION_ERROR(-20040,
    'Фамилия должна состоять только из букв русского алфавита, '||
    'пробела, дефиса и начинаться с заглавной буквы !');
    WHEN err_im THEN RAISE_APPLICATION_ERROR(-20041,
    'Имя должно состоять только из букв русского алфавита, '||
    'пробела, дефиса и начинаться с заглавной буквы !');
    WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999,
    'Какая-то другая ошибка');
    END люди_biur;
    /
    Пример 18.2. Создадим триггер для формирования номера строки при ее вводе в таблицу
    Н_УЧЕНИКИ
    и для проверки правильности ввода значений некоторых ее столбцов.
    CREATE OR REPLACE TRIGGER учен_BIR
    BEFORE INSERT ON н_ученики
    FOR EACH ROW
    DECLARE plan_gr EXCEPTION; -- План должен быть связан с группой nach_kon EXCEPTION; -- Начало должно быть меньше или равно концу null_val EXCEPTION; -- какое-то из обязательных значений не заполнено test NUMBER;
    BEGIN
    -- Если не введены какие-то обязательные значения, выводим сообщение

    Часть
    VI.
    Создание приложений на
    SQL
    330
    IF :new.члвк_ид IS NULL
    OR :new.план_ид IS NULL OR :new.группа IS NULL
    OR :new.начало IS NULL OR :new.конец IS NULL THEN
    RAISE null_val;
    END IF;
    -- Проверяем, и если такой строки нет, то вставляем ее в н_обучения
    SELECT COUNT(*) INTO test FROM н_обучения
    WHERE члвк_ид = :NEW.члвк_ид AND вид_обуч_ид = :NEW.вид_обуч_ид;
    IF test = 0 THEN
    INSERT INTO н_обучения(члвк_ид,вид_обуч_ид) VALUES
    (:NEW.члвк_ид,:NEW.вид_обуч_ид);
    COMMIT;
    END IF;
    -- генерируем ид
    SELECT н_учен_посл.NEXTVAL INTO :new.ид FROM dual;
    SELECT COUNT(*) INTO test FROM н_группы_планов WHERE группа =
    :new.группа AND план_ид = :new.план_ид;
    -- Если план не связан с группой
    IF test = 0 THEN
    RAISE plan_gr;
    END IF;
    IF :new.начало > :new.конец THEN
    RAISE nach_kon;
    END IF;
    :new.конец_по_приказу := :new.конец;
    EXCEPTION
    WHEN plan_gr THEN RAISE_APPLICATION_ERROR(-20040,'Номер группы не сопоставлен с номером плана !');
    WHEN nach_kon THEN RAISE_APPLICATION_ERROR(-20041,'Конец не может быть меньше начала !');
    WHEN null_val THEN RAISE_APPLICATION_ERROR(-20042,'Не заполнены обязательные поля (информация о человеке, группа/план, даты) !');
    WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999,
    'Какая-то другая ошибка');
    END учен_bir;
    /
    Пример 18.3. Создадим триггер для изменения ряда значений таблицы
    Н_УЧЕНИКИ
    CREATE OR REPLACE TRIGGER учен_bur
    BEFORE UPDATE OF члвк_ид, начало, конец, план_ид, группа
    ON н_ученики
    FOR EACH ROW

    Глава 18. Хранимые процедуры
    331
    DECLARE plan_gr EXCEPTION; -- План должен быть связан с группой nach_kon EXCEPTION; -- Начало должно быть меньше или равно концу null_val EXCEPTION; -- какое-то из обязательных значений не заполнено test NUMBER;
    BEGIN
    -- Если не введены какие-то обязательные значения, выводим сообщение
    IF :new.члвк_ид IS NULL
    OR :new.план_ид IS NULL OR :new.группа IS NULL
    OR :new.начало IS NULL OR :new.конец IS NULL THEN
    RAISE null_val;
    END IF;
    --
    SELECT COUNT(*) INTO test FROM н_группы_планов WHERE группа =
    :new.группа AND план_ид = :new.план_ид;
    -- Если план не связан с группой
    IF test = 0 THEN
    RAISE plan_gr;
    END IF;
    IF :new.начало > :new.конец THEN
    RAISE nach_kon;
    END IF;
    -- Если обновляется конец, то мы старый его вариант сохраняем
    -- в поле конец_по_приказу
    IF :old.конец <> :new.конец AND :new.конец <> :new.конец_по_приказу
    THEN
    :new.конец_по_приказу := :old.конец;
    END IF;
    EXCEPTION
    WHEN plan_gr THEN RAISE_APPLICATION_ERROR(-20040,'Номер группы не сопоставлен с номером плана ! (ид '||:NEW.ид||')');
    WHEN nach_kon THEN RAISE_APPLICATION_ERROR(-20041,'Конец не может быть меньше начала !');
    WHEN null_val THEN RAISE_APPLICATION_ERROR(-20042,'Не заполнены обяза- тельные поля (информация о человеке, группа/план, даты) !');
    WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999,'Какая-то другая ошибка');
    END учен_bur;
    /

    Часть
    VI.
    Создание приложений на
    SQL
    332
    18.4.5. Изменяющиеся (мутирующие)
    таблицы
    Из тела строкового триггера нельзя обращаться к таблицам, которые в момент обращения модифицируются предложениями
    INSERT
    ,
    DELETE
    или
    UPDATE
    , так как это может привести к неверным результатам. Такие таблицы получили названия изменяющихся (мутирующих) таблиц.
    Правда, если используется предложение:
    INSERT INTO имя_таблицы ... VALUES (скалярное_выражение [,...] ); для вставки одного значения (или по одному значению) в таблицу
    имя_таблицы
    , то для строковых триггеров
    BEFORE
    и
    AFTER
    эта таблица не является изменяющейся. И это единственная ситуация, когда строковый триггер может считывать и модифицировать информацию активизирующей таблицы.
    Однако при использовании предложения:
    INSERT INTO имя_таблицы ... предложение_SELECT; таблица
    имя_таблицы
    всегда является изменяющейся, несмотря на то, сколько строк возвращается в подзапросе.
    Рассмотрим достаточно объемный пример, в котором появится мутирующая таблица и ошибки, возникающие при ее появлении, и можно будет подробно обсудить, как избежать этих ошибок.
    1.
    Создадим сначала в схеме
    COOK
    таблицу
    Должности
    , в которой будут хра- ниться сведения о должностях и должностных окладах сотрудников пан- сионата:
    CREATE TABLE ДОЛЖНОСТИ
    (
    ИД NUMBER(9) PRIMARY KEY,
    ДОЛЖНОСТЬ VARCHAR2(25) NOT NULL,
    ОКЛАД NUMBER(15,2) NOT NULL,
    НАЧАЛО DATE NOT NULL,
    КОНЕЦ DATE NOT NULL,
    СОСТОЯНИЕ VARCHAR2(10) DEFAULT 'Проект' NOT NULL
    );
    COMMENT ON COLUMN ДОЛЖНОСТИ.ИД
    IS 'Уникальный идентификатор';
    COMMENT ON COLUMN ДОЛЖНОСТИ.ДОЛЖНОСТЬ
    IS 'Наименование должности';

    Глава 18. Хранимые процедуры
    333
    COMMENT ON COLUMN ДОЛЖНОСТИ.ОКЛАД
    IS 'Значение оплаты в рублях по должности';
    COMMENT ON COLUMN ДОЛЖНОСТИ.НАЧАЛО
    IS 'Начало периода действия оплаты';
    COMMENT ON COLUMN ДОЛЖНОСТИ.КОНЕЦ
    IS 'Актуальный конец периода оплаты';
    COMMENT
    ON
    COLUMN
    ДОЛЖНОСТИ.СОСТОЯНИЕ
    IS 'Состояние (Проект или Утвержден)';
    ALTER TABLE ДОЛЖНОСТИ
    ADD CONSTRAINT "Только Проект или Утвержден"
    CHECK (СОСТОЯНИЕ IN ('Проект', 'Утвержден'));
    2.
    Заполним эту таблицу данными: insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (1, 'Зав_производством', 170, '05.11.1987', '14.11.1987',
    'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (2, 'Директор', 250, '05.01.1987', '04.11.1987', 'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (3, 'Шеф_повар', 150, '05.11.1987', '09.09.9999', 'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (4, 'Повар_1_категории', 120, '05.11.1987', '09.09.9999',
    'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (5, 'Повар_2_категории', 100, '05.11.1987', '09.09.9999',
    'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (6, 'Посудомойка', 80, '05.11.1987', '09.09.9999',
    'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (7, 'Уборщица', 80, '05.11.1987', '09.09.9999', 'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (8, 'Директор', 270, '05.11.1987', '09.09.9999', 'Утвержден'); insert into ДОЛЖНОСТИ (ИД, ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО, КОНЕЦ, СОСТОЯНИЕ) values (9, 'Зав_производством', 180, '15.11.1987', '09.09.9999',
    'Утвержден'); commit;
    3.
    Выполним запрос: select * from должности t ORDER BY должность,начало;

    Часть
    VI.
    Создание приложений на
    SQL
    334
    4.
    Результат запроса:
    ИД ДОЛЖНОСТЬ ОКЛАД НАЧАЛО КОНЕЦ СОСТОЯНИЕ
    -- ----------------- ------ ----------- ----------- ---------
    2 Директор 250,00 05.01.1987 04.11.1987 Утвержден
    8 Директор 270,00 05.11.1987 09.09.9999 Утвержден
    1 Зав_производством 170,00 05.11.1987 14.11.1987 Утвержден
    9 Зав_производством 180,00 15.11.1987 09.09.9999 Утвержден
    4 Повар_1_категории 120,00 05.11.1987 09.09.9999 Утвержден
    5 Повар_2_категории 100,00 05.11.1987 09.09.9999 Утвержден
    6 Посудомойка 80,00 05.11.1987 09.09.9999 Утвержден
    7 Уборщица 80,00 05.11.1987 09.09.9999 Утвержден
    3 Шеф_повар 150,00 05.11.1987 09.09.9999 Утвержден
    Перед выполнением остальных действий создадим последовательность с на- чальным значением 10:
    CREATE SEQUENCE ДОЛЖН_ПОСЛ
    START WITH 10
    INCREMENT BY 1;
    А теперь рассмотрим "правила игры":
    1.
    Значение должностного оклада устанавливается с определенной даты
    (
    НАЧАЛО
    ), а так как не известно, до какой даты оно будет актуальным, то в столбец
    КОНЕЦ
    устанавливается запредельная дата: 9 сентября 9999 года.
    2.
    При изменении должностного оклада, с какой-либо даты необходимо вве- сти в таблицу
    ДОЛЖНОСТИ
    новую строку с: наименованием этой должности; новым значением оклада; датой, с которой должен выплачиваться этот оклад (эта дата должна быть больше даты начала предшествующего должностного оклада); датой окончания действия этого оклада, равной 09.09.9999; идентификатором этой строки (
    ИД
    ), полученным с использованием соз- данной ранее последовательности
    ДОЛЖН_ПОСЛ
    ; состоянием 'Проект'
    При этом в предложении
    INSERT
    достаточно указать только три первых пунк- та, последние пункты должны выполниться триггером и значением по умол- чанию, указанным в описании таблицы
    ДОЛЖНОСТИ
    :
    СОСТОЯНИЕ VARCHAR2(10) DEFAULT 'Проект' NOT NULL
    CREATE OR REPLACE TRIGGER должн_bir
    BEFORE INSERT ON должности

    Глава 18. Хранимые процедуры
    335
    FOR EACH ROW
    DECLARE null_val EXCEPTION; -- какое-то из обязательных значений не заполнено nach_nach EXCEPTION; -- новое начало должно быть больше существующего test NUMBER;
    ID NUMBER; nach DATE;
    BEGIN
    -- Если не введены какие-то обязательные значения, выводим сообщение
    IF :new.должность IS NULL
    OR :new.оклад IS NULL
    OR :new.начало IS NULL THEN
    RAISE null_val;
    END IF;
    -- Находим строку с той же должностью и датой конца = '09.09.9999'
    SELECT COUNT(*) INTO test FROM должности
    WHERE должность = :NEW.должность AND конец = '09.09.9999';
    IF test <> 0 THEN
    SELECT ид, начало INTO ID, nach FROM должности
    WHERE должность = :NEW.должность AND конец = '09.09.9999'
    AND состояние = 'Утвержден';
    -- если существующее начало больше нового, выводим сообщение
    IF nach > :NEW.начало THEN
    RAISE nach_nach;
    END IF;
    END IF;
    -- Установка даты конца нового оклада
    :new.конец := '09.09.9999';
    -- генерация ид
    SELECT должн_посл.NEXTVAL INTO :new.ид FROM dual;
    EXCEPTION
    WHEN null_val THEN RAISE_APPLICATION_ERROR(-20042,'Не заполнены обязательные поля !');
    WHEN nach_nach THEN RAISE_APPLICATION_ERROR(-20045,'Вводимое начало меньше существующего !');
    WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999, 'Какая-то другая ошибка');
    END должн_bir;
    /
    Проверим работу триггера, введя новый должностной оклад:
    INSERT INTO ДОЛЖНОСТИ (ДОЛЖНОСТЬ, ОКЛАД, НАЧАЛО)
    VALUES ('Зав_производством', 181, '15.11.1988'); и получив новую строку в таблице:
    ИД ДОЛЖНОСТЬ ОКЛАД НАЧАЛО КОНЕЦ СОСТОЯНИЕ
    -- ----------------- ------ ----------- ----------- ---------
    10 Зав_производством 181,00 15.11.1988 09.09.9999 Проект

    Часть
    VI.
    Создание приложений на
    SQL
    336
    Строка с состоянием 'Проект' должна быть в дальнейшем либо утверждена, либо удалена.
    Рассмотрим сначала предложение для удаления введенной строки (строка с
    ИД=10
    )
    DELETE должности WHERE ид=10; и триггер, который должен проверить допустимость удаления:
    CREATE OR REPLACE TRIGGER должн_bdr
    BEFORE DELETE ON должности
    FOR EACH ROW
    DECLARE sost_val EXCEPTION;
    BEGIN
    -- Если состояние строки "Утвержден", то удалять нельзя
    IF :OLD.состояние = 'Утвержден' THEN
    RAISE sost_val;
    END IF;
    EXCEPTION
    WHEN sost_val THEN RAISE_APPLICATION_ERROR(-20043,'Нельзя удалять утвержденный должностной оклад!');
    WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999,'Какая-то другая ошибка');
    END должн_bdr;
    /
    Если же требуется утвердить новый должностной оклад, то нужно изменить его состояние на 'Утвержден'
    . При этом значение даты окончания действия предыдущего оклада по этой должности должно быть заменено датой начала нового оклада, уменьшенной на один день. Изменение даты конца предыду- щего должностного оклада выполняем с помощью триггера:
    CREATE OR REPLACE TRIGGER должн_bur
    BEFORE UPDATE OF состояние ON должности
    FOR EACH ROW
    DECLARE sost_val EXCEPTION; test NUMBER;
    ID NUMBER;
    BEGIN
    -- Если состояние строки "Утвержден", то изменять нельзя
    IF :OLD.состояние = 'Утвержден' THEN
    RAISE sost_val;
    END IF;
    -- Ищем строку с той же должностью и датой конца, равной '09.09.9999'
    SELECT COUNT(*) INTO test FROM должности

    Глава 18. Хранимые процедуры
    337
    WHERE должность = :OLD.должность AND конец = '09.09.9999';
    IF test <> 0 THEN
    -- Определяем номер найденной строки
    SELECT ид INTO ID FROM должности WHERE должность = :OLD.должность
    AND конец = '09.09.9999' AND состояние = 'Утвержден';
    -- Изменяем дату конца найденной строки
    UPDATE должности SET конец = :OLD.начало-1 WHERE ид = ID;
    END IF;
    EXCEPTION
    WHEN sost_val THEN RAISE_APPLICATION_ERROR(-20044,'Нельзя изменять ут- вержденный должностной оклад!');
    END должн_bur;
    /
    Теперь можно утвердить должностной оклад:
    UPDATE должности SET состояние = 'Утвержден' WHERE ид = 10;
    Результат выполнения этого предложения дал такой результат:
    ORA-04091: таблица COOK.ДОЛЖНОСТИ изменяется, триггер/функция может не заметить это
    ORA-06512: на "COOK.ДОЛЖН_BUR", line 11
    ORA-04088: ошибка во время выполнения триггера 'COOK.ДОЛЖН_BUR'
    Вот мы и столкнулись с ошибкой изменяющейся (мутирующей) таблицы
    (ORA-04091), т. е. попыткой изменить таблицу, с которой связан строковый триггер
    ДОЛЖН_BUR
    А нельзя ли строковый триггер заменить табличным, который не подвержен подобным ошибкам? Тем более что для этого достаточно закомментировать в
    ДОЛЖН_BUR
    строку
    FOR EACH ROW
    Однако при таком изменении триггера и попытки утвердить должностной оклад, возникает новая ошибка:
    ORA-04082: ссылки на NEW или OLD недопустимы в триггерах уровня таблицы.
    Проблему можно разрешить с помощью создания вместо
    ДОЛЖН_BUR
    двух триггеров — строкового
    ДОЛЖН_BSUR (BEFORE)
    и табличного
    ДОЛЖН_ATUR
    (AFTER)
    . В строковом триггере
    ДОЛЖН_BSUR
    будут определяться значения
    :OLD.ид
    ,
    :OLD.начало
    ,
    :OLD.должность и
    :OLD.состояние
    , но сама таблица
    ДОЛЖНОСТИ
    запрашиваться и изменяться не будет. Запрос и изменение будут выполняться в табличном триггере
    ДОЛЖН_ATUR
    , где и будут использоваться значения, определенные в строковом триггере.
    А где же сохранять и как передать найденные значения
    :OLD.ид
    ,
    :OLD.начало
    ,
    :OLD.должность и
    :OLD.состояние
    ? Oracle рекомендует исполь- зовать для этого пакет. Поэтому дальнейшее рассмотрение проблемы изме- няющихся таблиц перенесено в разд. 18.5.5.

    Часть
    VI.
    Создание приложений на
    SQL
    338
    18.5. Пакеты (модули)
    18.5.1. Модули
    Модуль, или пакет (package) — еще одно средство, пришедшее в PL/SQL из языка программирования Ada. Модуль — это конструкция PL/SQL, которая позволяет хранить связанные объекты в одном месте. Модуль состоит из двух частей: описания и тела. Они хранятся по отдельности в словаре дан- ных. В отличие от процедур и функций, которые могут содержаться локально в блоке или храниться в базе данных, модули могут быть только хранимыми и никогда локальными. Помимо того, что модули позволяют группировать связанные объекты, они полезны еще и тем, что ограничений, налагаемых зависимостями, в них меньше, чем в хранимых подпрограммах. Кроме того, они имеют ряд свойств, улучшающих функционирование системы.
    В сущности, модуль представляет собой именованный раздел объявлений.
    Все, что может входить в состав раздела объявлений блока, может входить и в модуль: процедуры, функции, курсоры, типы и переменные. Размещение их в модуле полезно тем, что это позволяет обращаться к ним из других бло- ков PL/SQL, поэтому в модулях можно описывать глобальные переменные
    PL/SQL (внутри одного сеанса работы с базой данных).
    18.5.2. Создание описания пакета
    Описание спецификации (заголовка) пакета
    Спецификация (заголовок) пакета(package specification) содержит информа- цию о составе модуля, однако в описание не входит текст процедур. Синтак- сис спецификации пакета имеет вид:
    CREATE [OR REPLACE] PACKAGE [схема.]имя_пакета
    [ AUTHID {CURRENT_USER | DEFINER} ]
    { IS | AS } pl/sql_спецификация_пакета;
    Здесь:
    AUTHID CURRENT_USER
    Служит для того, чтобы указать, что пакет выполняется с привилегиями
    CURRENT_USER
    и что внешние названия в предложениях языка соответствуют схеме
    CURRENT_USER
    . В противном случае все будет соответствовать схеме, в которой расположен пакет.
    AUTHID DEFINER

    Глава 18. Хранимые процедуры
    339
    Служит для того, чтобы указать, что пакет выполняется с привилегиями вла- дельца схемы, в которой расположен пакет, и что внешние названия в пред- ложениях языка соответствуют этой же схеме.
    IS | AS
    Ключевые слова
    IS | AS
    идентичны и начинают
    pl/sql_спецификация_пакета
    pl/sql_спецификация_пакета
    Может включать:
    определение_типа
    ,
    описание_процедуры
    ,
    описание_функции
    ,
    объявление_переменной
    ,
    объявление_исключителъной_ситуации
    ,
    объявление_
    курсора
    ,
    объявление_прагмы
    Элементы пакета (описания процедур и функций, переменные и т. д.) анало- гичны тому, что указывается в разделе объявлений анонимного блока. Для заголовка пакета действуют те же синтаксические правила, что и для раздела объявлений, за исключением объявлений процедур и функций. Перечислим эти правила.

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

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

    Объявления всех процедур и функций должны быть предварительными.
    В предварительном объявлении (forward declaration) описываются подпро- грамма и ее аргументы (если есть), но не включается программный текст.
    В этом отличие пакета от раздела объявлений блока, где могут находиться как предварительные объявления, так и текст процедур и функций. Про- граммный текст процедур и функций пакета содержится в теле этого пакета.
    Описание тела пакета
    Тело пакета (package body) — это объект словаря данных, хранящийся от- дельно от заголовка пакета. Тело пакета нельзя скомпилировать, если ранее не был успешно скомпилирован заголовок. В теле содержится текст подпро- грамм, предварительно объявленных в заголовке пакета. В нем могут нахо- диться также дополнительные объявления, глобальные для тела пакета, но не видимые в его описании.

    Часть
    VI.
    Создание приложений на
    SQL
    340
    Синтаксис тела пакета имеет вид:
    CREATE [OR REPLACE] PACKAGE BODY [схема.]имя_пакета
    { IS | AS } pl/sql_тело_пакета;
    В теле пакета содержится программный текст (
    pl/sql_тело_пакета
    ) для предварительных объявлений, сделанных в заголовке пакета, и могут также находиться дополнительные переменные, курсоры, типы и подпрограммы.
    На объекты в заголовке, которые не были объявлены предварительно (на- пример, исключение), можно ссылаться в теле пакета непосредственно.
    Тело пакета не является обязательной его частью. Если в заголовке не указа- ны какие-либо процедуры или функции (а только переменные, курсоры, типы и т. д.), тело можно не создавать. Такой способ полезен для объявления гло- бальных переменных, поскольку все объекты пакета видимы вне его преде- лов (область действия и область видимости элементов пакета обсуждаются в следующем разделе).
    Любое предварительное объявление в заголовке пакета должно быть раскры- то в его теле. Описание процедуры или функции должно быть таким же и включать в свой состав имя подпрограммы, имена ее параметров и вид ка- ждого параметра.
    Область действия пакетов и их объектов
    Любой объект, объявленный в заголовке пакета, находится в области дейст- вия и видим вне этого пакета. При обращении к объекту нужно указать имя пакета. Например, для вызова процедуры p_upd1
    , размещенной в пакете pk_долж
    , надо предварить имя процедуры именем пакета: pk_долж.p_upd1
    Внутри тела пакета на объекты, представленные в его заголовке, можно ссы- латься без указания имени пакета.
    Если процедура объявлена в теле пакета локальной (т. е. она отсутствует в описании заголовка пакета), то область ее действия — только тело пакета.
    Ее можно вызывать из других процедур тела пакета, но вне тела она невидима.
    18.5.3. Изменение описания пакета
    Для изменения описания пакета используется предложение:
    ALTER PACKAGE [схема.]имя_пакета
    COMPILE [DEBUG] [PACKAGE | SPECIFICATION | BODY]

    Глава 18. Хранимые процедуры
    341
    [имя_параметра = значение_параметра
    [имя_параметра = значение_параметра] ... ]
    [REUSE SETTINGS];
    Здесь:
    COMPILE
    Ключевое слово перед тем, как указываются объекты перекомпиляции и (или) отладка.
    PACKAGE
    Определяет, что нужно перекомпилировать спецификацию и тело пакета, а также всех зависимых объектов.
    SPECIFICATION
    Определяет, что нужно перекомпилировать спецификацию. При этом будут лишены законной силы любые местные объекты, которые зависят от специ- фикации, типа процедур, которые называют процедуры или функции в паке- те. Тело пакета также зависит от его спецификации. Если впоследствии дела- ется ссылка на один из этих зависимых объектов (без предварительного их перекомпилирования), то СУБД сама их перекомпилирует.
    BODY
    Определяет, что нужно перекомпилировать тело пакета. Перекомпилирова- ние тела пакета не лишает законной силы объекты, которые зависят от спе- цификации пакета.
    DEBUG
    Дает указание компилятору генерировать и хранить код для использования отладчиком. Определение этого пункта имеет тот же самый эффект как уста- новка
    PLSQL_DEBUG=TRUE в
    compiler_parameters_clause
    compiler_parameters_clause
    Используется для определения текущих значений, указанных в перечне па- раметров компилятора PL/SQL.
    REUSE SETTINGS
    Не позволяет изменять существующие параметры настройки при перекомпи- ляции.
    18.5.4. Удаление пакета
    Для удаления всего пакета или только его тела (
    BODY
    ) используется син- таксис:
    DROP PACKAGE [BODY] [схема.]имя_пакета;

    Часть
    VI.
    Создание приложений на
    SQL
    342
    18.5.5. Примеры пакетов
    Продолжим рассмотрение примера разд. 18.4.5.
    1.
    Создадим строковый триггер
    ДОЛЖН_BSUR
    , в котором вызывается процеду- ра
    P_UPD1
    пакета
    PK_ДОЛЖ
    (см. далее). В нее передаются в качестве вход- ных параметров значения некоторых столбцов обновляемой строки.
    CREATE OR REPLACE TRIGGER должн_bsur
    BEFORE UPDATE OF состояние ON должности
    FOR EACH ROW
    BEGIN pk_долж.p_upd1(:OLD.ид, :OLD.начало,:OLD.должность,:OLD.состояние);
    END должн_bsur;
    /
    2.
    Аналогично создается табличный триггер
    ДОЛЖН_ATUR
    , в котором вызыва- ется процедура
    P_UPD2
    того же пакета.
    CREATE OR REPLACE TRIGGER должн_atur
    AFTER UPDATE OF состояние ON должности
    BEGIN pk_долж.p_upd2;
    END должн_atur;
    /
    3.
    Теперь создаем спецификацию (заголовок) пакета
    PK_ДОЛЖ
    , в которой опи- саны две процедуры, вызываемые в приведенных ранее триггерах:
    CREATE OR REPLACE PACKAGE pk_долж AS
    -- Описание структуры пакета процедур для триггеров таблицы ДОЛЖНОСТИ
    PROCEDURE p_upd1
    ( o_ИД ДОЛЖНОСТИ.ИД%TYPE, o_НАЧАЛО ДОЛЖНОСТИ.НАЧАЛО%TYPE, o_ДОЛЖНОСТЬ ДОЛЖНОСТИ.ДОЛЖНОСТЬ%TYPE, o_СОСТОЯНИЕ ДОЛЖНОСТИ.СОСТОЯНИЕ%TYPE
    );
    PROCEDURE p_upd2;
    END pk_долж;
    4.
    Далее создаем тело этого пакета:
    CREATE OR REPLACE PACKAGE BODY pk_долж AS
    -- Тело пакета процедур для триггеров таблицы ДОЛЖНОСТИ.
    -- Описание переменных, предназначенных для хранения некоторых
    -- значений строки

    Глава 18. Хранимые процедуры
    343
    v_ид должности.ид%TYPE; v_НАЧАЛО ДОЛЖНОСТИ.НАЧАЛО%TYPE; v_ДОЛЖНОСТЬ ДОЛЖНОСТИ.ДОЛЖНОСТЬ%TYPE; v_состояние должности.состояние%TYPE;
    -- Процедура, запускаемая триггером должн_bsur (BEFORE UPDATE ON
    -- ДОЛЖНОСТИ FOR EACH ROW). Этот строковый триггер стоит на первом месте
    -- в последовательности исполнения триггеров, созданных для UPDATE)
    --
    -- Получение значений столбцов обновляемой строки и перепись их
    -- в глобальные переменные
    PROCEDURE p_upd1( o_ид должности.ид%TYPE, o_НАЧАЛО ДОЛЖНОСТИ.НАЧАЛО%TYPE, o_ДОЛЖНОСТЬ ДОЛЖНОСТИ.ДОЛЖНОСТЬ%TYPE, o_состояние должности.состояние%TYPE
    ) IS
    BEGIN v_ид := o_ид; v_НАЧАЛО := o_НАЧАЛО; v_ДОЛЖНОСТЬ := o_ДОЛЖНОСТЬ; v_состояние := o_состояние;
    END p_upd1;
    --
    -- Процедура, запускаемая триггером должн_atur (AFTER UPDATE ON
    -- ДОЛЖНОСТИ). Этот табличный триггер стоит на втором месте
    -- в последовательности исполнения триггеров, созданных для UPDATE)
    --
    -- Анализ обновляемой строки и, если необходимо, принятие решения
    -- об отклонении обновления с выдачей сообщения о причине.
    PROCEDURE p_upd2 IS
    -- Описание локальных переменных и исключений
    ID NUMBER; test NUMBER; -- Рабочая переменная sost_val EXCEPTION; -- Нельзя изменять утвержденный должностной оклад
    BEGIN
    -- Если состояние строки "Утвержден", то изменять нельзя
    IF v_состояние = 'Утвержден' THEN
    RAISE sost_val;

    Часть
    VI.
    Создание приложений на
    SQL
    344
    END IF;
    -- Ищем строку с той же должностью и датой конца, равной '09.09.9999'
    SELECT COUNT(*) INTO test FROM должности
    WHERE должность = v_ДОЛЖНОСТЬ AND конец = '09.09.9999'
    AND состояние = 'Утвержден' AND ид <> v_ид;
    IF test <> 0 THEN
    -- Определяем номер найденной строки
    SELECT ид INTO ID FROM должности WHERE должность = v_ДОЛЖНОСТЬ
    AND конец = '09.09.9999' AND состояние = 'Утвержден'
    AND ид <> v_ид;
    -- Изменяем дату конца найденной строки
    UPDATE должности SET конец = v_НАЧАЛО-1 WHERE ид = ID;
    END IF;
    EXCEPTION
    WHEN sost_val THEN RAISE_APPLICATION_ERROR
    (-20044,'Нельзя изменять утвержденный должностной оклад!');
    END p_upd2;
    END pk_долж;
    /
    5.
    Снова попробуем утвердить должностной оклад:
    UPDATE должности SET состояние = 'Утвержден' WHERE ид = 10;
    6.
    Результат выполнения этого предложения:
    1 row updated
    7.
    Проверка результата обновления, выполненная с помощью предложения select * from должности t ORDER BY должность,начало;
    ИД ДОЛЖНОСТЬ ОКЛАД НАЧАЛО КОНЕЦ СОСТОЯНИЕ
    -- ----------------- ------ ----------- ----------- ----------
    2 Директор 250,00 05.01.1987 04.11.1987 Утвержден
    8 Директор 270,00 05.11.1987 09.09.9999 Утвержден
    1 Зав_производством 170,00 05.11.1987 14.11.1987 Утвержден
    9 Зав_производством 180,00 15.11.1987 14.12.1988 Утвержден
    10 Зав_производством 185,00 15.12.1988 09.09.9999 Утвержден
    4 Повар_1_категории 120,00 05.11.1987 09.09.9999 Утвержден
    5 Повар_2_категории 100,00 05.11.1987 09.09.9999 Утвержден
    6 Посудомойка 80,00 05.11.1987 09.09.9999 Утвержден
    7 Уборщица 80,00 05.11.1987 09.09.9999 Утвержден
    3 Шеф_повар 150,00 05.11.1987 09.09.9999 Утвержден

    Глава 18. Хранимые процедуры
    345
    18.6. Встроенные пакеты
    PL/SQL
    PL/SQL предлагает целый ряд дополнительных функций, которые доступны в поставляемых им пакетах. Эти пакеты принадлежат пользователю
    SYS
    , од- нако для них созданы общие синонимы, поэтому пакеты можно вызывать, не используя префикс
    SYS
    перед именем пакета. Для вызова процедур и функ- ций этих пакетов пользователь, который не является
    SYS
    , должен иметь пол- номочие
    EXECUTE
    на конкретный пакет. Дополнительную информацию можно найти в документации Oracle и литературе [10].
    Далее кратко описываются некоторые из почти тысячи встроенных пакетов
    (модулей), доступных для использования в PL/SQL.
    DBMS_ALERT
    Применяется для обмена сообщениями между сеансами, соединенными с од- ной и той же базой данных. Оповещения (alerts) синхронны, т. е. они посы- лаются при завершении транзакций. Оповещения отправляет процедура
    SIGNAL
    , а получают процедуры
    WAITONE
    и
    WAITANY
    . Чтобы получить оповеще- ние, сеанс должен быть зарегистрирован с помощью процедуры
    REGISTER
    Информацию об оповещениях можно найти в таблице словаря данных dbms_alert_info
    DBMS_APPLICATION_INFO
    Применяется для регистрации информации (info) о конкретной программе в системной таблице v$session
    . С помощью этой информации можно опре- делить, какие приложения (applications) функционируют в данный момент и какие действия они выполняют. В частности,
    DBMS_APPLICATION_INFO
    можно использовать для считывания и модификации столбцов module
    , action и client_info таблицы v$session текущего сеанса.
    В столбце module хранится имя приложения. Например, SQL*Plus помещает в этот столбец значение 'SQL*Plus'
    . В столбце action содержится фрагмент приложения, выполняющийся в данный момент. Например, можно задать определенное значение action перед вводом фрагмента программы Рго*С.
    В столбец client_info можно вносить произвольные строки символов.
    DBMS_APPLICATION_INFO
    может применяться также (с помощью процедуры
    SET_SESSION_LONGOPS
    ) для обновления таблицы v$session_longop
    , исполь- зуемой для указания статуса долговременных операций.
    DBMS_DDL
    Предоставляет PL/SQL-эквиваленты ряда полезных команд DDL, которые нельзя использовать в PL/SQL непосредственно. Для выполнения таких ко- манд можно применять динамический SQL, но в модуле
    DBMS_DDL
    они имеют

    Часть
    VI.
    Создание приложений на
    SQL
    346
    другой синтаксис. Среди процедур в
    DBMS_DLL
    имеются
    ALTER_COMPILE
    , служащая для компиляции пакетов, триггеров, процедур и функций, и
    ANALYZE_OBJECT
    , применяемая для анализа таблиц, кластеров и индексов.
    DBMS_OUTPUT
    DBMS_OUTPUT
    (в комбинации с SQL*Plus или
    SQLDEVELOPER
    ) предоставляет ограниченные возможности вывода для PL/SQL.
    Это удобно при тестировании и отладке кода PL/SQL.
    DBMS_OUTPUT
    не пред- назначен для написания отчетов — для этого лучше подходит утилита Oracle
    Reports.
    DBMS_SQL
    DBMS_SQL
    реализует динамический PL/SQL. С его помощью можно создавать операторы SQL и блоки PL/SQL во время выполнения и выполнять их. Кроме того,
    DBMS_SQL
    можно использовать для выполнения в PL/SQL операторов
    DDL. Oracle предлагает внутренний динамический SQL (см. разд. 17.8), бо- лее быстрый и простой, чем
    DBMS_SQL
    , использующийся в ранних версиях
    Oracle.
    DBMS_STANDARD
    и
    STANDARD
    Совместно
    DBMS_STANDARD
    и
    STANDARD
    реализуют все встроенные функции
    PL/SQL. В отличие от других модулей, имена входящих в них подпрограмм не нужно предварять именем модуля.
    DBMS_STATS
    Позволяет просматривать и модифицировать статистические показатели оп- тимизатора, хранящиеся в словаре данных или в определенной пользовате- лем таблице. Оптимизатор по стоимости выполнения будет использовать ста- тистику только из словаря данных.

    ЧАСТЬ
    V I I
    ПРИМЕР СОЗДАНИЯ
    БАЗЫ ДАННЫХ "UCHEB"
    1   ...   18   19   20   21   22   23   24   25   ...   28


    написать администратору сайта