执行约束的语句级触发器

Statement level trigger to enforce a constraint

我正在尝试实施语句级触发器来强制执行以下 "An applicant cannot apply for more than two positions in one day"。

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

您的规则同时涉及多行。所以你不能使用 FOR ROW LEVEL 触发器:按照你的建议查询 APPLIES 会抛出 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;

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

你是正确的,你没有 :OLD 和 :NEW 值 - 所以你需要检查整个 table 看看条件是否(我们不要称之为 "constraint" ,因为该术语在关系数据库的意义上具有特定含义)已被违反:

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

祝你好运。