web-dev-qa-db-ja.com

BEFOREINSERTトリガーでIFEXISTS(SELECT ...)を使用する(Oracle)

私が持っているコードは機能しません。Oracleは、トリガーがビルドエラーで作成されたと言っています。どうやら私はビルドエラーが何であるかについてこれ以上正確な情報を得ることができません...

私はこれまでSQLをあまり行ったことがないので、構文にあまり詳しくありません。オラクルが気に入らないのはIFEXISTS(SELECT ...)THENステートメントだという予感があります。同様の例をグーグルで探していましたが、自分の状況で機能するものは実際には見つかりませんでした。

だからコードについて:

  • 「デビュー」は日付属性です(開始を意味します)
  • 「fin」は別の日付属性です(終了を意味します)
  • これらの2つの行が同じ「numInfirmier」属性を持っている場合、新しい行の日付がテーブル内の他の行と重複しないようにしたいです。
  • そのため、新しい行と同じnumInfirmierを持ち、日付が重複しているすべての行を選択します。
  • そして、その選択に何かが存在する場合、私はエラーを発生させます。

    CREATE OR REPLACE TRIGGER chev_surv
    BEFORE INSERT OR UPDATE ON surveillance
    FOR EACH ROW
    BEGIN
        IF EXISTS (
            SELECT * FROM surveillance
            WHERE surveillance.numInfirmier = :NEW.numInfirmier
            AND ((surveillance.debut > :NEW.debut AND surveillance.debut < :NEW.fin)
            OR (surveillance.fin > :NEW.debut AND surveillance.fin < :NEW.fin))
        ) THEN
            RAISE_APPLICATION_ERROR(-20001,
            'Il ne doit pas y avoir de chevauchement entre deux périodes surveillance pour un surveillant.');
        END IF;
    END;
    /
    

何が問題なのか分かりますか?

4
MademoiselleC

まず、SQL * Plusを使用している場合、オブジェクトを作成してコンパイルエラーがあると通知されたら、コマンドshow errorsエラーが表示されます。

実行した場合show errors、あなたはIF EXISTSは有効な構文ではありません。あなたは次のようなことをすることができます

SELECT COUNT(*)
  INTO l_cnt
  FROM <<rest of query>>

IF( l_cnt > 0 )
THEN
  RAISE_APPLICATION_ERROR ...
END IF;

ただし、コンパイルエラーを修正すると、ランタイムエラーが発生します。 surveillanceの行レベルのトリガーでは、通常、surveillanceをクエリすることはできません(実行しているのがINSERT VALUES単一の行のみを挿入することが保証されています)。これを行うと、実行時に変更トリガーエラーが発生します。

データモデルの観点から、特定の行の有効なデータが同じテーブルの他の行に格納されているデータに依存するテーブルを設計していることに気付いた場合、通常は正規化の原則に違反しており、一般的に修正するほうがよいでしょう。基礎となるデータモデル。

データモデルを保持することを本当に決心している場合は、コミット時に更新されるマテリアライズドビューを作成します。このビューには、条件に違反する行のデータのみが含まれます。次に、基準に違反した場合にコミット時にエラーをスローするマテリアライズドビューに制約を設定できます。これには、テーブルにマテリアライズドビューログが必要です。

本当にデータモデルを保持し、トリガーを使用してロジックを適用する場合は、従来の3つのトリガーソリューション(または11.2以降を使用している場合は3つの部分からなる複合トリガー)が必要になります。主キー値のコレクションを使用してパッケージを作成します。 beforeステートメントトリガーはコレクションを初期化します。行レベルのトリガーは、このコレクションに挿入および/または更新された行の主キーを挿入します。そして、afterステートメントトリガーはこのコレクションを繰り返し処理し、必要なチェックを実装します。しかし、それは多くの感動的な部分です、それが私が一般的にそれに反対する理由です。

さらに、これらすべての要素が機能していても、マルチユーザー環境ではロジックで保護されません。複数のユーザーが同時にシステムにアクセスしている場合、1人のユーザーが行を挿入し、2番目のユーザーが範囲が重複する別の行を挿入してから、各セッションがコミットされる可能性があります。その場合、両方のトリガーセットで変更が許可されますが、要件に違反するデータがテーブルに残ります。マテリアライズドビューは、挿入時ではなくコミット時に適用されるため、マルチユーザー環境で適切に機能します。マルチユーザー環境でトリガーを機能させる場合は、最初のセッションがコミットされるか、最初のセッションがコミットされるまで、2番目のセッションのinsertの実行をブロックするシリアル化を強制するロジックを追加して、トリガーをさらに複雑にする必要があります。ロールバック。これにより、複雑さが増し、スケーラビリティが低下し、実装方法によっては、サポートが悪夢になる可能性があります。

7
Justin Cave