2011-04-26 29 views
1

嗨我无法正确地获取我的sql语法。我想创建一个唯一的约束来查看新添加的外键,查看新相关实体的一些属性,以决定是否允许关系。Oracle唯一约束 - 触发器来检查新关系中属性的值

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的答案是非常全面的,但是让我觉得我做错了。

情况是我有一张不同特权等级的表,我想检查这些特权等级,例如,用户'Bob'具有低级特权,并且他试图成为需要高权限的部门主管,以便系统防止这种情况发生。


有一个后续问题,它提出了一个相关的方案,但具有不同的数据模型。 Find it here

+1

不知道为什么你称这是一个独特的约束。 – APC 2011-04-27 05:45:57

+0

也许如果您可以根据人员和部门重新制定您的示例,而不是使用Table_1和Table_2,则您的要求会变得更加清晰。目前我无法弄清楚Table_1应该是一个新人,还是一个新部门,或者是一个新的person_department_assignment ... – 2011-04-27 09:09:40

回答

5

因此,您要强制执行的规则是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主键,值当你插入或更新表中的多行的。


你在这里要做的是强制一种称为ASSERTION的约束。断言是在ANSI标准中指定的,但是Oracle尚未实现它们。其他任何关系型数据库也没有涉及到。

断言是有问题的,因为它们是对称的。也就是说,该规则还需要在TABLE_2上实施。目前您在TABLE_1中创建记录时检查规则。假设稍后用户更新TABLE_2。NUMBER,因此它大于零:您的规则现在已被破坏,但您不会知道它被破坏,直到有人问题完全不相关 UPDATE TABLE_1,然后将失败。呸。

那么,该怎么办?

如果规则实际上是

TABLE_1只能引用TABLE_2如果 TABLE_2.NUMBER为零

那么你可以强制执行,而不触发。

  1. 在TABLE_2上为(ID,NUMBER)添加一个UNIQUE约束;您需要额外的约束,因为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引用您更新TABLE_2.NUMBER到大于零的值。只有在UPDATE语句触及TABLE_2.NUMBER以最小化执行查找的性能影响时才会触发。

3

请勿使用触发器来创建唯一约束或外键约束。甲骨文公司拥有独特的键和外键声明的支持,如:

添加唯一约束列:

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的子行”?如果是这样,问题出现在数据模型中,应该在那里修复。如果你提供了更多关于你想要达到的目标的解释,我们应该能够帮助你。

+0

我同意。对FK或独特的约束使用触发器不是一个好主意 – 2011-04-27 06:20:43