【问题标题】:Oracle - Problem creating trigger that updates another tableOracle - 创建更新另一个表的触发器时出现问题
【发布时间】:2011-11-22 09:15:51
【问题描述】:

我已经阅读了有关创建触发器的 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
          <--- 

当然,Oracle 并不足以真正告诉我警告是什么,我只是被证明有警告。

接下来,如果我更新训练计划表:

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 

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

【问题讨论】:

  • 要显示警告消息,请输入show errors。此外,您在 BEGIN 和 END 之间的语句中似乎缺少 :new 上的绑定冒号。

标签: sql database oracle oracle11g


【解决方案1】:

正如 Justin Cave 所建议的,您可以在需要时计算最短开始日期。如果您在 (projectid, startdate) 上创建索引可能会有所帮助;

如果您确实有很多项目和培训计划,另一种解决方案可能是创建一个包含您需要的所有数据的 MATERIALIZED VIEW:

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,以上代码仅供参考)

【讨论】:

  • 不幸的是,Oracle 不支持带有嵌套子查询的物化视图。否则,我肯定会这样做..
  • 将您的简单物化视图与您的其余查询相结合,无需将所有内容都放入视图中,它仍然会很快。
  • 您在上面创建的实体化视图需要手动更新,并且不支持 FAST REFRESH。这些对我来说是破坏交易的。我需要实时数据。
【解决方案2】:

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

首先,在行级触发器的主体中,需要使用: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 的逻辑放在该存储过程中而不是将其放在触发器中更有意义。尝试调试在触发器中嵌入了大量逻辑的应用程序是出了名的痛苦,因为这使得开发人员很难准确地跟踪正在执行的操作。或者,您可以从 TPM_PROJECT 表中删除 TRAININGDELIVERYSTART 列,然后只计算运行时的最短开始日期。

第四,如果您的触发器在插入、更新和删除时触发,您不能简单地引用 :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;

【讨论】:

  • 听起来我想做的事情根本不可行。现在,我们每次都计算这个值,而且非常非常慢。我正在尝试寻找提高这些查询性能的方法。在中间层更新它是可能的,但这不是一项小任务。但是,即使修复了所有语法错误,我仍然无法创建触发器。出于某种原因,SQLPlus 对我不起作用 - 它只是需要越来越多的输入,比如某些东西没有被关闭或结束,尽管我不知道它是什么。
  • 尝试在最后一行输入“/”。
  • 我今天去了,但明天我会试一试。我认为触发的事情对于贾斯汀提到的点是不可行的,尤其是变异表错误,即使我这样做了让一切正常工作。听起来最好的方法是从中间层更新值。所有的 SQL 代码都是在 .NET Entity Framework 上编写的,现在是一团糟,但希望我能找到一些“钩子”来在每次表更改时更新此列..
  • @Mike - 你是如何每次计算价值的?查询计划是什么?如果TPM_TRAININGPLAN 表中的 (PROJECTID, STARTDATE) 上有索引,则它不应该是“非常非常慢”,除非您在 @987654345 中有很多具有相同 PROJECTID 的行@表。
  • 这非常非常慢,因为我们从 PROJECTVERSIONS 中选择了数千行,而这些行中的每一行都有数十个 TRAININGPLAN 行与之相关联。因此,对于这些行中的每一行,它必须计算最低日期(顺序扫描是唯一的方法)。我想通过直接将最低日期存储在 PROJECT 表中来规范化这些数据,并在 TRAININGPLAN 行更改时对其进行更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-02
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 2022-01-10
  • 2020-08-23
  • 2019-05-03
相关资源
最近更新 更多