在Oracle Trigger的5条记录之后引发异常

时间:2014-07-21 20:30:57

标签: oracle triggers

我遇到问题oracle触发器有问题,希望你们能在这里帮助我。

客户只能购买当前月份的5件物品,因此任何高于5的触发器都会引发异常。

这是我迄今为止所做的。

CREATE OR REPLACE TRIGGER five_reading
BEFORE INSERT OR UPDATE ON PURCHASE
FOR EACH ROW
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
    totover NUMBER(3);
BEGIN
    SELECT COUNT (custID)
    INTO   totover
    FROM   PURCHASE
    WHERE :NEW.custID = PURCHASE.custID
  AND EXTRACT(MONTH FROM SYSDATE)= EXTRACT(MONTH FROM :NEW.datevisited)
  AND EXTRACT(YEAR FROM SYSDATE)= EXTRACT(YEAR FROM :NEW.datevisited);  
    IF totover = 5  THEN
       RAISE_APPLICATION_ERROR ( -20001, 
        'Customer' || :NEW.custID ||
        ' already has 5 purchases' );
    END IF;
END;
/

但是,当客户少于5次购买时,我仍然无法插入新记录。

2 个答案:

答案 0 :(得分:0)

我很困惑 - 在你说的问题中,客户“...只能购买当前月份的5件物品,因此任何高于5的触发器都会抛出异常”,但触发器只会抛出异常客户已经完成了五次购买。

尝试以下方法:

CREATE OR REPLACE TRIGGER five_reading
BEFORE INSERT OR UPDATE ON PURCHASE
FOR EACH ROW
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
  totover NUMBER;
BEGIN
  SELECT COUNT(*)
  INTO   totover
  FROM   PURCHASE p
  WHERE p.custid = :NEW.custid AND
        TRUNC(p.datevisited, 'MONTH') = TRUNC(SYSDATE, 'MONTH');
  IF totover > 5  THEN
    COMMIT;  -- autonomous transaction

    RAISE_APPLICATION_ERROR ( -20001, 
                             'Customer' || :NEW.custID ||
                             ' already has ' || totover || ' purchases' );
  END IF;

  COMMIT;  -- autonomous transaction
END;

此外,您没有说明在引发上述异常后会发生什么。捕获此异常时,您应该回滚外部事务。

分享并享受。

答案 1 :(得分:0)

正如我假设您已发现,您无法从同一个表中选择定义行级触发器;它导致表变异异常。您试图通过添加自治事务编译指示来解决此问题。不幸的是,虽然这有效,但它只是掩盖了你在方法论上的错误。

为了使用触发器正确创建此验证,应创建一个过程以获取用户指定的锁,以便在多用户环境中正确地序列化验证。

PROCEDURE request_lock
  (p_lockname                     IN     VARCHAR2
  ,p_lockmode                     IN     INTEGER  DEFAULT dbms_lock.x_mode
  ,p_timeout                      IN     INTEGER  DEFAULT 60
  ,p_release_on_commit            IN     BOOLEAN  DEFAULT TRUE
  ,p_expiration_secs              IN     INTEGER  DEFAULT 600)
IS
  -- dbms_lock.allocate_unique issues implicit commit, so place in its own
  -- transaction so it does not affect the caller
  PRAGMA AUTONOMOUS_TRANSACTION;
  l_lockhandle                   VARCHAR2(128);
  l_return                       NUMBER;
BEGIN
  dbms_lock.allocate_unique
    (lockname                       => p_lockname
    ,lockhandle                     => p_lockhandle
    ,expiration_secs                => p_expiration_secs);
  l_return := dbms_lock.request
    (lockhandle                     => l_lockhandle
    ,lockmode                       => p_lockmode
    ,timeout                        => p_timeout
    ,release_on_commit              => p_release_on_commit);
  IF    (l_return = 1) THEN
    raise_application_error(-20001, 'dbms_lock.request Timeout');
  ELSIF (l_return = 2) THEN
    raise_application_error(-20001, 'dbms_lock.request Deadlock');
  ELSIF (l_return = 3) THEN
    raise_application_error(-20001, 'dbms_lock.request Parameter Error');
  ELSIF (l_return = 5) THEN
    raise_application_error(-20001, 'dbms_lock.request Illegal Lock Handle');
  ELSIF (l_return not in (0,4)) THEN
    raise_application_error(-20001, 'dbms_lock.request Unknown Return Value ' || l_return);
  END IF;
  -- Must COMMIT an autonomous transaction
  COMMIT;
END request_lock;

然后可以在复合触发器中使用此过程(假设至少Oracle 11,在早期版本中需要将其拆分为单独的触发器)

CREATE OR REPLACE TRIGGER too_many_purchases
  FOR INSERT OR UPDATE ON purchase
  COMPOUND TRIGGER

  -- Table to hold identifiers of inserted/updated customer purchases
  g_custIDs sys.odcinumberlist;

BEFORE STATEMENT 
IS
BEGIN
-- Reset the internal customer table
  g_custIDs := g_custIDs();
END BEFORE STATEMENT; 

AFTER EACH ROW
IS
BEGIN
  -- Store the inserted/updated customers
  IF (  INSERTING
     OR (   UPDATING
        AND (  :new.custID <> :old.custID
            OR (   trunc(:new.dateVisited) = trunc(sysdate, 'MM')
               AND trunc(:old.dateVisited) <> trunc(sysdate, 'MM')))))
  THEN           
    g_custIDs.EXTEND;
    g_custIDs(g_custIDs.LAST) := :new.custID;
  END IF;
END AFTER EACH ROW;

AFTER STATEMENT
IS
  CURSOR csr_customers
  IS
    SELECT DISTINCT
           cst.column_value custID
    FROM TABLE(g_custIDs) cid
    ORDER BY cst.column_value;
  CURSOR csr_constraint_violations
    (p_custID   purchase.custID%TYPE)
  IS
    SELECT count(*) purchases
    FROM purchase pch
    WHERE pch.custID = p_custID
    AND trunc(pch.dateVisited, 'MM') = trunc(sysdate, 'MM')
    HAVING count(*) > 5;
  r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
  -- Check if for any inserted/updated customer purchase there exists more than
  -- 5 purchases for the same customer for the current month. Serialise the
  -- constraint for each custID so concurrent transactions do not affect each
  -- other
  FOR r_customer IN csr_customers LOOP
    request_lock('TOO_MANY_PURCHASES_' || r_customer.custID);
    OPEN csr_constraint_violations(r_customer.custID);
    FETCH csr_constraint_violations INTO r_constraint_violation;
    IF csr_constraint_violations%FOUND THEN
      CLOSE csr_constraint_violations;
      raise_application_error(-20001, 'Customer ' || r_customer.custID || ' now has ' || r_constraint_violation.purchases || ' purchases this month');
    ELSE
      CLOSE csr_constraint_violations;
    END IF;
  END LOOP;
END AFTER STATEMENT;

END;