在具有“新”和“旧”值依赖项的Oracle触发器中重构子句

时间:2019-07-29 19:28:36

标签: oracle

考虑以下Oracle触发器,该触发器不起作用,因为每个if语句中都有一个子句

CREATE OR REPLACE TRIGGER track_rm_area_t
BEFORE UPDATE OR INSERT ON RM
FOR EACH ROW
BEGIN

    IF(:NEW.area IS NOT NULL) THEN
        INSERT INTO rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, date_effective, usable_flag)
        VALUES(:NEW.bl_id, :NEW.fl_id, :NEW.rm_id, :NEW.rm_cat, :NEW.rm_type, :NEW.area, SYSDATE, 
            CASE WHEN :NEW.rm_cat IN (SELECT rm_cat FROM rmcat WHERE supercat = 'USBL') THEN 1 ELSE 0 END);
    ELSIF(:NEW.rm_cat IS NOT NULL AND :OLD.rm_cat IS NOT NULL AND (:NEW.rm_cat <> :OLD.rm_cat)) THEN
        IF(
            :NEW.rm_cat IN (SELECT rm_cat FROM rmcat WHERE supercat = 'USBL') 
            AND :OLD.rm_cat NOT IN (SELECT rm_cat FROM rmcat WHERE supercat = 'USBL')
        ) THEN
        INSERT INTO rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, date_effective, usable_flag)
        VALUES(:NEW.bl_id, :NEW.fl_id, :NEW.rm_id, :NEW.rm_cat, :NEW.rm_type, :NEW.area, SYSDATE, 1);
        ELSIF 
        (
            :NEW.rm_cat NOT IN (SELECT rm_cat FROM rmcat WHERE supercat = 'USBL') 
            AND :OLD.rm_cat IN (SELECT rm_cat FROM rmcat WHERE supercat = 'USBL')
        ) THEN
        INSERT INTO rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, date_effective, usable_flag)
        VALUES(:NEW.bl_id, :NEW.fl_id, :NEW.rm_id, :NEW.rm_cat, :NEW.rm_type, :NEW.area, SYSDATE, 0);
        END IF;
    END IF;
END;
/
COMMIT;

总体而言,我要完成的工作是:

  • 任何时候更新rm.area的时间,请将一条记录插入rm_area。如果房间可用,则rm_area.usable_flag应该为1,否则为0。如果rm.rm_cat IN (SELECT rm_cat FROM rmcat WHERE rmcat.supercat = 'USBL')
  • ,则将房间定义为可用
  • 如果已更新现有记录的rm.rm_cat,以致该房间不再可用,反之亦然,则将新记录插入到rm_area中,并带有已修改的标志及其现有面积值。

我知道我需要一个变量和循环来遍历子查询中的所有值,但是不确定如何最好地考虑这一点。当我尝试以下操作时:

CREATE OR REPLACE TRIGGER track_rm_area_t
BEFORE UPDATE OR INSERT ON RM
FOR EACH ROW
DECLARE 
    usable_rm_cat VARCHAR(12 BYTE);
BEGIN
    FOR r IN (SELECT rm_cat INTO usable_rm_cat FROM rmcat WHERE supercat = 'USBL')
    LOOP
    usable_rm_cat := r.rm_cat;
    IF
    (
        :NEW.area IS NOT NULL OR 
        (
            :NEW.rm_cat IS NOT NULL AND :OLD.rm_cat IS NOT NULL AND (:NEW.rm_cat <> :OLD.rm_cat) AND
                (
                    (:OLD.rm_cat IN (usable_rm_cat) 
                    AND :NEW.rm_cat NOT IN (usable_rm_cat))

                    OR (:OLD.rm_cat NOT IN (usable_rm_cat)
                    AND :NEW.rm_cat IN (usable_rm_cat))
                )
        )
    ) THEN
        INSERT INTO rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, date_effective, usable_flag)
        VALUES(:NEW.bl_id, :NEW.fl_id, :NEW.rm_id, :NEW.rm_cat, :NEW.rm_type, :NEW.area, SYSDATE, 
            CASE WHEN :NEW.rm_cat IN (usable_rm_cat) THEN 1 ELSE 0 END);
            EXIT;
    END IF;
    END LOOP;
END;
/
COMMIT;

insert语句中的CASE总是产生0

要解决此问题,我考虑过从循环开始,然后检查是否为:NEW.rm_cat = r.rm_cat,但这使我无法确定如何最好地检查:OLD.rm_cat的值并处理所有更新的情况到rm.area区域需要进行跟踪(这要优先于rm_cat的任何值)。

如何创建触发器,以便跟踪rm.area的所有值并将其插入此新表中,以及rm.rm_cat的特定值?

2 个答案:

答案 0 :(得分:1)

我认为您使事情复杂化了,尤其是在这里不需要任何循环。首先在变量中选择值,然后在插入时使用它们:

create or replace trigger track_rm_area_t
before update or insert on rm for each row
    v_old_usable int;
    v_new_usable int;
begin

    select nvl(max(case rm_cat when :old.rm_cat then 1 end), 0),
           nvl(max(case rm_cat when :new.rm_cat then 1 end), 0)
        into v_old_usable, v_new_usable 
        from rmcat
        where rm_cat in (:old.rm_cat, :new.rm_cat) and supercat = 'USBL';

    if :new.area is not null then

        insert into rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, 
            date_effective, usable_flag)
        values(:new.bl_id, :new.fl_id, :new.rm_id, :new.rm_cat, :new.rm_type, :new.area, 
            sysdate, v_new_usable);

    elsif :new.rm_cat is not null and :old.rm_cat is not null and :new.rm_cat <> :old.rm_cat 
        and v_old_usable <> v_new_usable then

        insert into rm_area(bl_id, fl_id, rm_id, rm_cat, rm_type, area, 
            date_effective, usable_flag)
        values(:new.bl_id, :new.fl_id, :new.rm_id, :new.rm_cat, :new.rm_type, :new.area, 
            sysdate, v_new_usable);

    end if;
end;

我没有您的桌子,所以没有经过测试。希望对您有所帮助:)

答案 1 :(得分:0)

触发器应始终非常轻巧。尽管一直在执行此操作,但许多人认为用触发器维护辅助表是一种不好的形式。我已经做到了,对此感到遗憾。我希望您永远不必使用这样的触发器进行批量加载。也就是说,请在插入内容之外进行计数。这是您的SQL的第一部分:

CREATE OR REPLACE TRIGGER track_rm_area_t
    BEFORE UPDATE OR INSERT
    ON rm
    FOR EACH ROW
DECLARE
    l_cnt   INTEGER;
BEGIN
    IF (:new.area IS NOT NULL)
    THEN
        SELECT COUNT( * )     c
          INTO l_cnt
          FROM rmcat
         WHERE supercat = 'USBL' AND rm_cat = :new.rm_cat AND ROWNUM = 1;

        INSERT INTO rm_area( bl_id
                           , fl_id
                           , rm_id
                           , rm_cat
                           , rm_type
                           , area
                           , date_effective
                           , usable_flag )
             VALUES ( :new.bl_id
                    , :new.fl_id
                    , :new.rm_id
                    , :new.rm_cat
                    , :new.rm_type
                    , :new.area
                    , SYSDATE
                    , l_cnt );
. . .