6

私のテーブル:

TableA (id number, state number)
TableB (id number, tableAId number, state number)
TableC (id number, tableBId number, state number)

したがって、TableC の項目は TableB の子であり、TableB の項目は TableA の子です。その逆 - TableA の項目は TableB の親であり、TableB の項目は TableC の親です。

親アイテムの状態を制御したい...たとえば、次のデータがあるとしましょう:

TableA (id, state): 
1, 40

TableB (id, tableAId, state): 
1, 1, 40
2, 1, 60

TableC (id, tableBId, state): 
1, 1, 40
2, 1, 50
3, 2, 60
4, 2, 70

親の状態は常に、その子の最小の状態でなければなりません。したがって、TableC を次のように更新するとします。

update TableC set state = 50 where Id = 1;

私のトリガーは自動的に TableB を更新し (id = 1 の状態 = 50 に設定)、次に TableA (id = 1 の状態に状態 = 50 を設定) も更新する必要があります。

これをトリガー (AFTER UPDATE、INSERT、DELETE、TableA、TableB、TableC で) で行いたいので、すべてのアクションの後にこの手順が実行されます。

  1. 親IDを取得する
  2. 現在の親のすべての子から最小の状態を見つける
  3. すべての子の最小状態が親の状態より大きい場合、親を更新します

「変更テーブルエラー」を回避するにはどうすればよいですか? この例で自律型トランザクションを使用しても問題ないでしょうか? テーブルの変更エラーはアプリケーションのロジックに欠陥があることを示しているという意見をいくつか見ましたが、これは本当ですか?また、このエラーを防ぐためにロジックを変更するにはどうすればよいですか?

ありがとう


編集:すべてのすばらしい答えをありがとう!

最後に、トリガーを使用しました (Tom Kyte の記事を指摘してくれた Vincent Malgrat に感謝します)。


EDIT:REAL ENDでは、ストアドプロシージャを使用し、トリガーを削除しました:)

4

8 に答える 8

12

お気づきのように、トリガーを使用してビジネス要件に対応するのは困難です。その理由は、Oracle単一のクエリ (並列 DML) に対して同時に複数のスレッドでテーブルを更新/挿入する可能性があるためです。これは、更新が行われている間、セッションが更新するテーブルに対してクエリを実行できないことを意味します。

トリガーを使用してこれを本当に実行したい場合は、Tom Kyte によるこの記事に示されているようなロジックに従う必要があります。ご覧のとおり、単純なものではありません。

もう 1 つの、よりシンプルで洗練された、維持しやすい方法があります: プロシージャを使用します。アプリケーションのユーザーに対する更新/挿入の権利を取り消し、アプリケーションが状態列を更新できるようにする一連のプロシージャを記述します。

これらのプロシージャは、(複数のセッションが同じ行セットを変更するのを防ぐために) 親行のロックを保持し、ビジネス ロジックを効率的で読みやすく、保守しやすい方法で適用します。

于 2010-01-26T09:29:19.783 に答える
5

複雑なビジネスロジックにはトリガーを使用しないでください。ストアド プロシージャ (PL/SQL パッケージ) またはクライアント コードに移動します。トリガーが多いアプリは、すぐに「一連のアクション」の感覚が失われるため、メンテナンスができなくなります。

自律型トランザクションの使用は絶対に安全ではありません。自律型トランザクションは、ロギング、トレース、デバッグ、および場合によっては監査にのみ使用してください。

読む: http://www.oracle.com/technetwork/issue-archive/2008/08-sep/o58asktom-101055.html

ここでは、自律型トランザクションを使用せずにトリガーを使用する場合の問題を解決する方法を読むことができます: http://www.procaseconsulting.com/learning/papers/200004-mutating-table.pdf

于 2010-01-26T09:19:22.597 に答える
3

計算を実行するためのビューを含めるようにソリューションをリファクタリングできますか?

CREATE VIEW a_view AS
SELECT a.Id, min(b.State) State FROM tableA,tableB
WHERE a.Id=b.tableAId
GROUP BY a.Id;

ストアドプロシージャ(他の投稿で提案されている)も良い候補であることに同意しますが、データを保持するにはストアドプロシージャの実行をスケジュールする必要があると思いますが、ビューは自動的に最新の状態に保たれることに注意してください'in-sync':これは問題ないかもしれません-それはあなたの要件に依存します。

別のオプションは、計算を行うためのいくつかの関数を作成することだと思いますが、個人的にはビューアプローチを選択します(すべてが等しい)。

于 2010-01-26T09:36:39.110 に答える
3

テーブルの変更エラーはアプリケーションのロジックに欠陥があることを示しているという意見をいくつか見ましたが、これは本当ですか?また、この エラー
を防ぐためにロジックを変更するにはどうすればよいですか?

あなたがどこでそれを見たかはわかりませんが、以前に何度もその意見を投稿したことは知っています.

テーブルの変更は、通常、データ モデルの欠陥を示していると思うのはなぜですか? ORA-4091 を発生させるコードを駆動する「要件」の種類は、多くの場合、不十分な設計、特に不十分な正規化に関連付けられているためです。

あなたのシナリオは、この典型的な例です。TableC挿入または更新時に選択しているため、ORA-04091 が発生します。しかし、なぜあなたはから選択していTableCますか?親の列を更新する必要があるため、TableB. しかし、その列は冗長な情報です。完全に正規化されたデータ モデルでは、その列は存在しません。

非正規化は、クエリのパフォーマンスを向上させるためのメカニズムとしてよく宣伝されます。残念ながら、非正規化の支持者は、挿入、更新、および削除の際に過度に複雑な通貨で支払われるそのコストについて口を閉ざしています。

では、どうすればロジックを変更できますか? 簡単な答えは、列を削除し、親 ID ごとに最小の状態を保存する必要はありません。代わりに、MIN()その情報が必要なときはいつでもクエリを実行してください。頻繁に必要で、クエリの実行に費用がかかる場合は、データを格納するマテリアライズド ビューを作成します (必ず を使用してくださいENABLE QUERY REWRITE) 。

于 2010-01-26T15:09:23.050 に答える
2

ロジックが失敗する理由の例として、親 A に子レコード 1A および 1B を持つレコード 1 があるシナリオを考えてみましょう。1A の STATE は 10 で 1B は 15 であるため、親を 10 にする必要があります。

ここで、誰かが 1A の STATE を 20 に更新し、同時に誰かが 1B を削除します。1B の削除はコミットされていないため、1A を更新するトランザクションは引き続き 1B を認識し、親の状態を 15 に設定する必要がありますが、1B を削除するトランザクションは 1A の古いコミットされていない値を認識し、親の状態を10.

これを非正規化する場合は、子レコードを挿入/更新/削除する前に、親レコードがロックされ、変更を実行し、すべての子レコードを選択し、親を更新してから、ロックに細心の注意を払う必要があります。ロックを解放するために commit します。トリガーを使用して行うこともできますが、ストアド プロシージャを使用することをお勧めします。

于 2010-01-26T21:51:26.680 に答える
2

トリガーと整合性制約の両方を使用して、あらゆるタイプの整合性ルールを定義および適用できます。ただし、トリガーを使用してデータ入力を制約するのは、次の状況に限定することを強くお薦めします。

子テーブルと親テーブルが分散データベースの異なるノードにある場合に参照整合性を適用する場合 整合性制約を使用して定義できない複雑なビジネス ルールを適用する場合 次の整合性制約を使用して必要な参照整合性ルールを適用できない場合:

  • 非 NULL、一意
  • 主キー
  • 外部キー
  • 小切手
  • カスケードの削除
  • DELETE SET NULL

出典: Oracle9i データベースの概念

于 2010-01-26T09:24:56.330 に答える
1

自律トランザクションを使用しないでください。使用すると、非常に興味深い結果が得られます。

テーブルの変更の問題を回避するには、次のようにします。

AFTER INSERT OR UPDATE OR DELETE FOR EACH ROWトリガーで、親IDを見つけて、それをPL / SQLコレクション(PACKAGE内)に保存します。次に、AFTER INSERT OR UPDATE OR DELETE TRIGGER(ステートメント・レベル、「for each row」の部分なし)で、PL / SQLコレクションから親IDを読み取り、それに応じて親表を更新します。

于 2010-01-26T13:17:47.470 に答える
1

このようなことをすることは大きな誘惑であり、他の人が参照しているトム・カイトの記事の提案に従うと、それは可能です。しかし、何かができるからといって、それが行われるべきだという意味ではありません。このようなものをストアドプロシージャ/関数/パッケージとして実装することを強くお勧めします。この種の複雑なロジックは、明らかな誘惑にもかかわらず、トリガーを使用して実行しないでください。これは、対応するユーティリティの増加なしにシステムの複雑さを大幅に高めるためです。私は時々このようなコードに取り組む必要があり、それは喜びではありません。

幸運を。

于 2010-01-26T13:07:55.700 に答える