2017-05-17 13 views
1

この問題の適切な解決策を見つけるのに数時間を費やしたので、Qでこの問題を作成しました。PL/SQL:列を更新するときに再帰的なトリガーを避ける

私はこのようなテーブルを持っている:

CREATE TABLE SoftwareVersion 
(
    ID NUMBER NOT NULL, 
    DeviceID NUMBER NOT NULL, 
    ReadoutDate DATE NOT NULL, 
    Version VARCHAR2(20 CHAR) NOT NULL, 
    NextReadoutDate DATE NULL 
); 

このテーブルには、デバイスのソフトウェアバージョンのコードが含まれています。各デバイスは、1つ以上のソフトウェアバージョンを持つことができます。このテーブルのINSERT文を実行するインポートプロセスでは、ID、DeviceID、ReadoutDate、およびVersionのみが入力されます。 ReadoutDateは、インポートプロセスの現在のタイムスタンプです。 ソフトウェアバージョンの最初の発生は、「ソフトウェアバージョンは読み取り日以降有効です」とみなされます。

私の問題は、ソフトウェアバージョンの範囲が必要になったことです。どのタイムスタンプから各ソフトウェアバージョンがタイムスタンプに有効か?

パフォーマンスを向上させるため(およびそのテーブルのトリガーが既にあるので)、トリガーによって維持されるNextReadoutDate列を追加しました。そのDeviceIDの次の有効なReadoutDate値を受け取ります。したがって、各ソフトウェアのバージョンは範囲になります(〜から有効)。

変更表の問題(ORA-04091)を回避するために、私は私はこのようなAFTER行トリガーを使用してい文のすべての更新情報の収集:その後

CREATE OR REPLACE TRIGGER TRG_SoftwareVersion1 
    AFTER INSERT OR UPDATE OR DELETE ON SoftwareVersion 
    FOR EACH ROW 
BEGIN 
    IF UPDATING THEN 
    IF :OLD.DeviceID = :NEW.DeviceID AND :OLD.ReadoutDate = :NEW.ReadoutDate OR 
     (:OLD.DeviceID IS NULL OR :OLD.ReadoutDate IS NULL) AND 
     (:NEW.DeviceID IS NULL OR :NEW.ReadoutDate IS NULL) THEN 
     -- Nothing to do 
     RETURN; 
    END IF; 
    END IF; 

    -- Evaluate later 
    INSERT INTO 
    SoftwareVersion_TrgHelper 
    (
     OldDeviceID, 
     OldReadoutDate, 
     NewDeviceID, 
     NewReadoutDate 
    ) 
    VALUES 
    (
     :OLD.DeviceID, 
     :OLD.ReadoutDate, 
     :NEW.DeviceID, 
     :NEW.ReadoutDate 
    ); 
END; 

を私はAFTER文の中でテーブルを更新

CREATE OR REPLACE TRIGGER TRG_SoftwareVersion2 
    AFTER INSERT OR UPDATE OR DELETE ON SoftwareVersion 
DECLARE 
    CURSOR cCursorMain IS SELECT * FROM SoftwareVersion_TrgHelper FOR UPDATE; 
    vOldDeviceID    NUMBER; 
    vOldReadoutDate   DATE; 
    vNewDeviceID    NUMBER; 
    vNewReadoutDate   DATE; 
BEGIN 
    OPEN cCursorMain; 
    LOOP 
    FETCH cCursorMain INTO vOldDeviceID, vOldReadoutDate, vNewDeviceID, vNewReadoutDate; 
    EXIT WHEN cCursorMain%NOTFOUND; 

    IF UPDATING OR DELETING THEN 
     UPDATE 
     SoftwareVersion SV 
     SET 
     SV.NextReadoutDate = (SELECT MIN(SV2.ReadoutDate) KEEP (DENSE_RANK FIRST ORDER BY SV2.ReadoutDate ASC, SV2.ID ASC) FROM SoftwareVersion SV2 WHERE SV.DeviceID = SV2.DeviceID AND SV.ReadoutDate < SV2.ReadoutDate) 
     WHERE 
     SV.DeviceID  = vOldDeviceID AND 
     SV.ReadoutDate <= vOldReadoutDate; 
    END IF; 
    IF UPDATING OR INSERTING THEN 
     UPDATE 
     SoftwareVersion SV 
     SET 
     SV.NextReadoutDate = (SELECT MIN(SV2.ReadoutDate) KEEP (DENSE_RANK FIRST ORDER BY SV2.ReadoutDate ASC, SV2.ID ASC) FROM SoftwareVersion SV2 WHERE SV.DeviceID = SV2.DeviceID AND SV.ReadoutDate < SV2.ReadoutDate) 
     WHERE 
     SV.DeviceID  = vNewDeviceID AND 
     SV.ReadoutDate <= vNewReadoutDate; 
    END IF; 
    DELETE FROM SoftwareVersion_TrgHelper WHERE CURRENT OF cCursorMain; 
    END LOOP; 
    CLOSE cCursorMain; 
END; 
/

残念ながら、私はインポートプロセスを再度実行した直後にORA-00036を取得しました。 トリガーの再帰を避ける方法が必要でした。

答えて

1

(SQLサーバが行うように)OracleはTRIGGER_NESTLEVELのような機能を提供していないと、(あなたがセッション変数またはこのようなものを使用することができますが、これは他のエラーが発生する可能性があります)すべての再帰を避けるために、多くの機能が存在しないようです。

私の単純な解決策は、特定の列が変更されたときにのみトリガーをトリガーできるようにすることです。トリガー内の私のUPDATEステートメントはNextReadoutDate列のみを変更するため、これが可能です。代わりの

AFTER INSERT OR UPDATE OR DELETE OF DeviceID, ReadoutDate ON SoftwareVersion 

を、すべてが、その後罰金です:

AFTER INSERT OR UPDATE OR DELETE ON SoftwareVersion 

私が使用しています。