web-dev-qa-db-ja.com

同じテーブルのトリガーで親行を更新できません

現在、データベースに次の2つのテーブルがあります

CREATE TABLE folders (
  id NUMBER NOT NULL,
  name VARCHAR2(100) NOT NULL,
  bytes NUMBER DEFAULT 0 NOT NULL,
  parentId NUMBER,
  CONSTRAINT pk_folders PRIMARY KEY (id),
  CONSTRAINT fk_folders_parent FOREIGN KEY(parentId) REFERENCES folders(id)
);

CREATE TABLE files (
  id NUMBER NOT NULL,
  name VARCHAR2(100) NOT NULL,
  bytes NUMBER DEFAULT 0 NOT NULL,
  folderId NUMBER NOT NULL,
  CONSTRAINT fk_folders FOREIGN KEY (folderId) REFERENCES folders(id)
);

ファイルの行が挿入、削除、または更新されると、ファイルテーブルに行トリガーがあり、フォルダーのバイト列を更新します。これは、フォルダの合計サイズを追跡するためです。この場合、トリガーは正常に機能し、フォルダーに含まれるすべてのファイルの合計バイト数でフォルダーを正常に更新します。

CREATE OR REPLACE TRIGGER ari_files
AFTER INSERT ON files
FOR EACH ROW
BEGIN
  UPDATE folders f
  SET f.bytes = f.bytes + :NEW.bytes
  WHERE f.id = :NEW.folderid;
END ari_files;

また、各フォルダーがそのファイルとサブフォルダーに含まれるすべてのファイルの合計を保持するように、フォルダーの親も更新したいと思います。私が直面している問題は、フォルダーテーブルにトリガーを追加して、parentIdのバイト値を更新しようとするときです。フォルダーテーブルのトリガーもフォルダーテーブルを更新しているため、変化するテーブルエラー(ORA-04091)が発生します(ただし、常に別の行になり、階層にループはありません)。

CREATE OR REPLACE TRIGGER aru_bytes_folders
AFTER UPDATE OF bytes ON folders
FOR EACH ROW
WHEN (OLD.parentId IS NOT NULL)
BEGIN
  UPDATE folders f
  SET f.bytes = f.bytes + (:NEW.bytes - :OLD.bytes)
  WHERE f.id = :OLD.parentId;
END aru_bytes_folders;

これを機能させるためにできることはありますか?

私がこれまでに得た最も近いのは、一時テーブルを作成し、行トリガーでテーブルにデータを入力してから、ステートメントトリガーで一時テーブルから読み取り、その親で更新を処理します(これにより、さらに更新がトリガーされます)。私がそれに遭遇した問題は、一時テーブルにすでに処理された以前の更新が含まれていて、データベースがループでスタックしていたことでした。

3
mestar99

あなたがしたいことは実際に各フォルダで使用されたバイトの合計の概要を持っているようですよね? Oracleのトリガーは、この機能を提供するためのテクノロジーの最良の選択ではないと思います。これは、ビューを使用してより適切にモデル化できます。

トリガーの問題は、トリガーがDMLをテーブルにシリアル化し、あまりスケーリングされないことです。たくさんの行を更新して挿入している状況を想像してみてください。行ごとにフォルダテーブルが更新されます。

代わりにビューを使用して問題をモデル化する場合、フォルダーテーブルで余分なDMLのオーバーヘッドが発生することはありません。次のような単純なクエリでうまくいくはずです。

SELECT fd.name AS folder_name,
       SUM(f.bytes) AS folder_size
  FROM folders fd, files f
 WHERE fd.id = f.folderId
 GROUP BY fd.name;

次に、ビュー(ビューにクエリを実行するたびに合計が実行されます)またはコミット時に高速更新されるマテリアライズドビュー(この場合、マテリアライズドビューは計算済みのテーブルとして計算を保存するので便利です)を作成して、このクエリを永続化できます。テーブルが巨大で合計が高額な場合)。

マテリアライズドビューの「コミット時に高速リフレッシュ」機能は、基になるテーブルが変更されるたびに集計を再計算するタスクをOracleに委任します。

データウェアハウジングガイドには、この機能に関する詳細な説明があります。 https://docs.Oracle.com/database/121/DWHSG/refresh.htm#DWHSG015

0

変更テーブルエラーは、データベースの不整合が発生しないようにするためのものです。いくつかのハックがありますが、それらはほとんど常に悪い考えです。

Tom Kyteは、 トリガーが一般に悪い考えである理由 についての良いレビューを公開しています。テーブルの変更に関する問題については、「不適切な実装」というセクションで説明しています。

この場合、Danielaによって提案されたビューを持つソリューションがおそらく最良の実行可能性です。

0
Tony