触发与约束

时间:2015-04-14 08:50:43

标签: database oracle plsql triggers constraints

我应该使用触发器还是检查约束,我是否可以创建执行以下工作的检查约束:

如果某篇文章的类型为“Bert”,那么它只能在mytable中有一行,而在库存中只有1行。 如果它不是'Bert'类型,那么这两个条件都不适用。(可以在库存中有更多行和多于1行)

INSERT INTO mytable VALUES('Chip',1,'Bert'); -- Yes
INSERT INTO mytable VALUES('Screen',1,'Bert'); -- Yes
INSERT INTO mytable VALUES('Chip',1,'Bert'); -- No
INSERT INTO mytable VALUES('Cable',2,'Bert'); -- No
INSERT INTO mytable VALUES('Keyboard',2,'Non-bert'); -- Yes
INSERT INTO mytable VALUES('Keyboard',2,'Non-bert'); -- Yes
INSERT INTO mytable VALUES('Keyboard',3,'Non-bert'); -- Yes

这样的触发器导致问题,因为我试图访问触发器内的基表:

CREATE OR REPLACE TRIGGER trg_mytable
BEFORE INSERT OR UPDATE ON mytable 
FOR EACH ROW 
WHEN (NEW.TYPEB = 'Bert')
DECLARE
    L_COUNT NUMBER;
BEGIN
    SELECT  COUNT(*) INTO L_COUNT
    FROM    MYTABLE 
    WHERE   ARTICLE = :NEW.ARTICLE
        AND TYPEB = :NEW.TYPEB;

    IF L_COUNT > 0  THEN
        RAISE_APPLICATION_ERROR( -20001, 'Bert already exists!' );
    ELSIF :NEW.STOCK_COUNT > 1 THEN
        RAISE_APPLICATION_ERROR( -20001, 'Can''t insert more than one with type Bert!' );
    END IF;
END;

3 个答案:

答案 0 :(得分:3)

为确保表中只有一个Bert,您可以在此虚拟列上使用虚拟列和唯一约束的组合。这利用了UNIQUE约束允许多个NULL值的事实:

create table mytable(category varchar2(30), stock number, name varchar2(30));
-- virtual column for checking uniqueness
alter table mytable add is_bert generated always as (
  case name when 'Bert' then 1 else null end
);
alter table mytable add constraint uq_bert unique(is_bert);

为确保Bert始终具有等于1的股票,您可以使用带有简单布尔检查的CHECK约束(这假设名称为非NULL):

-- check constraint for ensuring stock for bert equals 1
alter table mytable add constraint chk_bert_has_stock_one check(
  name != 'Bert' or stock = 1);

关于触发器与约束的选择,有一个简单的规则:

从不使用触发器来强制执行业务逻辑。它们很难调试,容易出错,并且在多用户环境中无法正常工作,请参阅

AskTom on Triggers

<强>更新

上述解决方案不符合更新的要求,因为(正如@LalitKumarB正确指出的那样)它错误地拒绝Bert的多行。对于这个更复杂的场景,我将使用带有REFRESH ON COMMIT的物化视图(这假设mytable有一个主键)和两个以不同方式处理Bert和Non-Bert行的约束:

create materialized view log on mytable;

create materialized view mv_mytable 
refresh on commit as 
  select 
     (case when type = 'Bert' then name 
      else null 
      end) as name,
     (case when type = 'Bert' and stock_count != 1 then null 
      else stock_count 
      end) as stock_count
  from mytable;

alter table mv_mytable add constraint uq_bert unique(name);
alter table mv_mytable add constraint chk_bert_stock_count 
  check(stock_count is not null);        

这种解决方案的缺点是它不会立即拒绝假行;在尝试COMMIT(类似于延迟约束)之前,你不会注意到任何错误。

答案 1 :(得分:-1)

如果typeb(=&#39; Bert&#39;?)应该是唯一的,只需创建一个唯一索引:create unique index myIndex on mytable ( typeb),您在尝试插入时会收到重复的输入错误

答案 2 :(得分:-1)

我们可以使用这样的设置。请修改Frank Schmitt的is_bert专栏,以检查Bert购买的独特类别。

create table mytable(category varchar2(30) , stock number, name varchar2(30));

alter table mytable add is_bert generated always as (
  case  when name='Bert'  and stock=1 then category
            else null end
);
alter table mytable add constraint uq_bert unique(is_bert);
alter table mytable add constraint chk_bert_has_stock_one check(
  name != 'Bert' or stock = 1);


INSERT INTO mytable(category,stock,name) VALUES('Chip',1,'Bert'); -- Yes
INSERT INTO mytable(category,stock,name) VALUES('Screen',1,'Bert'); -- Yes
INSERT INTO mytable(category,stock,name) VALUES('Chip',1,'Bert'); -- No
INSERT INTO mytable(category,stock,name) VALUES('Cable',2,'Bert'); -- No
INSERT INTO mytable(category,stock,name) VALUES('Keyboard',2,'Non-bert'); -- Yes
INSERT INTO mytable(category,stock,name) VALUES('Keyboard',2,'Non-bert'); -- Yes
INSERT INTO mytable(category,stock,name) VALUES('Keyboard',3,'Non-bert'); -- Yes

我猜也是基于函数的索引可以实现它。

create table mytable(category varchar2(30) , stock number, name varchar2(30));
alter table mytable add constraint chk_bert_has_stock_one check(
  name != 'Bert' or stock = 1);

  create unique index myfun_index on mytable (case  when name='Bert'  and stock=1 then category
            else null end);