3

私は、行を削除すると、残りの行から新しい MAX 値を選択し、それを別のテーブルに書き込む Oracle の DELETE トリガーを求めて、長い間闘っています。面倒な ORA-04091 mutating table エラー (FOR EACH ROW ではテーブルを読み取れない) に出くわした後、Oracle の複合トリガーに切り替えました。

削除された行を最適に保存するにはどうすればよいですか (行ごとに複数の値。それ以降のチェックでは、削除されたスコアが低いスコアではなく、高いスコアであった可能性がある場合にのみ更新されるため)。複数のトリガーイベントがクロスファイアし、たとえば、実際には削除されていないが、Before トリガーイベントによって登録された「DeletedMatches」に対してハイスコアの更新が実行されると、グローバル一時テーブルが混乱して終了する可能性があるのではないかと心配しています。

a) このトリガーでローカルにのみ存在する b) 通常の DB テーブルや一時テーブルのように 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 の組み込み機能を使用してみませんか? オラクルは、特に集計を処理するために 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

この場合、グローバル一時テーブルがその役割を果たしました。すべての :old 行を BEFORE EACH ROW カーソルで収集し、AFTER STATEMENT で一時テーブルを削除テーブルと結合し、アイテムが削除された場所の新しい MAX 値を見つけます。

グローバル temp のトリガー エントリが他のトリガー イベントからのエントリを台無しにするのではないかという私の懸念は、一般的に MSSQL #tempTable と同じように間違っていました。ON COMMIT DELETE ROWS は正常に動作します。

MSSQL で (テストを含めて) 数時間で完了する単純なトリガー タスクが、Oracle のバックグラウンドをすべて読み込んでしまい、非常に時間がかかりすぎたのは残念です。また、Oracle SQL Developer では、行番号が間違っている (少なくとも表示されている行を参照して) 奇妙で不可解なエラー メッセージを数時間後に簡単に調べて、行末のセミコロンがないか、正しく閉じられていないことが原因であることがわかります。ブロック。

具体化されたビューは将来のオプションになる可能性があります。私の質問は、matview データはトランザクション (トリガーによって行われた変更など) ですか、それとも遅延レポート機能ですか? 定期的に更新するように構成できることは知っていますが、少なくともほぼ即座に「高速」更新を使用することもできます。ただし、これにはさらに厄介な制限があります (MAX 値を検索する場合は Where 句を使用しないなど)。

于 2012-07-05T19:47:45.883 に答える