Oracle Unique Constraint - триггер для проверки значения свойства в новом отношении - PullRequest
1 голос
/ 27 апреля 2011

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

CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1" 
FOR EACH ROW
BEGIN
Select "Table_2"."number" 
(CASE "Table_2"."number" > 0
  THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
END)
from "Table_1"
WHERE "Table_2"."ID" = :new.FK_Table_2_ID 
END;

Редактировать: ответ APC удивительно исчерпывающий, однако заставляет меня думать, что я делаю это неправильно.

Ситуация такова, что у меня есть таблица людей с разными уровнями привилегий, и я хочу проверить эти уровни привилегий, например, Пользователь «Боб» имеет права низкого уровня, и он пытается стать начальником отдела, который требует высоких прав, чтобы система не допускала этого.


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

Ответы [ 2 ]

5 голосов
/ 27 апреля 2011

Таким образом, правило, которое вы хотите применить, заключается в том, что TABLE_1 может ссылаться только на TABLE_2, если какой-либо столбец в TABLE_2 равен нулю или меньше.Хммм .... Давайте разберем логику триггера, а затем обсудим правило.

Триггер должен выглядеть следующим образом:

CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1" 
FOR EACH ROW
declare
  n "Table_2"."number".type%;
BEGIN

    Select "Table_2"."number" 
    into n
    from "Table_2"
    WHERE "Table_2"."ID" = :new.FK_Table_2_ID; 

    if n > 0
    THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
    end if;

END;

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


Здесь вы пытаетесь применить тип ограничения, известный какУТВЕРЖДЕНИЕ.Утверждения указаны в стандарте ANSI, но Oracle их не реализовал.Ни одна другая СУБД не подходит к этому.

Утверждения проблематичны, потому что они симметричны.То есть правило также должно быть применено к TABLE_2.В данный момент вы проверяете правило при создании записи в TABLE_1.Предположим, что через некоторое время пользователь обновит TABLE_2.NUMBER, чтобы он был больше нуля: ваше правило теперь нарушено, но вы не будете знать, что оно нарушено, пока кто-нибудь не выдаст совершенно не связанное ОБНОВЛЕНИЕ в TABLE_1,который тогда потерпит неудачу.Гадость.

Итак, что делать?

Если правило действительно

TABLE_1 может ссылаться только на TABLE_2, если TABLE_2.NUMBER равен нулю

затем вы можете принудительно применить его без триггеров.

  1. Добавить ограничение UNIQUE для TABLE_2 для (ID, NUMBER);вам нужно дополнительное ограничение, потому что ID остается первичным ключом для TABLE_2.
  2. Добавьте в TABLE_1 фиктивный столбец с именем TABLE_2_NUMBER.По умолчанию он равен нулю и имеет ограничение проверки, чтобы гарантировать, что он всегда равен нулю.(Если вы используете 11g, вам следует рассмотреть возможность использования виртуального столбца для этого.)
  3. Измените внешний ключ на TABLE_1, чтобы (FK_Table_2_ID, TABLE_2_NUMBER) ссылался на уникальное ограничение, а не на первичный ключ TABLE_2.
  4. Сбросьте триггер "New_Trigger";он вам больше не нужен, поскольку внешний ключ не позволит никому обновить TABLE_2.NUMBER до значения, отличного от нуля.

Но если правило действительно так, как я его сформулировал сверху, то есть

TABLE_1 может ссылаться только на TABLE_2, если TABLE_2.NUMBER не больше нуля (т.е. отрицательные значениявсе в порядке)

тогда вам нужен еще один триггер, на этот раз на TABLE_2, чтобы применить его на другой стороне правила.

CREATE or replace TRIGGER "Assertion_Trigger"
BEFORE UPDATE of "number" ON "Table_2" 
FOR EACH ROW
declare
  x pls_integer;
BEGIN

    if :new."number"  > 0
    then
        begin
            Select 1 
            into x
            from "Table_1"
            WHERE "Table_1"."FK_Table_2_ID" = :new.ID
            and rownum = 1;

           RAISE_APPLICATION_ERROR(-20001, :new.ID
                 ||' has dependent records in Table_1');
        exception
           when no_data_found then 
               null; -- this is what we want
        end;

END;

Этот триггер не позволит вам обновить TABLE_2.NUMBER до значения больше нуля, если на него ссылаются записи в TABLE_2.Он срабатывает только в том случае, если инструкция UPDATE касается TABLE_2.NUMBER, чтобы минимизировать влияние на производительность при выполнении поиска.

3 голосов
/ 27 апреля 2011

Не используйте триггер для создания ограничения уникальности или внешнего ключа.Oracle имеет декларативную поддержку уникальных и внешних ключей, например:

Добавить ограничение уникальности для столбца:

ALTER TABLE "Table_1" ADD (
  CONSTRAINT table_1_uk UNIQUE (column_name)
);

Добавить отношение внешнего ключа:

ALTER TABLE "ChildTable" ADD (
  CONSTRAINT my_fk FOREIGN KEY (parent_id)
    REFERENCES "ParentTable" (id)
);

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

Хорошее эмпирическое правило: если ваш триггер запрашивает ту же таблицу, что и триггер, возможно, это неправильно.

Я не уверен, но выпосле какого-то условного отношения внешнего ключа?то есть "разрешать только дочерние строки там, где родитель удовлетворяет условию x"?Если так, то проблема в модели данных и должна быть там исправлена.Если вы дадите больше объяснений того, чего вы пытаетесь достичь, мы сможем вам помочь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...