【问题标题】:Oracle DBMS - Read a table before processing Updating in an AFTER trigger - mutating tableOracle DBMS - 在 AFTER 触发器中处理更新之前读取表 - 变异表
【发布时间】:2014-04-17 03:28:38
【问题描述】:

过去几周我一直在尝试使用 Oracle,但偶然发现了一个我似乎无法解决的问题。

我正在构建一个小型物业管理系统,我正在尝试在数据库端处理尽可能多的操作(纯粹是实验性的,我只是想在有人问之前澄清这一点,“你为什么不通过更新这些行客户”)

在我的系统中,我有一个属性和房间表(下面的简化架构):

`-------------------------------
`           PROPERTIES
`-------------------------------
`- PropertyID:          PK 
`- PropertyStatus:      VARCHAR
`------------------------------- 

`-------------------------------
`            ROOMS
`-------------------------------
`- RoomID:              PK 
`- PropertyID:          FK
`- RoomStatus:          VARCHAR
`------------------------------- 

每当用户被分配到一个房间时,房间状态都会更新为OCCUPIED,一旦发生这种情况,我想检查有多少与属性n相关的房间被占用,如果所有房间都被占用,property_status 应该被更新到 FULL,然后如果用户未从属性中分配,则值更新为 VACANCIES AVAILABLE 等。

我已经制定了这个的基本逻辑:

-- Return how many vacant rooms belong to this property
CREATE OR REPLACE FUNCTION prop_vacancy_query(
   p_property_id       properties.property_id%TYPE
)
RETURN NUMBER
   IS 
v_prop_rooms NUMBER;
BEGIN
   SELECT COUNT(room_status) 
   INTO v_prop_rooms
   FROM rooms 
     JOIN properties ON
     rooms.property_id = properties.property_id
     WHERE room_status = 'VACANT'
     AND rooms.property_id = p_property_id;
  RETURN v_prop_rooms;
END prop_vacancy_query;

在我的房间表上的 AFTER 触发器中,我尝试调用查询,但我得到一个变异表错误,我相信这是因为 prop_vacancy_query 正在读取属性表。

CREATE OR REPLACE TRIGGER trg_rooms_after
AFTER INSERT OR UPDATE ON rooms FOR EACH ROW
BEGIN
-- Update the table based on the result
IF prop_vacancy_query(:NEW.property_id) = 0 THEN
    UPDATE properties
    SET prop_status = 'VACANT'
    WHERE properties.property_id = :NEW.property_id;
ELSE
    UPDATE properties
    SET prop_status = 'FULL'
    WHERE properties.property_id = :NEW.property_id;
END IF;
END;

以前这段代码适用于我的系统,但自从阅读更多 pragma autonomous transactions 后,我意识到在自己的独立事务上运行 prop_vacancy_query() 是非常糟糕的做法。

有什么方法可以从属性表中读取,然后更新房间表而不会出现变异错误?

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    澄清一下,变异表异常被抛出是因为您试图从函数中的rooms 表中读取,而不是因为您试图从properties 表中读取。由于您在rooms 上有一个行级触发器,这意味着rooms 表在行级触发器触发时处于更改中间,并且它可能处于不一致状态。 Oracle 会阻止您在这种情况下查询 rooms 表,因为结果不一定是确定性的或可重现的。

    如果您创建了语句级触发器(删除FOR EACH ROW)并将您的逻辑放在那里,您将不再遇到变异表异常,因为rooms 表将不再处于不一致状态。但是,语句级触发器无法查看修改了哪些行。这意味着您需要查看所有属性以查看应该调整哪些状态值。这不会特别有效。

    以增加复杂性为代价,您可以通过捕获行级触发器中更改的属性然后在语句级触发器中引用该属性来提高性能。这通常需要三个触发器和一个包,这显然会大大增加移动部件的数量(如果您使用的是 11.2,则可以使用具有三个组件触发器的复合触发器,通过消除使用包的需要来简化一些事情) .那看起来像

    CREATE OR REPLACE PACKAGE trigger_collections
    AS
      TYPE modified_property_tbl IS TABLE OF properties.property_id%type;
      g_modified_properties modified_property_tbl;
    END;
    
    -- Initialize the collection in a before statement trigger just in case
    -- there were values there from a prior run
    CREATE OR REPLACE TRIGGER trg_initialize_mod_prop_coll
      BEFORE INSERT OR UPDATE ON rooms
    BEGIN
      trigger_collections.g_modified_properties := trigger_collections.modified_property_tbl();
    END;
    
    -- Put the property_id of the modified row in the collection
    CREATE OR REPLACE TRIGGER trg_populate_mod_prop_coll
      AFTER INSERT OR UPDATE ON rooms
      FOR EACH ROW
    BEGIN
      trigger_collections.g_modified_properties.extend();
      trigger_collections.g_modified_properties( trigger_collections.g_modified_properties.count + 1 ) := :new.property_id;
    END;
    
    CREATE OR REPLACE TRIGGER trg_process_mod_prop_coll
      AFTER INSERT OR UPDATE ON rooms
    BEGIN
      FOR p IN 1 .. trigger_collections.g_modified_properties.count
      LOOP
        IF prop_vacancy_query( trigger_collections.g_modified_properties(i) ) = 0 
        THEN
          ...
    END;
    

    【讨论】:

    • 感谢您抽出宝贵时间,贾斯汀的解决方案和解释消除了我对此事的困惑! :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 2016-06-11
    相关资源
    最近更新 更多