ChinaUnix首页 > 精华文章 > Oracle > 正文

[精彩] 能否在表的触发器中当一记录变化修改另一记录


http://www.chinaunix.net 作者:duwenhua  发表于:2004-10-09 16:22:52
发表评论】 【查看原文】 【Oracle讨论区】【关闭

:em02: 各位同仁高手请指教:表A有A_id,u_orig_value,parent_id等字段,parent_id指向表A的另一记录的A_id,
能否用表A的trigger实现---当A的某一条记录的字段u_orig_value发生变化时,相应修改其parent_id指向的记录的u_orig_value ??
我用过trigger,但资源锁定冲突啊!
小弟从网络搜索无所收获,从自己有的几本书也未能找到帮助,同事朋友给的建议就是尽量改用存储过程,存储过程需要调用者啊,而对用户来说多个按钮不知道做啥用的确不能少点不能多点,用户感到不理解。请求高手指教如何解决?不胜感谢!!!!



 duwenhua 回复于:2004-06-09 18:05:13

是不是没有人能解决阿?


 jrlee 回复于:2004-06-09 21:54:09

这样用trigger应该可以的,检查一下你的tigger是不是合理化的修改其相关记录


 binary 回复于:2004-06-10 08:44:58

能否将你初步写的trigger贴上来,大家帮你看看


 hyh1234 回复于:2004-06-10 09:29:23

可能是我反应太迟钝了,看不懂你想实现什么样的操作和你表中各字段的关系。


 duwenhua 回复于:2004-06-10 11:05:51

谢谢各位大侠相助!!!感谢感谢先:)
是这样的:tablename-U_EQUIPMENT_Essence,包括如下字段:equipment_original_value、u_equipment_essence_id、u_main_equipment_essence_id。建的trigger如下:create or replace trigger U_EQUIPMENT_Essence_TRG
  before  update on U_EQUIPMENT_Essence
  for each row

begin
update u_equipment_essence set equipment_original_value =equipment_original_value + :new.equipment_original_value where u_equipment_essence_id = :new.u_main_equipment_essence_id
and u_assembtype ='M';
end ;
报错如下:ORA-04091: 表 CQSBGL.U_EQUIPMENT_ESSENCE 发生了变化,触发器/函数不能读
ORA-06512: 在"CQSBGL.U_EQUIPMENT_ESSENCE_AFTER_TRG
请大家相助


 duwenhua 回复于:2004-06-10 11:18:15

接上再说明一下,u_equipment_essence_id 为表u_equipment_essence的主键,u_main_equipment_essence_id用作关联的,u_assembtype  表示设备类别的,u_assembtype='M'表示主机 u_assembtype='P'表示部件,部件的u_main_equipment_essence_id字段存的就是主机的u_equipment_essence_id的值,当部件的equipment_original_value 增加或减少时,主机的也要相应增加或减少
在触发器中的本意是当部件的equipment_original_value 变化就修改其主机的equipment_original_value 。当前只锁定部件的记录行啊,为何修改主机会报 触发器/函数不能读 呢?


 duwenhua 回复于:2004-06-10 11:32:52

我也试过用表层触发器,但表层是不能用:new或:old的阿,就是说我没办法找到当前修改的行,那么我只好写成如下:
Create Or Replace Trigger U_Equipment_Essence_After_Trg
  after  update 
  on u_equipment_essence 
   
declare 
cursor main_equipment is
 select u_equipment_essence_id from u_equipment_essence 
where u_assembtype ='M';
begin
 for p1 in main_equipment loop
 update u_equipment_essence es set 
 es.equipment_original_value = (select sum(equipment_original_value) from u_equipment_essence where u_main_equipment_essence_id =es.u_equipment_essence_id and u_assembtype='P'),
 es.month_depreciation_value = (select sum(month_depreciation_value) from u_equipment_essence where u_main_equipment_essence_id =es.u_equipment_essence_id and u_assembtype='P'),
 es.year_depreciation_value = (select sum(year_depreciation_value) from u_equipment_essence where u_main_equipment_essence_id =es.u_equipment_essence_id and u_assembtype='P'),
 es.u_ljzj = (select sum(u_ljzj) from u_equipment_essence where u_main_equipment_essence_id =es.u_equipment_essence_id and u_assembtype='P')
 where es.u_equipment_essence_id =p1.u_equipment_essence_id;
 end loop;

end U_Equipment_Essence_After_Trg;
这样,运行时报错如下:ORA-00036: 超过递归 SQL (50) 级的最大值
ORA-06512: 在"CQSBGL.U_EQUIPMENT_ESSENCE_AFTER_TRG"


 binary 回复于:2004-06-10 11:41:49

试试

.........
before update on U_EQUIPMENT_Essence 
for each row 

[color=red]
DECLARE
   PRAGMA AUTONOMOUS_TRANSACTION;
[/color]

begin 
update u_equipment
..........

应该没有问题了


 duwenhua 回复于:2004-06-10 11:42:39

如下的也报错:ORA-04091: 表 CQSBGL.U_EQUIPMENT_ESSENCE 发生了变化,触发器/函数不能读

create or replace trigger U_EQUIPMENT_Essence_TRG 
before update 
of equipment_original_value
on U_EQUIPMENT_Essence 

for each row 
when (new.u_assembtype ='P')
begin 
update u_equipment_essence set 
equipment_original_value =equipment_original_value + :new.equipment_original_value 
where u_equipment_essence_id = :new.u_main_equipment_essence_id 
and u_assembtype ='M'; 

end U_EQUIPMENT_Essence_TRG;


 duwenhua 回复于:2004-06-10 11:47:40

按光明使者老兄的改了,如下:
create or replace trigger U_EQUIPMENT_Essence_TRG1 
before update 

on U_EQUIPMENT_Essence 

for each row 

declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin 
update u_equipment_essence set 
equipment_original_value =equipment_original_value + :new.equipment_original_value 
where u_equipment_essence_id = :new.u_main_equipment_essence_id 
and u_assembtype ='M'; 

end U_EQUIPMENT_Essence_TRG1;
但还不行:ORA-00060: 等待资源时检测到死锁
ORA-06512: 在"CQSBGL.U_EQUIPMENT_ESSENCE_TRG1", line 4


 duwenhua 回复于:2004-06-10 11:49:07

我变动了一下,如下:
create or replace trigger U_EQUIPMENT_Essence_TRG1 
before update 
of equipment_original_value
on U_EQUIPMENT_Essence 

for each row 
when (new.u_assembtype ='P')

declare
PRAGMA AUTONOMOUS_TRANSACTION;

begin 
update u_equipment_essence set 
equipment_original_value =equipment_original_value + :new.equipment_original_value 
where u_equipment_essence_id = :new.u_main_equipment_essence_id 
and u_assembtype ='M'; 

end U_EQUIPMENT_Essence_TRG1;
报错是:ORA-06519: 检测到活动的自治事务处理,已经回退
ORA-06512: 在"CQSBGL.U_EQUIPMENT_ESSENCE_TRG1", line 10


 duwenhua 回复于:2004-06-10 12:09:18

请各位同仁高手发表各种看法见解啊,我是钻破头了啊!有哪位好心人提供专门讲解(oracle) trigger的资料、书籍也好,我去找办法。谢谢大家!


 binary 回复于:2004-06-10 12:49:52

这种写法的trigger,需要commit,你再试试


 追风篮人 回复于:2004-08-10 09:37:42

我也碰到了这个问题,请问这个问题最后解决了吗?


 sidt 回复于:2004-08-10 14:55:29

建议对需要实现的业务再次分析,使之能够更便于实现。
对这种会接连触发的操作,一定要十分谨慎,否则避免触发器的更新操作再次触发了触发器的操作。在触发器中造成死循环。


 追风篮人 回复于:2004-08-10 21:55:05

其实我个人觉得这个问题理论上应该可以解决。
对一个字段的UPDATE操作做触发器监视,触发器中在修改该字段的同时修改另一个触发器不做监视的字段,应该不会再次触发触发器,不会陷入死循环。
不知道是否会有这种比较直接的解决办法。
现在只好利用中间表记录修改情况,再借助后台存储过程的定时执行来更新源表的字段。。。


 byheart 回复于:2004-08-11 18:09:36

You can do:
  1. Rename your table with another name;
  2. Create a view for select * from the table with the original name of the table;
  3. Create a instead of trigger on the view, in the view you can access and update yur base table.


Example of instead of trigger for this case:

[ Code Start ]
create table t1(
  id        number(6)    primary key
 ,pid       number(6)
 ,value     number(15,2)
 ,f1        varchar2(10)
 ,f2        varchar2(20)
);


create or replace view t1_v as select * from t1;

create or replace trigger bug_t1_v
instead of update on t1_v
for each row
declare
  procedure update_parents(i_id in number, i_value in number);
  procedure update_parents(i_id in number, i_value in number) is
  begin
    declare
      l_pid t1.pid%type;
    begin
      select pid into l_pid
        from t1
       where id = i_id;
      if l_pid <>; 0 then 
        update t1 set value = nvl(value,0) + nvl(i_value,0)
         where id = l_pid;
        update_parents(l_pid, i_value);
      end if;
    exception
      when no_data_found then
        null; 
    end;       
  end update_parents;
begin
  --
  -- Update Value Field for current record and Parent records 
  --
  if nvl(:new.value,0) - nvl(:old.value,0) <>; 0 then
     update t1 set value = value + nvl(:new.value,0) - nvl(:old.value,0)
      where id = :new.id;
     update_parents(:new.id, nvl(:new.value,0) - nvl(:old.value,0)); 
  end if;
  --
  -- Update Others Fields
  --
  update t1 set f1 = :new.f1
               ,f2 = :new.f2
   where id = :new.id;
end;
 
--
-- Testing
--
-- With this view: t1_v
--
begin
  for i in 1..50 loop
    Insert into t1_v values(i, i-1, 0, '', '');
  end loop;
end;
/

delete from t1_v;

begin
  for i in 1..50 loop
    Insert into t1_v values(i, i-1, 0, '', '');
  end loop;
end;
/

update t1_v set f1 = 'TEST' where id = 49;


update t1_v set value = value + 5 , f1 = 'AA', F2 = 'BB'
where id = 50;


[ Code End ]


 追风篮人 回复于:2004-08-11 21:57:16

高手,GOOD IDEA!
这个主意不错,可以借鉴一下,明天去单位试试。
利用这个思想,稍微做一下改动。
源表因为是业务表,不宜做改动,触发器还是建在源表上为好,另外建一个同源视图,赋予它相应的UPDATE权限,在该触发器中再对视图做UPDATE操作更新视图的相应其它字段。。。
还不知道这种方法是否可行,明天试了再回复。。。


 byheart 回复于:2004-08-12 12:47:26

引用:原帖由 "追风篮人" 发表:
高手,GOOD IDEA!
这个主意不错,可以借鉴一下,明天去单位试试。
利用这个思想,稍微做一下改动。
源表因为是业务表,不宜做改动,触发器还是建在源表上为好,另外建一个同源视图,赋予它相应的UPDATE权限,在?.........



应该是不行的. 

触发器建在原表上, 触发器执行时, 原表及其视图是不可见的(MUTATING).

所以我给的解决方案中建议: 
  1. 改原表名; 2. 用原表名建视图; 3. 建触发器在视图上.


 追风篮人 回复于:2004-08-12 13:01:40

是啊,试过了,仍然是同样的错误,的确是不行的;
在源表上建的触发器代码中无法修改源表,提示源表已被修改,无法对源表做更新操作。触发器代码中对视图操作也是实际上也是修改源表,所以也不行。
那么我再试试楼上的办法看看。。。


 rollingpig 回复于:2004-08-12 13:18:39

From Metalink

Problem Description -------------------
Oracle does not allow you to read a mutating table in a row trigger because if you can read it, the information may be incorrect (not read consistent). If you attempt this, the following error is returned:
ORA-04091
Table %s.%s is mutating, trigger/function may not see it
However, you can perform this operation in a statement trigger.
One way to handle this situation is to use a package PL/SQL table to store ROWIDs of updated records in a row trigger, and reprocess the updated
 records in a statement trigger.
Below is an example.

 Important Note --------------
Note that there are concurrency issues with this if more than one session tries to perform operations simultaneously.
This is not intended as a total solution, but as the framework to help show one option.
 Example Workaround ------------------

create or replace package emp_pkg as
type
emp_tab_type is table of rowid index by binary_integer;
emp_tab
 emp_tab_type;
emp_index binary_integer;
 end emp_pkg;
 /
create or replace trigger emp_bef_stm_all
 before insert or update or delete on emp
 begin
/*
Remember to reset the pl/sql table before each statement
*/
emp_pkg.emp_index := 0;
 end;
 /
create or replace trigger emp_aft_row_all
 after insert or update or delete on emp
 for each row
 begin
/*
Store the rowid of updated record into global pl/sql table
*/
emp_pkg.emp_index := emp_pkg.emp_index + 1;
emp_pkg.emp_tab(emp_pkg.emp_index) := :new.rowid;
 end;
 /


create or replace trigger emp_aft_stm_all
 after
insert or update or delete on emp
 begin
for i in 1 .. emp_pkg.emp_index loop
 /*

 Re-process the updated records.
 There is no restriction here.
 */
 dbms_output.put_line(emp_pkg.emp_tab(i));
end loop;
emp_pkg.emp_index := 0;
 end;
 /
 



 huanggzcn 回复于:2004-10-09 13:10:54

试试我的办法,把for each row删除应该就可以了。


 z-fh 回复于:2004-10-09 16:22:52

用trigger 来改同一表的记录没必要吧!我想trigger 的主要作用就是改别的表(特别是主从表记录)。同一表你在保存时写一下sql:
update A set  parent_id = u_orig_value where A_id = :value ;
update 的条件是你的主键,别忘了记录不存在时 insert into A




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=344222
转载请注明作者名及原文出处