【问题标题】:Statement level trigger to enforce a constraint执行约束的语句级触发器
【发布时间】:2019-06-01 15:27:12
【问题描述】:

我正在尝试实现语句级触发器以强制执行以下“申请人一天内不能申请超过两个职位”。

我可以使用行级触发器(如下所示)来执行它,但是当我不能使用:NEW 或:OLD 时,我不知道如何使用语句级触发器来执行此操作。

我知道使用触发器有其他替代方法,但我正在修改我的考试,可能会有类似的问题,所以我希望能提供任何帮助。

CREATE TABLE APPLIES(
anumber     NUMBER(6)   NOT NULL,  /* applicant number */
pnumber     NUMBER(8)   NOT NULL, /* position number */
appDate     DATE        NOT NULL, /* application date*/
CONSTRAINT APPLIES_pkey PRIMARY KEY(anumber, pnumber)
);

CREATE OR REPLACE TRIGGER app_trigger
BEFORE INSERT ON APPLIES
FOR EACH ROW
DECLARE 
  counter NUMBER;
BEGIN
  SELECT COUNT(*) INTO counter 
  FROM APPLIES 
  WHERE anumber = :NEW.anumber
  AND to_char(appDate, 'DD-MON-YYYY') = to_char(:NEW.appDate, 'DD-MON-YYYY');

  IF counter = 2 THEN
      RAISE_APPLICATION_ERROR(-20001, 'error msg');
  END IF;
END;

【问题讨论】:

    标签: oracle plsql database-trigger


    【解决方案1】:

    您是正确的,您没有 :OLD 和 :NEW 值 - 所以您需要检查整个表格以查看条件(我们不要称其为“约束”,因为该术语在关系数据库的意义)已被违反:

    CREATE OR REPLACE TRIGGER APPLIES_AIU
      AFTER INSERT OR UPDATE ON APPLIES
    BEGIN
      FOR aRow IN (SELECT ANUMBER,
                          TRUNC(APPDATE) AS APPDATE,
                          COUNT(*) AS APPLICATION_COUNT
                     FROM APPLIES
                     GROUP BY ANUMBER, TRUNC(APPDATE)
                     HAVING COUNT(*) > 2)
      LOOP
        -- If we get to here it means we have at least one user who has applied
        -- for more than two jobs in a single day.
    
        RAISE_APPLICATION_ERROR(-20002, 'Applicant ' || aRow.ANUMBER ||
                                        ' applied for ' || aRow.APPLICATION_COUNT ||
                                        ' jobs on ' ||
                                        TO_CHAR(aRow.APPDATE, 'DD-MON-YYYY'));
      END LOOP;
    END APPLIES_AIU;
    

    添加一个索引来支持这个查询是个好主意,这样它就可以有效地运行:

    CREATE INDEX APPLIES_BIU_INDEX
      ON APPLIES(ANUMBER, TRUNC(APPDATE));
    

    dbfiddle here

    祝你好运。

    【讨论】:

    • 很好的例子和解释!也谢谢你的祝福!
    【解决方案2】:

    您的规则同时涉及多行。因此,您不能使用 FOR ROW LEVEL 触发器:按照您的建议查询应用程序会抛出 ORA-04091: table is mutating exception。

    所以,它是 AFTER 语句。

    CREATE OR REPLACE TRIGGER app_trigger
    AFTER INSERT OR UPDATE ON APPLIES
    DECLARE 
      cursor c_cnt is
        SELECT 1 INTO counter 
        FROM APPLIES 
        group by anumber, trunc(appDate) having count(*) > 2;
      dummy number;
    BEGIN
      open c_cnt;
      fetch c_cnt in dummy;
      if c_cnt%found then 
          close c_cnt;
          RAISE_APPLICATION_ERROR(-20001, 'error msg');
      end if;
      close c_cnt;
    END;
    

    显然,大规模查询整个表的效率会很低。 (不建议将触发器用于此类事情的原因之一)。所以在这种情况下,我们可能想要使用a compound trigger(假设我们使用的是 11g 或更高版本)。

    【讨论】:

    • 这里有一件关键的事情......它可能不起作用。因为两个会话都可以插入相同的数据,并且每个会话在提交之前只会看到计数为 1。瞧……现在您的数据已损坏。您需要锁定所有可能在此处受到影响的行……这是可扩展性的杀手。看看这里asktom.oracle.com/pls/apex/…
    • @ConnorMcDonald - 不幸的是,我们在这里看到的几乎所有基于触发器的家庭作业都落入了这个陷阱。
    • 阿门 :-)
    猜你喜欢
    • 1970-01-01
    • 2020-03-10
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 1970-01-01
    • 2017-01-11
    • 2019-07-24
    • 2020-02-12
    相关资源
    最近更新 更多