过去几周我一直在尝试使用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()
是非常糟糕的做法。
有什么方法可以从属性表中读取然后更新房间表而不会出现变异错误?
答案 0 :(得分: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;