我有一张表可以为一个帐户保存许多记录:不同金额。
ACCOUNTID | AMOUNT
id1 | 1
id1 | 2
id2 | 3
id2 | 4
每次插入/更新/删除此表中的记录时,我们需要评估总量以了解是否应该触发事件(通过将数据插入另一个表)。金额根据此表中的记录总数(每个帐户)计算。
金额的计算应使用记录的新值,但我们还需要旧值以检查某些条件(例如旧值为X - 新值为Y:如果[X <=阈值且Y>阈值]然后通过将记录插入另一个表来触发事件。)
因此,为了计算和触发事件,我们在此表上创建了一个触发器。像这样:
CREATE OR REPLACE TRIGGER <trigger_name>
AFTER INSERT OR UPDATE OR DELETE OF MOUNT ON <table_name>
FOR EACH ROW
DECLARE
BEGIN
1. SELECT SUM(AMOUNT) INTO varSumAmounts FROM <table_name> WHERE accountid = :NEW.accountid;
2. varAmount := stored_procedure(varSumAmounts);
END <trigger_name>;
问题是语句1.抛出以下错误:'ORA-04091:表正在变异,触发器/函数可能看不到它。'
我们尝试了以下但没有成功(相同的异常/错误)来选择rowId与当前rowId不同的所有记录:
(SELECT SUM(AMOUNT)
INTO varSumAmounts
FROM <table_name>
WHERE accountId = :NEW.accountid
AND rowid <> :NEW.rowid;)
为了计算金额,作为当前行旁边所有行的金额总和+当前行的数量(我们在触发器的上下文中有)。
我们搜索了其他解决方案,我们发现了一些,但我不知道它们中哪一个更好,每个解决方案的缺点是什么(尽管它们有些相似)
使用复合触发器
http://www.oracle-base.com/articles/9i/mutating-table-exceptions.php
http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
为避免基于解决方案1和2的“表格变异”错误,我使用了复合触发器与全局临时表的组合。
现在我们有一个复合触发器,它使用一些全局临时表来存储以下相关数据:OLD和:NEW伪记录。基本上我们会做下一件事:
CREATE OR REPLACE TRIGGER trigger-name
FOR trigger-action ON table-name
COMPOUND TRIGGER
-------------------
BEFORE STATEMENT IS
BEGIN
-- Delete data from global temporary table (GTT) for which source is this trigger
-- (we use same global temporary tables for multiple triggers).
END BEFORE STATEMENT;
-------------------
AFTER EACH ROW IS
BEGIN
-- Here we have access to :OLD and :NEW objects.
-- :NEW and :OLD objects are defined only inside ROW STATEMENTS.
-- Save relevant data regarding :NEW and :OLD into GTT table to use it later.
END AFTER EACH ROW;
--------------------
AFTER STATEMENT IS
BEGIN
-- In this block DML operations can be made on table-name(the same table on which
--the trigger is created) safely.
-- Table is mutating error will no longer appear because this block is not for EACH ROW specific.
-- But we can't access :OLD and :NEW objects. This is the reason why in 'AFTER EACH ROW' we saved them in GTT.
-- Because previously we saved :OLD and :NEW data, now we can continue with our business logic.
-- if (oldAmount<=threshold && newAmount>threshold) then
-- trigger event by inserting record into another table
END AFTER STATEMENT;
END trigger-name;
/
使用的全局临时表是使用选项'ON COMMIT DELETE ROWS'创建的,这样我就可以确保在事务结束时清理此表中的数据。 然而,发生了这个错误:'ORA-14450:尝试访问已在使用的事务临时表'。
问题是应用程序使用分布式事务,并在oracle文档中提到: “将全局临时表(GTT)与分布式或XA事务结合使用时,可以报告各种内部错误。 ...
任何分布式XA协调事务都不支持临时表。 最安全的选择是不在分布式或XA事务中使用临时表,因为它们在此上下文中的使用不受官方支持。 ...
如果数据库中只有单个分支事务使用它,则可以安全地使用全局临时表,但如果存在环回数据库链接或涉及多个分支的XA事务,则可能会出现问题,包括根据错误5344322的块损坏。 “
值得一提的是,我无法避免XA事务或在同一个表上制作DML,这是触发器的主题(修复数据模型不是一个可行的解决方案)。我尝试使用而不是全局临时表一个触发器变量 - 一个集合(对象表),但我不确定这种方法。分布式交易是否安全?
在这种情况下,哪个其他解决方案适合修复初始问题:'ORA-04091:表名变异,触发/功能可能看不到',或者第二个:'ORA-14450:尝试访问已经在使用的交易临时表'?
答案 0 :(得分:0)
您应该仔细检查您的代码是否使用自治事务来访问临时表数据:
SQL> create global temporary table t (x int) on commit delete rows
2 /
SQL> insert into t values(1)
2 /
SQL> declare
2 pragma autonomous_transaction;
3 begin
4 insert into t values(1);
5 commit;
6 end;
7 /
declare
*
error in line 1:
ORA-14450: attempt to access a transactional temp table already in use
ORA-06512: error in line 4
答案 1 :(得分:0)
如果您使用DELETE FROM <temp-table-name>
或BEFORE STATEMENT
来定义GTT,那么AFTER STATEMENT
和ON COMMIT PRESERVE ROWS
中的ON COMMIT DELETE ROWS
并不重要。
在触发器中,您可以定义RECORD / TABLE变量。您可以在BEFORE STATEMENT
块中初始化此变量,并在BEFORE STATEMENT
块中循环它。
会是这样的:
CREATE OR REPLACE TRIGGER TRIGGER-NAME
FOR TRIGGER-action ON TABLE-NAME
COMPOUND TRIGGER
TYPE GTT_RECORD_TYPE IS RECORD (ID NUMBER, price NUMBER, affected_row ROWID);
TYPE GTT_TABLE_TYPE IS TABLE OF GTT_RECORD_TYPE;
GTT_TABLE GTT_TABLE_TYPE;
-------------------
BEFORE STATEMENT IS
BEGIN
GTT_TABLE := GTT_TABLE_TYPE(); -- init the table variable
END BEFORE STATEMENT;
-------------------
AFTER EACH ROW IS
BEGIN
GTT_TABLE.EXTEND;
GTT_TABLE(GTT_TABLE.LAST) := GTT_RECORD_TYPE(:OLD.ID, :OLD.PRICE, :OLD.ROWID);
END AFTER EACH ROW;
--------------------
AFTER STATEMENT IS
BEGIN
FOR i IN GTT_TABLE.FIRST..GTT_TABLE.LAST LOOP
-- do something with values
END LOOP;
END AFTER STATEMENT;
END TRIGGER-NAME;
/