表是变异的,触发/功能可能看不到它ORA-06512

时间:2015-11-22 01:12:51

标签: oracle if-statement plsql triggers sql-update

我有两个名为DetailRental和Video的表。 VID_NUM是Video的PK和DetailRental的FK。

此代码想要实现的是当DetailRental表中的Detail_Returndate或Detail_Duedate更改(更新或插入新行)时,触发器将逐行检查Detail_Returndate的值。如果其值为null,则视频表中的相应(根据VID_NUM)属性VID_STATUS将更改为“OUT”。

已成功创建触发器。但是,当我想更新日期时。 Oracle给了我错误:

ORA-04091: table SYSTEM2.DETAILRENTAL is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM2.TRG_VIDEORENTAL_UP", line 3
ORA-04088: error during execution of trigger 'SYSTEM2.TRG_VIDEORENTAL_UP'

1. UPDATE DETAILRENTAL
2. SET DETAIL_RETURNDATE = null
3. WHERE RENT_NUM = 1006 AND VID_NUM = 61367 

以下是我的代码:

CREATE OR REPLACE TRIGGER trg_videorental_up
  AFTER INSERT OR UPDATE OF DETAIL_RETURNDATE, DETAIL_DUEDATE ON DETAILRENTAL
  FOR EACH ROW
AS
  DECLARE
    DTRD DATE;
  BEGIN
    SELECT DETAIL_RETURNDATE
      INTO DTRD
      FROM DETAILRENTAL;
    IF DTRD IS NULL
      THEN UPDATE VIDEO
        SET VIDEO.VID_STATUS = 'OUT'
        WHERE EXISTS
          (SELECT DETAILRENTAL.VID_NUM
            FROM DETAILRENTAL
            WHERE DETAILRENTAL.VID_NUM = VIDEO.VID_NUM
          );
    END IF;
END;

非常感谢!

这里解决的问题是代码:

CREATE OR REPLACE TRIGGER trg_videorental_up
AFTER INSERT OR UPDATE OF DETAIL_RETURNDATE, DETAIL_DUEDATE ON DETAILRENTAL
FOR EACH ROW
DECLARE DETAIL_RETURNDATE DATE;
BEGIN
IF :NEW.DETAIL_RETURNDATE IS NULL THEN UPDATE VIDEO SET VID_STATUS = 'OUT' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE > SYSDATE THEN UPDATE VIDEO SET VID_STATUS = 'OUT' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE <= SYSDATE AND TO_CHAR(DETAIL_RETURNDATE)!= '01/01/0001' THEN UPDATE VIDEO SET VID_STATUS = 'IN' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE = '01/01/0001' THEN UPDATE VIDEO SET VID_STATUS = 'LOST' WHERE VID_NUM = :NEW.VID_NUM;
END IF;
END;

2 个答案:

答案 0 :(得分:2)

良好的数据模型是没有物理存储冗余信息的模型。如果你可以查看table.column中的一个(或多个)值并找出另一个table.column中的值,那么你就有了冗余。在您的情况下,一个人可以看到一个DETAILRENTAL.DETAIL_DUEDATE为VIDNUM 61367不为空,并且&#34;知道&#34; VIDEO.STATUS字段应为OUT。

最容易解决的问题如下:
1)创建一个VIDEO_BASE表,其中包含除VID_STATUS之外的所有VIDEO列:

CREATE TABLE VIDEO_BASE AS 
SELECT {list all columns except STATUS}
FROM VIDEO;  

2)删除原始VIDEO表并创建视图VIDEO,其中显示VIDEO_BASE的所有列,并将STATUS公开为派生字段:

CREATE OR REPLACE VIEW VIDEO 
AS 
SELECT  V.*,
        CASE WHEN 
            (
            SELECT COUNT(*) 
            FROM
                (
                SELECT 'X'
                FROM DETAILRENTAL D
                WHERE D.VID_NUM = V.VID_NUM 
                      AND DETAIL_RETURNDATE IS NOT NULL
                      AND ROWNUM <= 1
                )
            ) > 0 
        THEN 'OUT' 
        ELSE NULL
        END VID_STATUS
FROM VIDEO_BASE V;

通常,如果您觉得需要触发器来保持两个不同的表同步,那么您就会遇到数据模型问题。在我15年的Oracle经验中,修复有问题触发器的唯一最佳方法是修复数据模型 - 知道所有触发器正常工作的最可靠方法是数据库中的触发器数量为0时。

答案 1 :(得分:0)

在阅读@KirkKirkpatrick的答案两三次之后,我意识到他是对的 - 个人视频的进/出状态 可以从数据库中的其他信息中导出。也就是说,你可能有这样做的实际理由。

坏消息是你不能从同一个表中的行触发器中选择表 - 这就是“变异表”问题的含义。好消息是,在这种情况下,你真的不需要。

我没有Oracle安装我可以测试它,所以我不保证语法正确性,但它应该足够接近让你开始。

CREATE OR REPLACE TRIGGER trg_videorental_up
  AFTER INSERT OR UPDATE
    OF detail_duedate, detail_returndate
  ON detailrental
  FOR EACH ROW
AS
BEGIN
  IF :new.detail_returndate IS NULL
    AND :new.detail_duedate IS NOT NULL
  THEN
    UPDATE video
      SET status = 'OUT'
      WHERE video_num = :new.video_num;
  END IF;
END;
相关问题