sql中的触发器:Mutating Trigger问题

时间:2014-10-26 05:58:55

标签: oracle triggers

我想在尝试表中插入行时将行插入另一个名为excercise_scores的表中。根据评分方法(可以是最新得分,所有尝试的平均值或所有尝试的最高得分),有多次尝试进行练习,我必须在excercise_scores表中插入得分。
因此,当将分数插入excercise_score时,我还必须检查练习的评分方法是什么,根据方法从尝试表中获取分数,然后最终插入到excercise_scores表中。

为此,我创建了一个SQL触发器,如下所示

CREATE OR REPLACE TRIGGER scores_trigger AFTER
 INSERT
  ON attempts FOR EACH row DECLARE P1 VARCHAR2(50);
 SCORES FLOAT;
 v_exists VARCHAR2(1) := 'F';
 BEGIN
  BEGIN
   SELECT
    'T'
     INTO
     v_exists
   FROM
     excercise_scores
   WHERE
    user_id        = :NEW.USER_ID
    AND EXCERCISE_ID = :NEW.EXCERCISE_ID
    AND COURSE_ID    = :NEW.COURSE_ID;
   EXCEPTION
    WHEN no_data_found THEN
    NULL;
  END;
 SELECT
  SELECTION_METHOD
 INTO
  P1
 FROM
  EXCERCISES
 WHERE
  EXCERCISE_ID = :NEW.EXCERCISE_ID
 AND COURSE_ID  = :NEW.COURSE_ID;
 IF (P1         = 'Latest') THEN
   SCORES      := :NEW.TOTAL_POINTS;
 ELSE
   IF (P1 = 'best') THEN
     SELECT
       MAX(TOTAL_POINTS)
     INTO
       SCORES
     FROM
      ATTEMPTS
     WHERE
      EXCERCISE_ID = :NEW.EXCERCISE_ID
     AND COURSE_ID  = :NEW.COURSE_ID
     AND USER_ID    = :NEW.USER_ID;         
   ELSE
     SELECT
       AVG(TOTAL_POINTS)
     INTO
       SCORES
     FROM
       ATTEMPTS
     WHERE
       EXCERCISE_ID = :NEW.EXCERCISE_ID
     AND COURSE_ID  = :NEW.COURSE_ID
     AND USER_ID    = :NEW.USER_ID;
   END IF;
 END IF;
 IF v_exists = 'T' THEN
   UPDATE
     EXCERCISE_SCORES
   SET
     TOTAL_POINTS = SCORES
   WHERE
     user_id        = :NEW.USER_ID
   AND EXCERCISE_ID = :NEW.EXCERCISE_ID
   AND COURSE_ID    = :NEW.COURSE_ID;
 ELSE
   INSERT
   INTO
     EXCERCISE_SCORES VALUES
     (
       :NEW.EXCERCISE_ID,
       :NEW.COURSE_ID,
       :NEW.COURSE_ID,
       SCORES
      ) ;
 END IF;
END;

但是当我尝试执行此触发器时,它会给我以下错误:

Error starting at line 47 in command:
insert into attempts values(21,0,'vshesha',3,'CSC540',TO_DATE('20141014', 'YYYYMMDD'),12,6)
Error report:
SQL Error: ORA-04091: table VSHESHA.ATTEMPTS is mutating, trigger/function may not see it
ORA-06512: at "VSHESHA.SCORES_TRIGGER", line 18
ORA-04088: error during execution of trigger 'VSHESHA.SCORES_TRIGGER'
04091. 00000 -  "table %s.%s is mutating, trigger/function may not see it"
*Cause:    A trigger (or a user defined plsql function that is referenced in
       this statement) attempted to look at (or modify) a table that was
       in the middle of being modified by the statement which fired it.
*Action:   Rewrite the trigger (or function) so it does not read that table.

我无法弄清楚为什么它会在插入后运行触发器时发出此错误。一般来说,在插入触发器之后不应出现变异表问题。

任何人都可以帮助我。我很久以来都很沮丧。

2 个答案:

答案 0 :(得分:2)

错误消息非常自我解释,导致它的SQL语句是:

     SELECT
       AVG(TOTAL_POINTS)
     INTO
       SCORES
     FROM
       ATTEMPTS
     WHERE
       EXCERCISE_ID = :NEW.EXCERCISE_ID
     AND COURSE_ID  = :NEW.COURSE_ID
     AND USER_ID    = :NEW.USER_ID;

您正在从ATTEMPTS中进行选择,这是当前正在更改的表格。没有简单的解决方案 - 最明智的方法是使用将您的业务逻辑从触发器移到应用程序代码中(参见AskTom about mutating table errors)。

顺便说一句:你的声明"一般来说,在插入触发器后不应该出现变异表问题。"是完全错的。有关详细说明,请参阅database journal article(简而言之:您将始终遇到多行插入的变异表错误,并且几乎每种情况都会遇到错误单行插入)。

答案 1 :(得分:0)

在Oralce中,您可以使用COMPOUND TRIGGER。在你的情况下,它将类似于这一个。我没有将你的所有操作复制到这个例子中,但你应该得到原则。

然而,正如其他回复已经提到的那样。首选方法是将所有这些逻辑移到PL / SQL过程中。

CREATE OR REPLACE TRIGGER scores_trigger
    FOR INSERT ON attempts
    COMPOUND TRIGGER

    TYPE RowTableType IS TABLE OF ROWID;
    RowTable RowTableType;
    aRow attempts%ROWTYPE;

    SCORES FLOAT;
    v_exists VARCHAR2(1) := 'F';

    BEFORE STATEMENT IS
    BEGIN
        RowTable := RowTableType();
    END BEFORE STATEMENT;

    AFTER EACH ROW IS
    BEGIN
        RowTable.EXTEND;
        RowTable(RowTable.LAST) := :NEW.ROWID;
    END AFTER EACH ROW;

    AFTER STATEMENT IS
    BEGIN

        FOR i IN RowTable.FIRST..RowTable.LAST LOOP
            SELECT * 
            INTO aRow
            FROM attempts
            WHERE ROWID = RowTable(i);

            SELECT SELECTION_METHOD
            INTO P1
            FROM EXCERCISES
            WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
                AND COURSE_ID  = aRow.COURSE_ID;

            IF P1 = 'Latest' THEN
                SCORES := :NEW.TOTAL_POINTS;
            ELSE
                IF (P1 = 'best') THEN
                    SELECT MAX(TOTAL_POINTS)
                    INTO SCORES
                    FROM ATTEMPTS
                    WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
                        AND COURSE_ID  = aRow.COURSE_ID
                        AND USER_ID    = aRow.USER_ID;         
                ELSE
                    SELECT AVG(TOTAL_POINTS)
                    INTO SCORES
                    FROM ATTEMPTS
                    WHERE EXCERCISE_ID = aRow.EXCERCISE_ID
                        AND COURSE_ID  = aRow.COURSE_ID
                        AND USER_ID    = aRow.USER_ID;
                END IF;
            END IF;

        END LOOP;

    END AFTER STATEMENT;
END scores_trigger;

PL / SQL过程的一个例子是:

CREATE OR REPLACE PROCEDURE insert_score(V_EXCERCISE IN SCORES.EXCERCISE_ID%TYPE, V_COURSE IN SCORES.COURSE_ID%TYPE, V_USER IN USER_ID%TYPE) IS

BEGIN
    INSERT INTO insert_score (EXCERCISE_ID, COURSE_ID, USER_ID) VALUES (V_EXCERCISE, V_COURSE, V_USER);

    BEGIN
    SELECT 'T'
    INTO v_exists
    FROM excercise_scores
    WHERE user_id        = :NEW.USER_ID
        AND EXCERCISE_ID = :NEW.EXCERCISE_ID
        AND COURSE_ID    = :NEW.COURSE_ID;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RETURN;
    END;

    ... other stuff

END insert_score;

对于插入,您必须调用此过程而不是直接执行插入。