在Trigger

时间:2016-05-30 18:19:10

标签: sql oracle plsql oracle10g

考虑下表

create table Test1
(
id number(5),
batch number(5),
value number(10)
);

Insert into test1 values(1,10,200);
Insert into test1 values(2,10,700);
Insert into test1 values(3,10,400);
Insert into test1 values(1,20,1000);
Insert into test1 values(2,20,100);
Insert into test1 values(3,20,5000);
commit;

现在,这里的要求是每批只保留1行并添加批处理的所有值,并将其分配给要保留的Id(此处选择的最小ID)。应删除批处理的剩余行。只要为批处理插入行,就会发生这种情况。

预期结果

Id Batch Value
1  10    1300
1  20    1600 

现有系统有一个逻辑,其中Test1上有After Insert或Update Trigger,它通过dbms_job调用Package。在此软件包中,更新和删除发生。

Create or replace trigger Trigger_Test_1
After Insert or Update of Value on Test_1
For Each row
Begin
...
dbms_job(call to package Pkg_Test_1.Proc_1(:New.Id, :New.Batch));
...
End;

Create or Replace Package Body Pkg_Test_1
As
Procedure Proc_Test_1(p_id number, p_batch number) Is
Begin
Select Min(Id), Batch, sum(Value)
Into v_id, v_batch, value_sum
From Test1
Where Batch = p_batch
Group by Batch
Having count(*) > 1;

If (v_id is not null) then
 update test1
 set value = value_sum
 where id = v_id;

 delete from test1
 where id <> v_id
 and batch = p_batch;
commit;
End If;

End;
End;

问题出在更新期间,其中发生另一次对Trigger(Trigger_Test_1)的调用,导致值在无限循环中更新。最后我不得不删除记录以停止更新。 这是一个尴尬的情况,我知道在触发器中编写这样的逻辑是不值得推荐的,但这是我们系统中现有的逻辑。

有关如何改进代码或如何以不同方式实现此结果的任何想法?

2 个答案:

答案 0 :(得分:1)

你能够在触发器中编写它的唯一方法是使用INSTEAD OF触发器,触发器动作,但根本不执行该动作。如果你没有,你将得到 ORA-04091:表A正在变异,触发器/功能可能看不到它,这是因为你正在尝试修改当前正在进行的表在同一会话中被修改。

您试图通过DBMS_JOB (note that DBMS_JOB is deprecated in 10g in favour of DBMS_SCHEDULER)异步执行更新。这也不会真正起作用 - 你可以遇到并发问题并且你没有解决基本问题 - 人们正在将数据插入到为其他东西设计的表中。

你的问题的答案是改变对表的期望。您只能 这样做才能为列BATCH添加唯一键。从你所说的,这是你的自然钥匙。应用程序应该期望在与此表交互时只有一行,并且应该考虑到这一点。

显然,这并不能解决所有遗留应用程序的问题。但是,也有答案。

  1. 将表重命名为其他内容(请耐心等待。)
  2. 在表格上创建一个与表格名称相同的视图。
  3. 如上所述,在视图上创建一个INSTEAD OF INSERT触发器 - 这意味着每当有人试图与表交互不正确时,您立即处理它。此触发器应使用MERGE statement
  4. 类似的东西:

    rename test1 to real_test1;
    alter table test1 add constraint uk_test1 unique (batch);
    create or replace view test1 as select * from real_test1;
    
    create or replace trigger tr_test1
     instead of insert on test1
     for each row
    declare
    
       merge into real_test1 o
       using ( select :new.batch as batch
                    , :new.value as value
                 from dual ) n
          on (o.batch = n.batch)
        when matched then
             update
                set o.value = o.value + n.value
        when not matched then
             insert (o.batch, o.value)
             values (n.batch, n.value);
    end tr_test;
    

    这意味着:

    • 如果有人访问旧表,则他们会看到该视图,而不会看到任何更改
    • 如果有人尝试更新表格的旧方式,那么他们会被抓住并妥善处理
    • 您表格的所有更新都会立即以一致的方式处理

    更重要的是,它会告诉您的用户此表的用途,并开始对其进行正确使用培训。如果可以,请记录仍在尝试插入行的人,并让他们开始合并。

    P.S。请不要提交包裹......

答案 1 :(得分:0)

您的更新应使用整个密钥(批次,ID)。目前,您的DBMS作业将更新多行。

update test1
set value = value_sum
where id = v_id;

变为

update test1
set value = value_sum
where batch = v_batch
and id = v_id;

在插入(而不是行级触发器)之后,可能有更直接的方法使用语句级别触发器。您需要阅读一下以确定这是否有效。

您也可以在插入后运行触发器,但不能在更新后运行。更新不会向表中添加新行,因此不需要合并行。