3

我现在正在为 Oracle 中的 DELETE 触发器进行长期斗争,该触发器在删除一行后,会从剩余的行中选择一个新的 MAX 值并将其写入另一个表。在偶然发现烦人的 ORA-04091 变异表错误(在 FOR EACH ROW 时无法读取表)后,我切换到 Oracle 的复合触发器。

我怎样才能最好地存储已删除的行(每行有多个值,因为只有在删除的分数可能是高分而不是低分时,进一步检查才会更新)?我担心如果多个触发事件交叉触发并且例如为“DeletedMatches”运行高分更新,该全局临时表可能会以一团糟而告终,这些“DeletedMatches”实际上并没有被删除,而是由触发事件之前注册。

我可以创建一个表,它 a) 仅在此触发器中本地存在 b) 可以像普通数据库表或临时表一样在 SQL 中使用?

每当匹配被删除时,以下(伪)代码将更新 CurrentHighScores 表(旧的高分消失并由最高的剩余分数代替)。

CREATE TABLE GameScores (
    MatchId number not null --primary key
    Player  varchar(255) not null,
    Game    varchar(255) not null, -- PacMan, Pong, whatever...
    Score   number not null );

-- High score for each game:
CREATE TABLE CurrentHighScores (
    HiScId number not null --primary key
    Player  varchar(255) not null,
    Game    varchar(255) not null,
    HighScore   number not null );

create or replace TRIGGER UpdHiScoreOnMatchDelete
FOR DELETE ON GameScores 
COMPOUND TRIGGER
    TYPE matchtable IS TABLE OF GameScores%ROWTYPE INDEX BY SIMPLE_INTEGER;
    DeletedMatches matchtable;
    MatchIndex SIMPLE_INTEGER := 0;

BEFORE EACH ROW IS -- collect deleted match scores
BEGIN
  MatchIndex:= MatchIndex+ 1;
  DeletedMatches(MatchIndex).Game := :old.Game;
  DeletedMatches(MatchIndex).Score := :old.Score;
  -- don't want to set every column value, want to 
      -- do like: INSERT :old INTO DeletedMatches;
      -- don't want the Index either!
END BEFORE EACH ROW;

AFTER STATEMENT IS
BEGIN
    UPDATE CurrentHighScores hsc 
    SET hsc.HighScore=(
      select max(gsc.Score) from GameScores gsc
      where hsc.Game=gsc.Game)
    where hsc.Game IN (
      select del.Game from DeletedMatches del where hsc.HighScore = del.Score)
      -- won't work, how can I check within the SQL if a row 
              -- for this game has been deleted, or anyhow integrate 
              -- DeletedMatches into the SQL, without a cursor?
              -- Optional further cond. in subselect, to update only 
              -- if deleted score equals highscore: 
    and exists(
      select 1 from GameScores where Game=hsc.Game); 
      -- ignore games without remaining match scores.

    -- Delete/set zero code for games without existing scores omitted here.
END AFTER STATEMENT;
4

2 回答 2

5

“烦人”的变异表错误几乎总是表明设计不佳,通常是非规范化的数据模型。这似乎适用于这种情况。如果您需要维护汇总值、计数、最大值等,为什么不使用 Oracle 的内置功能?Oracle 为我们提供了专门用于处理摘要的 MATERIALIZED VIEW 对象。 了解更多

在您的情况下,将 CurrentHighScores 替换为物化视图。

CREATE MATERIALIZED VIEW CurrentHighScores 
BUILD IMMEDIATE
REFRESH FAST
as select 
( 
    Player , 
    Game    , 
    max(score) as HighScore  
from GameScores 
group by player, game ; 

您还需要在 GameScores 上构建一个 MATERIALIZED VIEW LOG。

于 2012-06-29T10:01:23.187 回答
1

对于这种情况,全局临时表完成了这项工作。收集 BEFORE EACH ROW 游标中的所有 :old 行,然后在 AFTER STATEMENT 中,将临时表与删除表连接起来,并找到已删除项目的新 MAX 值。

我担心全局 temp 中的触发条目会与其他触发事件中的条目混淆,就像 MSSQL #tempTable 一样,这是错误的,ON COMMIT DELETE ROWS 工作正常。

可惜这么简单的触发任务,要在 MSSQL 中在几个小时内完成(包括测试),花了太多时间,阅读所有 Oracle 后台。在 Oracle SQL Developer 中,人们可以很容易地在一些奇怪的、神秘的错误消息(至少以可见的行作为参考)之后查看几个小时,却发现原因是缺少行结束分号或未正确关闭堵塞。

物化视图可能是未来的选择,我的问题是:matview 数据是事务性的(如触发器所做的更改),还是延迟报告功能?我知道它可以配置为定期更新,但也可以至少几乎立即进行“快速”更新。但是,这有更多烦人的限制(例如,如果要搜索 MAX 值,则没有 Where 子句)。

于 2012-07-05T19:47:45.883 回答