语句级触发器以强制执行约束

时间:2019-06-01 15:27:12

标签: oracle plsql database-trigger

我正在尝试执行一个语句级触发器来强制执行以下“申请人一天内不能申请两个以上职位”的问题。

我能够使用行级触发器(如下所示)来执行它,但是当我不能使用: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;

2 个答案:

答案 0 :(得分:1)

您的规则同时涉及多个行。因此,您不能使用FOR ROW LEVEL触发器:按照您的建议在APPLIES上进行查询会抛出ORA-04091:表正在更改异常。

因此,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 :(得分: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

好运。