受INSTEAD OF触发器

时间:2017-04-21 16:07:03

标签: oracle view plsql triggers

我们使用视图来抽象我们的数据库设计正在改变的事实......旧的遗留代码将所有用户数据转储到一个非规范化表中;新设计在正常设计中将数据分开。由于一些原因(一些是合法的,另一些不是),我们的一些新开发必须针对新数据库完成,因此用户数据必须保持同步,直到新数据库成为用户数据的权威来源。这是一个乱伦乱七八糟的混乱,但这是我坚持的。

我们选择在视图上使用替代触发器来写入两个数据库...唯一的另一个选择是在权威来源的表上写一个触发器,但这可能会引入意想不到的副作用现有的代码。

问题:如果我没有直接在表的更新中包含status_date,则函数updating('status_date')在视图的任何触发器中返回false。但是,替代触发器必须在其更新查询中包含status_date字段,因此它似乎总是“设置”为相同的值。

例如,如果用户的状态发生更改,则当前用户表中的状态日期字段会自动更新(如果更新中未包含该字段):

-- Don't change the status date if a date has been explicitly set
if (not updating('status_date')) then 
   :new.status_date := sysdate;
end if;

如果我直接将表更新为:

update user_status set
   status = 'GONE',
   status_date = to_date('20170101','yyyymmdd')    
where user_id = '123456';

该表将使用2017/01/01作为日期。但是,将日期保留为将触发sysdate。

-- The trigger will set status_date to sysdate
update user_status set
   status = 'GONE'    
where user_id = '123456';

在视图触发器中,我必须更新所有内容:

update user_status set
   status = :new.status,
   status_date = :new.status_date
where user_id = :new.user_id;

但是这导致updating('status_date')总是在user_status表触发器中返回true - 我必须检查值,如果它们是相同的,我必须假设没有设置日期。

-- Don't change the status date if a date has been explicitly set
if (:new.status_date = :old.status_date) then 
   :new.status_date := sysdate;
end if;

在大多数情况下,它的工作方式相同。除非我通过明确设置来失去保持当前日期的能力 - 以下两个查询现在看起来相同,并且两者都将更新状态日期。但是,只有第二个应该:(

-- Not setting the date, trigger will use sysdate
update v_user_status set
   status = 'GONE',
   status_date = status_date     
where user_id = '123456';

-- I'm correcting the status, but I need to keep the original date
update v_user_status set
   status = 'GONE' 
where user_id = '123456';

从视图触发器更新表时,如何保持字段的“未更新”状态?

1 个答案:

答案 0 :(得分:0)

如果还原表触发器以使用not updating(),则可以在视图触发器中使用相同的检查来决定使用哪个值,设置局部变量然后在表更新中使用它:

...
declare
   l_status_date date;
begin
   if (not updating('status_date')) then 
      l_status_date := sysdate;
   else
      l_status_date := :new.status_date;
   end if;
   update user_status set
      status = :new.status,
      status_date = l_status_date
   where user_id = :new.user_id;
end;
/

表触发器总是将其视为更新;但它获得的相同列值与它本来可以获得或生成的相同。当你将它设置为自身时,在表触发器中使用相等检查不起作用,因为当你不想这样时,这仍然是正确的。

设置一些虚拟DDL和DML:

create table user_status (user_id varchar2(7), status varchar2(6), status_date date);

create trigger user_status_trig
before update on user_status
for each row
begin
   -- Don't change the status date if a date has been explicitly set
   if (not updating('status_date')) then 
      :new.status_date := sysdate;
   end if;
end;
/

create view v_user_status as select * from user_status;

create trigger v_user_status_trig
instead of update on v_user_status
declare
   l_status_date date;
begin
   if (not updating('status_date')) then 
      l_status_date := sysdate;
   else
      l_status_date := :new.status_date;
   end if;
   update user_status set
      status = :new.status,
      status_date = l_status_date
   where user_id = :new.user_id;
end;
/

insert into user_status values ('123456', 'NEW', date '2015-01-01');
commit;

直接使用三种方案更新表(每种方案之间的回滚):

update user_status set
   status = 'GONE',
   status_date = status_date
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2015-01-01

update user_status set
   status = 'GONE',
   status_date = to_date('20170101','yyyymmdd')    
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2017-01-01

update user_status set
   status = 'GONE'    
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2017-04-21

通过视图重复相同的场景:

-- Setting the date to itself
update v_user_status set
   status = 'GONE',
   status_date = status_date     
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2015-01-01

-- Setting the date, trigger will use that
update v_user_status set
   status = 'GONE',
   status_date = date '2017-01-01'     
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2017-01-01

-- I'm correcting the status, but I need to keep the original date
update v_user_status set
   status = 'GONE' 
where user_id = '123456';
select * from user_status where user_id = '123456';

USER_ID STATUS STATUS_DAT
------- ------ ----------
123456  GONE   2017-04-21