Книга: Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ
Поддержание обязательного отношения
Поддержание обязательного отношения
Обязательное отношение существует, когда две таблицы связаны через зависимость внешнего ключа и должна существовать по меньшей мере одна строка для каждой первичной строки. Поскольку SQL не предоставляет ограничения "обязательности", нужна логика триггера для осуществления правила "минимум один потомок" не только во время создания, но и при удалении зависимых строк.
Следующий пример вкратце описывает один способ использования триггеров для осуществления такого обязательного отношения главная-подчиненная. В нем предполагается, что первичный ключ главной таблице известен в приложении до того, как посылается новая запись.
Во-первых, создадим две таблицы:
CREATE TABLE MASTER (
ID INTEGER NOT NULL PRIMARY KEY,
DATA VARCHAR(10));
COMMIT;
CREATE TABLE DETAIL (
ID INTEGER NOT NULL PRIMARY KEY,
MASTER_ID INTEGER,
/* Столбец внешнего ключа сознательно сделан в виде, допускающем пустое значение */
DATA VARCHAR(10),
TEMP_FK INTEGER,
CONSTRAINT FK_MASTER FOREIGN KEY(MASTER_ID)
REFERENCES MASTER
ON DELETE CASCADE);
COMMIT;
Когда приложение посылает (post) строки главной и подчиненной таблиц, оно вначале будет передавать подчиненные строки со значением NULL в столбце внешнего ключа и со значением первичного ключа главной таблицы в столбце TEMP FK.
Затем нам нужно исключение, которое будет возникать при попытке нарушения правила обязательности и при удалении последней подчиненной строки. Мы также создадим генератор для подчиненной строки.
CREATE EXCEPTION CANNOT_DEL_DETAIL
'This is the only detail record: it can not be deleted.';
/* Это единственная подчиненная запись: она не может быть удалена */
CREATE GENERATOR GEN_DETAIL;
COMMIT;
Следующий триггер проверяет подчиненную таблицу после добавления новой версии главной записи. Он может "видеть" подчиненные строки, ранее посланные в той же транзакции, которые имеют значение первичного ключа (NEW.ID) В столбце TEMP FK. В случае изменения, в отличие от добавления, он также может определить подчиненные строки, которыми уже "владеет" главная строка. Любые строки, которые соответствуют условию TEMP_FK, получают значение их внешнего ключа, а TEMP_FK устанавливается в NULL.
Если не найдено строк, соответствующих этому условию, то триггер добавляет "пустую" подчиненную строку.
SET TERM ^;
CREATE TRIGGER AI_MASTER FOR MASTER
ACTIVE AFTER INSERT OR UPDATE POSITION 1
AS
BEGIN
IF (NOT (EXISTS (
SELECT 1 FROM DETAIL WHERE MASTER_ID = NEW.ID
OR TEMP_FK = NEW.ID))) THEN
INSERT INTO DETAIL (MASTER_ID)
VALUES (NEW.ID);
ELSE
IF (NOT (EXISTS (
SELECT 1 FROM DETAIL WHERE MASTER_ID = NEW.ID))) THEN
UPDATE DETAIL SET
MASTER_ID = NEW.ID,
TEMP_FK = NULL
WHERE TEMP_FK = NEW.ID;
END ^
Подчиненная таблица получает автоматически сгенерированный ключ:
CREATE TRIGGER BI_DETAIL FOR DETAIL
ACTIVE BEFORE INSERT AS
BEGIN
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(GEN_DETAIL, 1);
END ^
Следующий триггер BEFORE DELETE для подчиненной таблицы не позволит удалить строку, если она единственная:
CREATE TRIGGER BD_DETAIL FOR DETAIL
ACTIVE BEFORE DELETE POSITION 0
AS
BEGIN
IF (NOT (EXISTS (
SELECT 1 FROM DETAIL
WHERE MASTER_ID = OLD.MASTER_ID
AND ID <> OLD.ID)))
THEN
EXCEPTION CANNOT_DEL_DETAIL;
END ^
Сейчас у нас ситуация, когда обязательное отношение защищено настолько хорошо, что при попытке удалить главную строку этот триггер не позволит выполнить каскадное удаление. Нам нужно еще два триггера для главной таблицы, расширяющие действия триггеров, созданных системой для каскадного удаления. В триггере BEFORE DELETE для главной таблицы мы заполняем пустым значением внешний ключ подчиненной таблицы и устанавливаем значение столбца TEMP FK. После того как будет
выполнено удаление строки главной таблицы, мы возвращаемся назад и удаляем подчиненную строку.
CREATE TRIGGER BD_MASTER FOR MASTER
ACTIVE BEFORE DELETE
AS
BEGIN
UPDATE DETAIL
SET MASTER_ID = NULL,
TEMP_FK = OLD.ID
WHERE MASTER_ID = OLD.ID;
END ^
/* */
CREATE TRIGGER AD_MASTER FOR MASTER
ACTIVE AFTER DELETE AS
BEGIN
DELETE FROM DETAIL
WHERE TEMP_FK = OLD.ID;
END ^
COMMIT ^
SET TERM; ^
К сожалению, этот пример вряд ли удовлетворит всем требованиям обязательных отношений. Обычно необходимо рассмотреть некоторые другие факторы в терминах требований бизнес-правил и интерфейса программирования. Для логики триггеров редко не появляются такие причины.
- 3.4. Отношения между классами
- 5. Отношения. Типы отношений
- 2. Пример создания базового отношения в записи на псевдокоде
- Отношения типа многие-ко-многим
- Отношения типа один-ко-многим
- Отношения типа один-к-одному
- Операции отношения
- Агрегационные отношения
- 1.1. Пример программы: родственные отношения
- Поддержание состояния в специальном элементе управления
- Поддержание рентабельности: увеличение доходов за счет использования премиальных наценок (ценовых надбавок)
- Отношения и сигнатуры операций