Oracle - 问题创建更新另一个表的触发器

时间:2011-09-21 22:46:29

标签: sql database oracle oracle11g

我已经阅读了有关创建触发器的Oracle文档,并且正在完成它所显示的内容,但这只是不起作用。我的目标是使用TPM_TRAININGPLAN表中出现的最小STARTDATE更新TPM_PROJECT表。因此,每当有人更新TPM_TRAININGPLAN中的STARTDATE列时,我想更新TPM_PROJECT表。这是我正在尝试的:

CREATE TRIGGER Trigger_UpdateTrainingDelivery
    AFTER DELETE OR INSERT OR UPDATE OF STARTDATE
    ON TPM_TRAININGPLAN
    FOR EACH ROW WHEN (new.TRAININGPLANTYPE='prescribed')
    BEGIN
       UPDATE TPM_PROJECT SET TRAININGDELIVERYSTART = (SELECT MIN(TP.STARTDATE) FROM TPM_TRAININGPLAN TP WHERE TP.PROJECTID = new.PROJECTID AND TP.TRAININGPLANTYPE='prescribed')
       WHERE PROJECTID = new.PROJECTID
    END;

创建的触发器没有错误,但我收到警告:

 Warnings: ---> 
   W (1): Warning: execution completed with warning
          <--- 

当然甲骨文还不够好,实际上告诉我警告是什么,我只是证明有一个警告。

接下来,如果我用以下内容更新培训计划表:

UPDATE TPM_TRAININGPLAN
set STARTDATE = to_date('03/12/2009','mm/dd/yyyy')
where TRAININGPLANID=15916;

我收到错误消息:

>[Error] Script lines: 20-22 ------------------------
 ORA-04098: trigger 'TPMDBO.TRIGGER_UPDATETRAININGDELIVERY' is invalid and failed re-validation
 Script line 20, statement line 1, column 7 

任何想法我做错了什么?谢谢!

2 个答案:

答案 0 :(得分:4)

一些问题没有特别的顺序。

首先,在行级触发器的主体中,您需要使用:new:old来引用新旧记录。前导结肠是必要的。所以你的WHERE条款需要

WHERE PROJECTID = :new.PROJECTID

其次,如果您在SQL * Plus中运行CREATE TRIGGER,则可以使用SHOW ERRORS命令获取错误和警告的列表,即

SQL> show errors

您还可以查询DBA_ERRORS表(或ALL_ERRORSUSER_ERRORS,具体取决于您的权限级别),但这通常不需要诉诸。

第三,假设语法错误得到纠正,如果你使用这个逻辑,你将获得mutating table error。表A上的行级触发器(在这种情况下为TPM_TRAININGPLAN)无法查询表A,因为该表可能处于不一致状态。正如Tim在他的文章中所说,你可以通过创建一个包含集合的包,在before语句触发器中初始化该集合,在行级触发器中填充集合中的数据,然后处理修改后的行来解决这个问题。一个after语句触发器。然而,添加到系统中的复杂性相当大,因为您必须管理多个不同的对象。

通常,您最好将此逻辑作为用于操作TPM_TRAININGPLAN表的API的一部分来实现。如果这是一个存储过程,那么将逻辑放在该存储过程中更新TPM_PROJECT而不是将其置于触发器中会更有意义。尝试调试触发器中嵌入了大量逻辑的应用程序是非常痛苦的,因为这使得开发人员很难完全遵循正在执行的操作。或者,您可以从TRAININGDELIVERYSTART表中删除TPM_PROJECT列,并在运行时计算最小开始日期。

第四,如果触发器在插入,更新和删除时触发,则不能简单地引用:new值。 :new对插入和更新有效,但如果您正在删除它将为NULL。 :old对删除和更新有效,但如果您正在进行插入,则将为NULL。这意味着您可能需要具有逻辑(参考Tim的包解决方案)

BEGIN
  IF inserting 
  THEN
    trigger_api.tab1_row_change(p_id => :new.projectid, p_action => 'INSERT');
  ELSIF updating
  THEN
    trigger_api.tab1_row_change(p_id => :new.projectid, p_action => 'UPDATE');
  ELSIF deleting
  THEN
    trigger_api.tab1_row_change(p_id => :old.projectid, p_action => 'DELETE');
  END IF;
END;

答案 1 :(得分:2)

正如Justin Cave所建议的那样,您可以根据需要计算最短开始日期。如果您在(projectid,startdate);

上创建索引可能会有所帮助

如果你真的有很多项目和培训计划,另一个解决方案可能是创建一个包含所需数据的材料化视图:

CREATE MATERIALIZED VIEW my_view
... add refresh options here ...
AS
SELECT t.projectid,  MIN(t.start_date) AS min_start_date
FROM TPM_TRAININGPLAN t
GROUP BY t.projectid;

(抱歉,没有Oracle运行,上面的代码仅供参考)