Объектно-реляционные СУБД

advertisement
Объектно-реляционные СУБД.
Введение в объектно-реляционные СУБД.
До недавнего времени выбор типа СУБД ограничивался только реляционными и
объектно-реляционными СУБД. Но в настоящее время многие системы не подходят для
создания сложных специализированных приложений. Чтобы обеспечить возможность
применения реляционных СУБД в этих приложениях, необходимо расширить функциональные
возможности существующих систем. Если внимательно рассмотреть новые сложные
специализированные приложения баз данных, то можно заметить, что в них широко
используются такие объектно-ориентированные компоненты, как расширяемая пользователем
система типов, инкапсуляция, наследование, полиморфизм, динамическое связывание методов,
использование составных объектов, а также поддержка идентичных объектов. Наиболее
очевидный способ преодоления ограничений реляционной модели заключается в ее
расширении упомянутыми функциями. Именно этот подход был предпринят во многих
прототипах расширенных реляционных систем, хотя в каждой из них реализован свой
собственных и отличный от других набор функциональных возможностей. Но не существует
какой-то общепринятой расширенной реляционной модели, а скорее имеется несколько таких
моделей, характеристики которых зависят от способа и степени реализации внесенных
расширений. Однако во всех моделях используются одинаковые базовые реляционные таблицы
и язык запросов, включено понятие объекта, а в некоторых дополнительно реализована
возможность сохранения методов (или процедур, или триггеров) таким же способом, что и базе
данных.
Для систем с расширенной реляционной моделью данных используются самые разные
термины. Сначала применялся термин расширенная реляционная СУБД (Extended Relational
DBMS - ERDBMS). Однако в последние годы используется более информативный термин
объектно-реляционная СУБД, или ОРСУБД (Object-Relational DBMS - ORDBMS), в котором
содержится указание на использование понятия объект. Чаще всего используется термин
Объектно-реляционная СУБД или ОРСУБД. Три ведущих фирмы в области разработки ОРСУБД,
а именно Oracle, Informix, IBM, расширили свои системы до уровня ОРСУБД, хотя их
функциональные возможности немного отличаются. Концепция ОРСУБД, как комбинации
ООСУБД и РСУБД, очень притягательна за счет применения знаний и опыта, которые были
накоплены за время работы с РСУБД.
Разработка стандартов в этой сфере построена на расширении стандарта языка SQL.
Преимущества.
Основными преимуществами расширенной реляционной модели данных являются
повторное и совместное использование компонентов. Например, в приложении может
понадобиться использование данных пространственного типа, представляющие собой точки,
линии, и многоугольники, со связанными с ними функциями, которые вычисляют расстояние
между точками, расстояние между точкой и линией, проверяют наличие точки в
многоугольнике и т.д.
При правильном проектировании с учетом новых возможностей подобный подход
позволяет организациям воспользоваться преимуществами новых расширений эволюционным
путем без утраты преимуществ, получаемых от использования компонентов и функций уже
существующей базы данных.
1
Недостатки.
Очевидным недостатком подхода с использованием ОРСУБД являются сложность и
связанные с ней повышенные расходы. Простора и ясность, присущая реляционной модели,
утрачивается при использовании подобных типов расширения. Некоторые считают, что
расширения РСУБД предназначены для незначительного количества приложений, причем в
последних не может быть достигнута оптимальная производительность при использовании
имеющейся реляционной технологии. И многие другие, вплоть до терминологии.
Манифесты баз данных третьего поколения.
Комитет по вопросам расширения функциональности СУБД (Committee for Advance DBMS
Function - CADF) опубликовал манифест баз данных третьего поколения (Third-Generation
Database System Manifesto, Stonebraker et al.. 1990), в котором определено несколько
принципов, которым должна удовлетворять любая современная СУБД.
1. Прежде всего, обладать богатой системой типов.
2. Желательна поддержка механизма наследования.
3. Желательно, чтобы в ней поддерживались функции, включая процедуры базы данных и
методы, а также механизм инкапсуляции.
4. Уникальные идентификаторы для записей должны присваиваться средствами СУБД только
в том случае, когда нельзя использовать определенные пользователем первичные ключи.
5. Правила (триггеры и ограничения) станут важнейшим компонентом будущих систем. Они
должны быть связаны с какой-то конкретной функцией или коллекцией.
6. Очень важно, чтобы все программные способы доступа к базе данных осуществлялись с
помощью непроцедурного языка высокого уровня.
7. Должны существовать по крайней мере два способа определения коллекций: один на
основе перечисления членов коллекции, а другой – с использованием языка запросов для
определения членов в коллекции.
8. Существенным является наличие обновляемых представлений.
9. Индикаторы производительности не должны иметь никакого отношения к моделям данных
и не должны присутствовать в них.
10. СУБД третьего поколения должны быть доступны из многих языков высокого уровня.
11. Желательно, чтобы высокоуровневые языки программирования обладали перманентными
формами. Сверху все они должны поддерживаться единой СУБД с использованием
расширений компилятора и сложной системы поддержки выполнения программ.
12. Запросы и ответы на них должны составлять самый нижний уровень взаимодействия
клиента и сервера.
Создание ОРСУБД.
Переходим сразу к реализации, постановка задачи и E/R модель находятся документе
“Реляционная модель”, в разделе “Построение семантической модели”.
Реализовать эту модель можно по-разному, здесь приведены два способа.
1. Объектно-реляционный;
2. Реляционный, но с использованием типов определенных пользователем. В этой модели
используются внешние ключи, и все то, что можно было делать в реляционном подходе.
Перед тем как создавать типы, хорошо проанализируйте свою модель, какие типы вам
нужны, как и где вы будете использовать их в будущем (это просто совет). В данном примере
можно создать следующие типы: carriage_type, station_type, car_type, service_type, place_type,
advantage_type. Поскольку в типах нельзя создавать первичные ключи или задать какую-либо
уникальность данных, то я предложу два варианта, как решить эту проблему. Первый вариант чисто объектно-реляционный, в которой ответственность за внесение данных лежит на
пользователе. Необходимо предусмотреть, чтобы данные не дублировались (нельзя было
вставить одну и туже строку дважды), тип поезда м.б. либо “sp”-скорый, либо ”pass”2
пассажирский, а также тип вагона, обслуживающего персонала. Для этого нужно будет
написать триггеры, процедуры, также этот подход осложняется тем, что нет универсального
способа загрузки данных, нельзя будет использовать Loader, как это делали в реляционном
подходе. Эти запреты действуют лишь в Oracle, в стандарте SQL3 можно, следующий пример
демонстрирует это:
CREATE TYPE carriage type AS( id INT NOT NULL UNIQUE,
tr_num INT,
made_in VARCHAR2(100),
type VARCHAR2(50) CHECK(type=’plas’ OR type=’comp’));
CREATE TABLE carriage(
carriage carriage type,
PRIMARY KEY id);
или
CREATE TABLE carriage(
PRIMARY KEY id);
Реляционный подход, с использованием объектов.
Этот подход очень похож на обыкновенный реляционный подход, но с использованием
типов, определенных пользователем. В данном случае сразу решается проблема с ключами как
с первичными, так и с внешними. Нельзя лишь использовать check-и. Рассмотрим подробнее
типы и их создание (приведены SQL команды):
//тип – вагон, который определяет номер поезда, место формирования вагона и тип
вагона (plas или comp), в дальнейшем используется как тип – столбец.
CREATE TYPE carriage_type AS OBJECT( tr_num INT,
made_in VARCHAR2(100),
type VARCHAR 2(50));
/
//тип – вагон, этот тип, необходим для создания на основе его табличного типа, он
отличается от carriage_type, лишь отсутствием номера поезда.
CREATE TYPE car_type AS OBJECT (made_in VARCHAR 2(100),
type VARCHAR 2(50));
/
CREATE TYPE car_table_type AS TABLE of car_type;
/
//тип – станция, будет использоваться как тип – столбец, а также на его основе будет
создаваться табличный тип. Обратите внимание на содержание этого типа: город –
станция, название вокзала, время прибытия, время стоянки, тип (начальная(1), конечная(2),
промежуточная()), при наличии такого типа не будет необходимости в связи
“Останавливается”,
у
каждого поезда будет иметься вложенная таблица
“Остановки(stations)”.
CREATE TYPE staiton_type AS OBJECT (point VARCHAR2(100),
railst VARCHAR 2(100),
time_ar DATE,
time_st VARCHAR 2(30),
type INT);
/
//сразу создаю на основе station_type новый тип – таблицы
CREATE TYPE station_table_type AS TABLE of station_type;
/
//тип – поезд, который включает в себя тип поезда (скорый(sp) или
пассажирский(pass)), название маршрута, перечень всех остановок и вагонов. В этом типе две
вложенные таблицы.
3
CREATE TYPE train_type AS OBJECT ( type VARCHAR 2(50),
marshrut VARCHAR 2(100),
stations station_table_type,
carriages car_table_type);
/
//тип персонал – в нем находится вся информация о персонале, также типе
указывается либо проводник(cond), либо машинист(driv).
CREATE TYPE service_type AS OBJECT ( fio VARCHAR 2(150),
address VARCHAR2(150),
tel VARCHAR 2(50),
type VARCHAR 2(50));
/
//тип – место, номер вагона и номер места. (car_num должен был быть ссылкой на
“вагон”, но поскольку в типе, я не могу делать внешние ключи, делаю его типа int, а в
дальнейшем придется для этого написать либо триггер, либо этот факт учесть в процедуре
вставке, хотя вставлять место мы не будем – не имеет никакого смысла)
CREATE TYPE place_type AS OBJECT ( car_num INT,
num INT);
/
//тип – льготы
CREATE TYPE advantage_type AS OBJECT ( grup VARCHAR2(100),
persent REAL);
/
Теперь необходимо создать таблицы, также приведены SQL команды, (обратите
внимание на наличие ключей, все возможности реляционного подхода используются (кроме
ограничений)):
CREATE TABLE carriage( id INT PRIMARY KEY,
carriage carriage_type);
Обратите внимание на создание таблицы Train, а именно задание имен вложенным
таблицам (neasted).
CREATE TABLE train( id INT PRIMARY KEY,
train train_type)
NESTED TABLE train.stations STORE AS station_table
NESTED TABLE train.carriages STORE AS car_table;
CREATE TABLE service( id INT PRIMARY KEY,
service service_type);
CREATE TABLE place( id INT PRIMARY KEY,
place place_type);
CREATE TABLE advantage( id INT PRIMARY KEY,
adv advantage_type);
CREATE TABLE t_m( id INT PRIMARY KEY,
data DATE,
tr_num REFERENCES train(id) ON DELETE CASCADE);
CREATE TABLE t_s( id INT PRIMARY KEY,
data DATE,
tr_num REFERENCES train(id ON DELETE CASCADE,
ser_num REFERENCES service(id) ON DELETE CASCADE);
4
CREATE TABLE c_s( id INT PRIMARY KEY,
data DATE,
car_num REFERENCES carriage(id) ON DELETE CASCADE,
ser_num REFERENCES service(id) ON DELETE CASCADE);
CREATE TABLE buying( id INT PRIMARY KEY,
data DATE,
otkuda station_type,
kuda station_type,
pl_num REFERENCES place(id) ON DELETE CASCADE,
adv_num REFERENCES advantage(id) ON DELETE SET NULL,
passport VARCHAR 2(50),
price REAL,
flag INT CHECK(flag=1 OR flag='2'));
На этом этапе создана база, теперь необходимо заполнить ее данными.
Запуск загрузчика SQL*Loader.
По мере роста баз данных первостепенное значение имеет возможность быстрого и
эффективного пополнения внешними данными. Для решения этой проблемы, в состав продукта
Oracle включена программа – загрузчик SQL*Loader, которая позволяет загружать в базу
данных Oracle данные из внешних файлов. Загрузчик обладает множеством функциональных
возможностей, вот некоторые их них.
 Данные могут загружаться из нескольких файлов данных разных типов;
 Входные записи могут быть фиксированной или переменной длины;
 За один вызов можно загрузить много таблиц. Возможна также загрузка только выбранных
записей – каждой в соответствующую таблицу.
 Перед загрузкой в таблицы данные можно обрабатывать средствами SQL;
 Поддержка вложенных столбцов, вложенных таблиц, массивов и объектов LOB;
Компоненты загрузчика SQL*Loader.
5
Контрольный файл – это центральный нерв SQL*Loader. Он он задает параметры
преобразования информации из внешнего файла в данные, размещаемые в таблицах баз данных
Oracle. При загрузке будет выполнено неявное преобразование типов, а в случае некорректного
преобразования – выведено соответствующее сообщение. В контрольном файле используется
специальный язык определения данных SQL*Loader DDL.
Входные данные загрузчика. Зарузчик может воспринимать файлы входных данных,
представленных в различных форматах. Файлы могут храниться на диске, магнитной ленте или
быть встроенными в контрольный файл. Форматы записей данных могут быть различными фиксированной или переменной длины. Записи данных в формате фиксированной длины – это
записи, имеющие одинаковые размеры, причем поля данных в них также имеют
фиксированный размер, тип и позицию.
Таблицы и индексы Oracle. Загрузчик может за один сеанс загрузить сразу несколько
таблиц и индексов в базу данных Oracle.
Файл некорректных данных. Проверка загрузчиком данных при их вставке
выполняется в две стадьи.
1. Проверяется соответствие формата данных спецификации, заданной в контрольном файле.
Если формат данных или их размеры не соответствуют , то загрузчик заносит эту запись в
файл с некорректными данными.
2. Эта стадия происходит в самой базе данных. Записи может быть отвергнуты, если среди
них есть данные с неудовлетворительными результатами проверки ограничений и ошибки
преобразования типов.
Файл отброшенных записей. Загрузчик заносит записи в файл, если в контрольном
файле определены условия, а записи не удовлетворяют ни одному из них. В отличие от файла
некорректных данных, здесь по умолчанию может быть отброшено сколько угодно записей –
хоть все. Можно самостоятельно установить предел количества отбрасываемых записей,
используя в командной строке параметр discardmax.
Журнальный файл. Когда загрузчик начинает работу, он создает журнальный файл. Во
всех случаях, когда что-либо может помешать успешному созданию такого файла, процесс
загрузки прекращается. По умолчанию в качестве имени журнального файла используется имя
контрольного файла с расширением .log. В нем имеется несколько разделов, содержащих
сведения о среде, в которой выполняется сеанс загрузки, а также результаты и суммарная
статистика.
Систаксис контрольного файла.
Большинство контрольных файлов начинается ключевым словом: LOAD DATA
Другая комбинация символов в начале строки, которую тоже можно считать ключом, - это два
символа “минус” (--), которыми отмечается строка комментария. После этого следует
определение файла, испольэуемого при запуске,- источника внешних данных:
INFILE ‘имя файла.dat’
Указав несколько операторов INFILE, можно загрузить во время одного сеанса несколько
файлов данных.
INFILE ‘имя файла1.dat’
INFILE ‘имя файла2.dat’
Затем следует строка с методом загрузки. Указанный метод будет использоваться для всех
таблиц в течение сеанса загрузки.
Метод
INSERT
APPEND
REPLACE
Описание
Метод, используемый по умолчанию; при этом предполагается, что таблица
перед загрузкой была пустой. Если же в таблице оставались строки, то
выполнение загрузчика будет SQL*Loader прекращено.
Этот метод позволяет добавить строки в таблицу таким образом, чтобы они
не оказывали воздействия на уже имеющиеся строки.
При использовании этого метода удаляются все имеющиеся в таблице
строки, а затем загружаются новые. Отметим, что при удалении старых
6
строк срабатывает установленный для таблицы триггер удаления.
Для удаления старых строк используется SQL-команда TRUNCATE. Она
TRUNCATE немного удобнее, чем REPLACE, поскольку теперь при срабатывании
триггеров удаление не генерируются команды отката. Команда TRUNCATE
является необратимой для применения данного способа предварительно
убрать все ограничения ссылочной целостности для таблиц.
После этого следует определение таблицы.
INTO TABLE имя_таблицы метод
Здесь метод имеет такое же значение, как описано выше, но применяется только к таблице,
указанной в строке INTO TABLE.
После ключевых слов INTO TABLE следует спецификации полей и типов данных.
Загрузка таблиц.
В качестве примера применения загрузчика, я приведу контрольные и файлы с данными
для двух таблиц: train(необходимо загрузить только часть таблицы) и buying(вся таблица с
типами, определенными пользователем).
В таблицу train, я загрузчиком загружала только три поля: id, type, marshrut, а для
вложенных таблиц написана программа, которая перекидывает данные из таблицы с данными.
Контрольный файл (tr_load.ctl) выглядит следующим образом:
LOAD DATA
INFILE train.dat
APPEND INTO TABLE train
FIELDS TERMINATED BY '|'
(id, train column object(type, marshrut))
train column object - показываем, что train, это объект типа столбца.
Файл с данными (train.dat):
1|sp|Kazan-Moscow
2|pas|Kazan-Moscow
3|sp|Moscow-Adler
Вложенную таблицу carriages я заполняю из таблицы carriage следующей программой:
DECLARE
tr_id train2.id%type;
CURSOR cur is
SELECT id
FROM train2;
BEGIN
OPEN cur;
LOOP
FETCH cur into tr_id;
EXIT WHEN cur%NOTFOUND;
INSERT INTO THE(SELECT tt.train.carriages FROM train2 tt WHERE tt.id=tr_id)
VALUES(CAST(MULTISET(
SELECT cc.carriage.made_in, cc.carriage.type
FROM carriage cc
WHERE cc.carriage.tr_num=tr_id)AS car_table_type));
END LOOP;
CLOSE cur;
END;
.
RUN;
7
Теперь заполняется вся таблица- контрольный файл bu_load.ctl:
LOAD DATA
INFILE buying.dat
APPEND INTO TABLE buying
FIELDS TERMINATED BY '|'
TRAILING NULLCOLS
--могут быть null –значения.
(id, data date 'dd.mm.yy',
otkuda column object (point, railst, time_ar date 'hh24:mi:ss', time_st, type),
kuda column object(point, railst, time_ar date 'hh24:mi:ss', time_st, type),
pl_num, adv_num, passport, price, flag)
time_ar date 'hh24:mi:ss' –вставляем время.
Файл данных buying.dat
1|01.09.02| Kazan|Central|18.30|10 min|1| Moscow|Kazan|7,00|10 min|2|1|1|X1X-KB 456890|350|1
2|01.09.02| Kazan|Central|18.30|10 min|1| N.Novgorod|Central|1,00|10 min||49|2|
XIX-KB 234867|247|1
На этом этапе можно решить еще одну маленькую проблему, создать последовательность,
чтобы при добавлении новой строке не думать какое число поставить, чтобы оно не
повторилось. Последовательность – это своего рода счетчик, который всякий раз при
добавлении строки будет увеличиваться на единицу. Она создается для каждой таблицы
отдельная, я приведу лишь одну из них:
CREATE SEQUENCE tr_id //создаем последовательность
INCREMENT BY 1
//указываем разницу между индексами
START WITH 13
//с какого числа начать, 12 уже вставлено загрузчиком
MINVALUES 0
//минимальное значение
MAXVALUES 99999
//максимальное значение
NOCYCLE
NOORDER
CACHE 20;
Чтобы счетчик увеличился необходимо указать имя последовательности и действие:
tr_id.nextval –увеличить,
также можно получить текущее значение, изменять последовательность нельзя!




Триггеры и их создание.
Используйте следующие рекомендации при проектировании триггеров:
Используйте триггеры для того, чтобы гарантировать, что при выполнении определенной
операции будут выполнены связанные с ней действия.
Не определяйте триггеров, дублирующих возможности, уже встроенные в ORACLE.
Например, не определяйте триггеров для ввод в действие правил целостности данных,
которые могут быть легко реализованы посредством декларативных ограничений
целостности.
Будьте внимательны, чтобы не создавать рекурсивных триггеров. Например, создание
такого триггера AFTER для предложения UPDATE по таблице emp, который сам выдает
предложение UPDATE по таблице emp, приведет к рекурсивному возбуждению
этого триггер вплоть до переполнения чисел триггеров.
Имена триггеров должны быть уникальными среди всех триггеров в той же схеме. Имен
триггеров не обязаны быть уникальными по отношению к другим объектам схемы (таких
как таблицы, обзоры, процедуры); например, таблица и триггер могут иметь одно и то же
имя (хотя, во избежание путницы, это не рекомендуется).
8

Лишь один триггер каждого типа может существовать на таблицу. Это позволяет иметь для
таблицы двенадцать возможных триггеров:
BEFORE UPDATE строка
BEFORE DELETE строка
BEFORE INSERT предложения
BEFORE INSERT строка
BEFORE UPDATE предложения
BEFORE DELETE предложения
AFTER UPDATE строка
AFTER DELETE строка
AFTER INSERT предложения
AFTER INSERT строка
AFTER UPDATE предложения
AFTER DELETE предложения
Если вы попытаетесь повторно создать любой тип триггера, уже существующий для
таблицы, вы получите ошибку времени компиляции. Нельзя также управлять транзакциями
в контексте триггера. Поэтому внутри контекста тел триггера не допускаются следующие
предложения: ROLLBACK, COMMIT и SAVEPOINT.
Замечание: Процедура, вызываемая из триггера, также не может выполнять перечисленных
выше предложений управления транзакциями, ибо такая процедура исполняется внутри
контекста тела триггера.
Создание триггеров.
Триггеры создаются с помощью команды CREATE TRIGGER. Эту команду можно
использовать в любом интерактивном инструменте (таком как SQL*Plus или SQL*DBA);
при использовании в таких инструментах, одиночная наклонная черта ("/"), вводимая как
последняя строк, обозначает конец предложения CREATE TRIGGER. Следующее предложение
создает триггер, ассоциированный с таблицей emp:
CREATE TRIGGER dummy
BEFORE DELETE OR INSERT OR UPDATE ON emp
FOR EACH ROW
WHEN (new.empno > 0)
DECLARE
/* переменные, констнты, курсоры и т.п. */
BEGIN
/* блок PL/SQL */
END;
Опция FOR EACH ROW
Присутствие или отсутствие опции FOR EACH ROW определяет, является ли этот триггер
триггером предложения или триггером строки. Если эта опция включен, он указывает, что
тело триггер возбуждается отдельно для каждой строки таблицы, затрагиваемой предложением
триггер. Отсутствие опции FOR EACH ROW указывает, что данный триггер должен
возбуждаться лишь один раз для предложения триггер.
Тело триггера
Это блок PL/SQL, который может содержать предложения SQL и PL/SQL. Для
триггеров строк тело триггера имеет некоторые специальные конструкты, которые могут
быть включены в код этого блок PL/SQL: корреляционные имен, опцию REFERENCING, также
условные предикаты INSERTING, DELETING и UPDATING.
Доступ к значениям столбцов в триггерах строки
Внутри тел триггер строк, код PL/SQL и предложения SQL имеют доступ как к старым,
так и к новым значениям столбцов текущей строки, затрагиваемой предложением триггер.
Для каждого столбца модифицируемой таблицы определены два корреляционных имени:
одно для строго (old), другое - для нового значения
столбца (new). В зависимости от типа
предложения триггер, то или иное корреляционное имя может быть лишено смысла.
 Триггер, возбужденный предложением INSERT, имеет осмысленный доступ лишь к новым
значениям столбцов. Поскольку строка создается предложением INSERT, старые
значения столбцов пусты (NULL).
 Триггер, возбужденный предложением UPDATE, имеет доступ как к старым, так и к новым
значениям столбцов для обоих возможных типов триггер (BEFORE или AFTER).
9



Триггер, возбужденный предложением DELETE, имеет осмысленный доступ лишь к старым
значениям столбцов. Поскольку строка перестает существовать после ее удаления, новые
значения столбцов пусты (NULL).
Стрые и новые значения доступны как в триггерах BEFORE, так и в триггерах AFTER.
Назначать новое значение столбца можно в триггере строка BEFORE, но не в триггере строк
AFTER (потому что предложение триггер уже выполнено, прежде чем триггер AFTER
получает управление). Если триггер строка BEFORE изменяет значение NEW для столбца, то
триггер AFTER, возбужденный тем же самым предложением, видит значение, которое было
назначено триггером BEFORE.
Корреляционные имен могут также использоваться в булевском выражении фразы
WHEN. Заметьте, что перед квалификаторами OLD и NEW должно кодироваться двоеточие,
когда они используются в теле триггера, но двоеточие не допускается, когда эти
квалификаторы используются в фразе WHEN или опции REFERENCING.
Опция REFERENCING может специфицироваться в теле триггера строки для того, чтобы
избежать конфликтов между корреляционными именами и именами таблиц, в случае, если
таблица имеет имя "OLD" или "NEW". Поскольку такая ситуация редка, эта опция почти
никогда не применяется.
Например, предположим, что у вас есть таблица с именем new, содержащая столбцы field1
(числовой) и field2 (символьный). Следующее предложение CREATE TRIGGER показывает
триггер, ассоциированный с таблицей new, который использует опцию REFERENCING,
чтобы избежать конфликтов между корреляционными именами и именем таблицы:
CREATE TRIGGER dummy
BEFORE UPDATE ON new
REFERENCING new AS newest
FOR EACH ROW
BEGIN
:newest.field2 := TO_CHAR (:newest.field1);
END;
Заметьте, как квалификатор new переименован вnewest с помощью опции
REFERENCING, а затем использован в теле триггера.
Условные предикаты
Если триггер может быть возбужден более чем одним типом предложения DML
(например, "INSERT OR DELETE OR UPDATE OF emp"), то в теле триггер можно использовать
условные предикаты INSERTING, DELETING и UPDATING, для того чтобы выполнять
различные участки код в зависимости от тип предложения, возбудившего триггер.
Предположим, что предложение триггера определено следующим образом:
INSERT OR UPDATE ON emp
В коде внутри тела триггера вы можете использовать следующие условия:
IF INSERTING THEN . . . END IF;
IF UPDATING THEN . . . END IF;
Первое условие будет вычисляться как TRUE лишь в тех случаях, когда триггер был
возбужден предложением INSERT; второе условие будет вычисляться как TRUE лишь в тех
случаях, когда триггер был возбужден предложением UPDATE.
Кроме того, в триггере UPDATE условный предикат UPDATING можно
специфицировать перед именем столбца, чтобы определять, обновляется ли этот столбец
текущим предложением, возбудившим триггер. Например, предположим, что триггер
определен следующим образом:
CREATE TRIGGER . . .
. . . UPDATE OF sal, comm ON emp . . .
BEGIN
. . . IF UPDATING ('SAL') THEN . . . END IF;
END;
10
Код в фразе THEN выполняется лишь в том случае, если предложение UPDATE,
возбудившее триггера, обновляет столбец SAL. Например, следующее предложение возбудит
показанный выше триггер и заставит условный предикат вычисляться как TRUE:
UPDATE emp SET sal = sal + 100;
Мутирующие таблицы
Это таблица, модифицируемая в данный момент предложением UPDATE, DELETE или
INSERT, или таблица, которая может потребовать обновления в результате действия
декларативного ссылочного ограничения целостности DELETE CASCADE
Предложения в триггере строк не могут читать или модифицировать мутирующую таблицу
предложения триггера. Это ограничение не позволяет триггеру строк иметь дело с
несогласованным множеством данных.
Исходная
Предложение SQL,
Мутирующая
таблица EMP
возбуждающее триггер
таблица EMP
┌─────────────┐
┌─────────────┐
│ ENAME SAL │ UPDATE emp
│ ENAME SAL │
│ ----- --- │ SET sal = sal * 1.1; │ ----- --- │
│ SMITH 1000 │
│▒SMITH▒▒1100▒│─────┐
│ JONES 1000 │ ────────────────────_ │ JONES 1000 │
│
│ WARD 1000 │
│ WARD
1000 │
│
└─────────────┘
└─────────────┘
│
│
│
│
Не допускается, ─────────────────────_ X
│
так как таблица EMP
└─────────────────┘
мутирует
Возбуждаемый триггер AFTER ROW
содержит предложение:
SELECT sal FROM emp WHERE ...
Этот пример работает следующим образом. Предложение SQL исполняется для первой
строки таблицы. Затем возбуждается триггер. В свою очередь, предложение внутри тел триггер
пытается опросить первоначальную таблицу. Однако, поскольку таблица emp мутирует, Oracle
не позволяет этот запрос. Поэтому возникает ошибка времени выполнения, эффекты тел
триггер и предложения, возбудившего триггера, откатываются, и управление возвращается
пользователю или приложению.
Если вам необходимо обновить мутирующую или ограничивающую таблицу, вам следует
использовать временную таблицу, таблицу PL/SQL
Включение и выключение триггеров.
Триггер может находиться в одном из двух различных режимов:
1. Включенный триггер выполняет свое тело, если видно предложение триггера, и
ограничение триггера (если есть) вычисляется как TRUE.
Вы можете временно выключить триггер, если имеет место одно из следующих условий:
 Объект, к которому обращается триггер, недоступен.
 Вы должны выполнить массовую загрузку данных, и хотите осуществить ее быстро, не
возбуждая триггеров.
 Вы загружаете данные в таблицу, к которой применяется триггер.
2. Выключенный триггер не выполняет свое тело, даже если видно предложение триггера, и
ограничение триггера (если есть) вычисляется как TRUE.
Вы можете временно выключить триггер, если имеет место одно из следующих условий:
 Объект, к которому обращается триггер, недоступен.
 Вы должны выполнить массовую загрузку данных, и хотите осуществить ее быстро, не
возбуждая триггеров.
 Вы загружаете данные в таблицу, к которой применяется триггер.
11
По умолчанию, триггер включается в момент его создания. Чтобы отключить триггер,
используйте команду ALTER TRIGGER с опцией DISABLE. Например, следующее предложение
отключает триггер REORDER по таблице INVENTORY:
ALTER TRIGGER reorder DISABLE;
Вы можете одновременно отключить все триггеры, ассоциированные с таблицей, с помощью
команды ALTER TABLE с опциями DISABLE и ALL TRIGGERS. Например, следующее
предложение отключает все триггеры, определенные для таблицы INVENTORY:
ALTER TABLE inventory
DISABLE ALL TRIGGERS;
Получение информации о триггере REORDER:
SELECT type, triggering_statement, table_name
FROM user_triggers
WHERE name = 'REORDER';
TYPE
TRIGGERING_STATEMENT TABLE_NAME
---------------------------- ---------------------------------------- -----------AFTER EACH ROW UPDATE
INVENTORY
SELECT trigger_body
FROM user_triggers
WHERE name = 'REORDER';
TRIGGER_BODY
--------------------------------------------DECLARE
x NUMBER;
BEGIN
SELECT COUNT(*) INTO x
FROM pending_orders
WHERE part_no = :new.part_no;
IF x = 0 THEN
INSERT INTO pending_orders
VALUES (:new.part_no, :new.reorder_quantity, sysdate);
END IF;
END;
Ограничения и триггеры.
Теперь приведу в качестве примера решение проблемы с ограничениями. Ниже процедура
вставки персонала, где проверяется наличие его в таблице и правильность заполнения поля type.
CREATE OR REPLACE PROCEDURE ser_insert(
snum IN int,
fio IN varchar2,
adr IN varchar2,
tel IN varchar2,
tp IN varchar2,
res OUT number) IS
DECLARE
cnt int;
BEGIN
res:=1;
SELECT count(*) AS coun INTO cnt
FROM service ss
WHERE ss.service.fio=fio AND
ss.service.address=adr AND
ss.service.tel=tel AND
12
ss.service.type=pl;
IF((tp='cond' OR tp='driv') AND cnt=0) THEN
INSERT INTO carriage VALUES
(ser_id.nextval, service_type(fio, adr, tel, tp));
ELSE
res:=-1;
END IF;
END;
/
Пример вызова этой процедуры:
DECLARE p number;
BEGIN
ser_insert('Иванов','Чуйкова дом 45 кв.21', '56-78-90', ’driv’, p);
END;
/
Если такого нет, то вставится одна строка, если не правильно задан тип или уже имеется
уже такой машинист, точно такими же данными, то на экране появится сообщения об успешно
выполненной процедуре.
И еще один пример на создание триггера на обновление размера скидки, в этом случае
должна пересчитаться цена у всех забронированных билетов (flag=2);
CREATE OR REPLACE TRIGGER price_up AFTER UPDATE on advantage
FOR EACH ROW
DACLARE
newp buying.price%type;
CURSOR b IS
SELECT *
FROM buying
WHERE adv_num=:old.id AND flag=2
FOR UPDATE;
str b%rowtype;
BEGIN
OPEN b;
LOOP
FETCH b INTO str;
EXIT WHEN b%NOTFOUND;
newp:=str.price/(1-:old.adv.persent)-:new.adv.persent*str.price/(1-:old.adv.persent);
UPDATE buying SET price=newp WHERE CURRENT OF b;
END LOOP;
CLOSE b;
END;
.
run
UPDATE advantage aa SET aa.adv.persent=0.20 WHERE aa.adv.grup='Pensioners';
Запросы.
Ниже приведены несколько простых запросов к созданной базе данных:
//Выдать все поезда, идущие в определенную дату
SELECT data, tt.train.marshrut AS marshrut
FROM train tt, t_m
WHERE tt.id=t_m.id AND data='09/10/02';
DATA
MARSHRUT
------------01.09.02
--------------------------Kazan-Moscow
13
01.09.02
Kazan-Moscow
//Выдать все свободные места на определенный маршрут
SELECT DISTINCT(pp.place.num) AS place,
pp.place.car_num AS carge,
cc.carriage.type AS type
FROM carriage cc, place pp, buying bb, t_m
WHERE cc.carriage.tr_num=1 AND
t_m.tr_num=1 AND
t_m.data='01/09/02' AND
pp.place.car_num=cc.carriage.id AND
pp.place.id NOT IN(SELECT pl_num
FROM buying);
PLACE
CARGE
TYPE
MARSHRUT
------------ -------------- ---------- -------------------------1
2
plas
Kazan-Moscow
1
3
comp
Kazan-Moscow
2
1
comp
Kazan-Moscow
2
2
plas
Kazan-Moscow
2
3
comp
Kazan-Moscow
//Узнать по забронированным билетам, куда едут
SELECT DISTINCT(place.num) AS place,
pp.place.car_num AS carge, kuda
FROM carriage cc, t_m, place pp, buying
WHERE cc.carriage.tr_num=t_m.tr_num AND
pp.place.car_num=cc.carriage.id AND
type=2;
OTKUDA
KUDA
PASSPORT
--------------- ------------------ ----------------------Kazan
Moscow
X1X-KB 456890
Kazan
G.Hrustalny XIX-KB 234867
Объектно-реляционный подход.
Этот подход отличается тем, что здесь не выделяется отдельное поле для ключа, поле
id int включается в описание типа. В данном подходе разработчик должен учитывать, что нет
ключей и это всегда проверять, хотя сами коды программ, процедур и триггеров сильно
отличаться не будут. В этом случае будет больше по количеству триггеров, поскольку нет такой
возможности как on delete cascade/on update… и т.д. Я приведу лишь само создание, вставки,
некоторые программы, процедуры и запросы. Хотя они очень похожи.
Создание типов:
CREATE TYPE carriage_type AS object( id INT,
tr_num INT,
made_in VARCHAR2(100),
type VARCHAR2(50));
/
CREATE TYPE car_type AS object( id INT,
made_in VARCHAR2(100),
type VARCHAR2(50));
/
14
CREATE TYPE car_table_type AS TABLE OF car_type;
/
CREATE TYPE station_type AS object( point VARCHAR2(100),
railst VARCHAR2(100),
time_ar DATE,
time_st VARCHAR2(20)
type VARCHAR2(50));
/
CREATE TYPE station_table_type AS TABLE OF station_type;
/
CREATE TYPE train_type AS OBJECT( id INT,
type VARCHAR2(50),
stations station_table_type,
carriages car_table_type);
/
CREATE TYPE service_type AS OBJECT( id INT,
fio VARCHAR2(200),
address VARCHAR2(200),
tel VARCHAR2(30),
type VARCHAR2(50));
/
CREATE TYPE place_type AS OBJECT( id INT,
car_num REF carriage_type
num INT);
/
CREATE TYPE adv_type AS OBJECT( group VARCHAR2(100),
persent REAL);
/
Создаем таблицы:
CREATE TABLE carriage OF carriage_type;
/
CREATE TABLE train OF train_type
NESTED TABLE stations STORE AS station_table
NESTED TABLE carriages STORE AS car_table;
/
CREATE TABLE service OF service_type;
/
CREATE TABLE place OF place_type;
/
CREATE TABLE advantage OF advantage _type;
/
CREATE TABLE t_m( id INT PRIMARY KEY,
data DATE,
tr_num REF train_type,
mar_num REF marshrut_type);
CREATE TABLE t_s( id INT PRIMARY KEYy,
data DATE,
tr_num REF train_type,
ser_num REF service_type);
15
CREATE TABLE c_s( id INT PRIMARY KEY,
data DATE,
car_num REF carriage_type,
ser_num REF service_type);
CREATE TABLE buying( id INT PRIMARY KEY,
data DATE,
otkuda station_type,
kuda station_type,
pl_num REF place_type,
advantage REF advantage_type,
passport VARCHAR2(30),
price REAL,
flag INT CHECK(flag=1 OR flag='null'));
Вставки по одному кортежу в каждую таблицу.
INSERT INTO carriage VALUES(carriage_type(1,1,'Kazan','comp');
INSERT INTO train VALUES (train_type(1,'pas',
station_table_type(station_type('Kazan','center',to_date(‘18.30’, ‘hh24:mi:ss’),'10 min',1),
station_type('Kanash','center',to_date(‘23.00’, ‘hh24:mi:ss’),'3 min'),
station_type('Novgorod','center',te_date(‘1.00’, ‘hh24:mi:ss’),'10 min'),
station_type('Moscow','Kazan',to_date(‘7.00’, ‘hh24:mi:ss’),'10 min',2)),
CAST(MULTISET(
SELECT cc.id, cc.made_in, cc.type
FROM carriage cc
WHERE cc.tr_num=1
)AS car_table_type)));
INSERT INTO t_m VALUES (1,to_date('01.09.02','dd.mm.yy'),
(SELECT REF(tr) FROM train tr WHERE tr.id=1));
INSERT INTO service VALUES(
service_type(1,'Stiven Karten','Dubrovka 134','23-56-89','con'));
INSERT INTO service VALUES (
service_type(2,'Julia Sokol','Dubrovka 100','74-56-89','driv'));
INSERT INTO place VALUES (
place_type(1,(SELECT REF(car) FROM carriage car WHERE car.id=1),1),
place_type(1,( SELECT REF (car) FROM carriage car WHERE car.id=1),2));
INSERT INTO advantage VALUES (advantage_type(1,'students',0.50));
INSERT INTO t_s VALUES (1,to_date('01.09.02','dd.mm.yy'),
(SELECT REF (tr) FROM train tr WHERE tr.id=1),
(SELECT REF (ser) FROM service ser WHERE ser.id=1));
INSERT INTO c_s VALUES (1,to_date('01.09.02','dd.mm.yy'),
(SELECT REF (car) FROM carriage car WHERE car.id=1),
(SELECT REF (ser) FROM service ser WHERE ser.id=2));
INSERT INTO buying VALUES (1,to_date('31.08.02','dd.mm.yy'),
station_type('Kazan','center',18.30,'10 min'),
station_type('Moscow','Kazan',7.00,'10 min'),
16
(SELECT REF (pl) FROM place pl WHERE pl.id=1),
(SELECT REF (aa) FROM advanture aa WHERE aa.id=1),
'X1X 67845-BK',
300,1);
Пример заполнения таблицы train из другой таблицы:
DECLARE
id_tr train2.id%type;
tp train2.typ%type;
CURSOR cur is
SELECT id, typ
FROM train2;
BEGIN
OPEN cur;
LOOP
FETCH cur INTO id_tr, tp;
EXIT WHEN cur%NOTFOUND;
INSERT INTO train VALUES (train_type(id_tr, tp,
station_table_type(station_type(0,0,0,0)),
(CAST(MULTISET(
SELECT cc.id, cc.made_in, cc.type
FROM carriage cc
WHERE cc.tr_num=id_tr
)AS car_table_type))));
END LOOP;
CLOSE cur;
END;
.
RUN;
CREATE OR REPLACE PROCEDURE insert_stations(
id_tr IN int,
point IN varchar2,
railst IN varchar2,
time_ar IN float,
time_st IN varchar2,
type IN varchar2
) AS
BEGIN
INSERT INTO THE(SELECT stations FROM train WHERE id=id_tr)
VALUES (train_type(station_type(point,railst,time_ar,time_st,type)));
END;
.
RUN;
Запросы, что и в предыдущей части, для этого варианта результаты получаются
аналогичные:
//Выдать все поезда, идущие в определенную дату
SELECT data, tt.marshrut AS marshrut
FROM train tt, t_m
WHERE tt.id=t_m.id AND data='09/10/02';
17
//Выдать все свободные места на определенный маршрут
SELECT distinct(pp. num) AS place,
pp. car_num AS carge,
cc. type AS type
FROM carriage cc, place pp, buying bb, t_m
WHERE cc. tr_num=1 AND
t_m.tr_num=1 AND
t_m.data='01/09/02' AND
pp.car_num=cc.id AND
pp.id NOT IN(SELECT pl_num
FROM buying);
//Узнать по забронированным билетам, куда едут
SELECT DISTINCT(pp.num) AS place,
pp.car_num AS carge, kuda
FROM carriage cc, t_m, place pp, buying
WHERE cc.tr_num=t_m.tr_num AND
pp.car_num=cc.id AND
type=2;
18
Download