一、前言
我所在的公司,有的人数据库设计喜欢冗余字段,比如订单中需要存储客户,一般我们只放客户id,但是他不,要把客户名称冗余进去。如果后期来客户名称更改了,这里是需要改过来的。如果用程序来实现同步修改的话,hi比较麻烦。与有的人不喜欢用触发器和存储过程相反,我喜欢用触发器来做这类简单粗暴的事情,简单又不失优雅。N年前,我曾经用存储过程实现过一个接口系统,当时未解决sql server 存储过程递归调用不能超过9层大伤脑筋。这是闲话,下面我们来讲怎么用触发器实现数据的关联修改。
二、配置表
要实现关联修改,首先要知道哪些表引用了哪些表,也就是表与表之间的引用关系,比如上面呢的例子,订单表引用了客户表。这个需要通过一个配置表进行定义。配置表的结构如下:
列 | 说明 | |
1 | table_name | 引用其他表数据的表的名称,比如上面的订单表 |
2 | fk_col | 外键列名称,比如订单中的客户id列 |
3 | fk_name_col | 引用数据的名称列,比如订单中的客户名称列 |
4 | fkref_table | 引用的表名称,比如上例中的客户表 |
5 | cond_expr | 修改的附件条件,比如只修改当年的数据,历史年度的维持不动 |
6 | enable | 本条配置是否生效,可以修改为0,不启用本条配置 |
举个例子:
insert into phs_dateref_rel( table_name, fk_col, fk_name_col fkref_table ) values( 'sd_order', 'cust_id', 'cust_name', 'md_customer' );
以上的配置信息,定义了 sd_order 订单表的 cust_id 引用了 md_customer 客户表 的id,cust_name 是客户的名称。(本文只处理 id-name 这种简单的引用关系,其他的雷同)。
三、触发器
我们在md_customer上实现一个触发器,当客户名称修改时,同步修改订单表中的客户名称。
1 create or replace trigger tri_update_ref_name 2 after update on md_customer 3 for each row 4 declare 5 v$sql varchar2(1000) ; 6 begin 7 if updating( 'name') then 8 for r in( select table_name,fk_col, fk_name_col, cond_expr from phs_fkref_relation where UPPER(fkref_table) = 'MD_CUSTOMER') loop 9 v$sql :='update '|| r.table_name ||' set '||r.fk_name_col||' =:1 where '||r.fk_col||' = :2';
10 if ( trim( r.cond_expr ) is not null ) then
11 v$sql := v$sql + ' and ' || trim( r.cond_expr) ;
12 1end if ;
13 execute immediate v$sql using :new.name, :new.id; 14 end loop; 15 end if ; 16 end ;
以上触发器的功能是,当客户名称修改时,他会根据配置表,查找所有引用客户名称的表,将表中的客户名称列的值修改为新的值。
四、总结
以上方案实现了,通过触发器修改关联的数据。比较适合的场景是:
存在大量的这种冗余数据,尤其是历史遗留项目,如果通过代码避免这种冗余或实现同步修改会关联数据,付出大量的成本。
局限:
以上方案只处理了通过id-name这种形式,冗余name这种情况。但是举一反三,通过扩展,他也可以期限其他冗余数据的级联更新,比如客户联系信息等等。
其实解决数据不一致的根本举措还是避免滥用冗余,使用冗余要有原则。文中的方案只是一种外挂式的备选方案。