一、前言
我所在的公司,有的人數據庫設計喜歡冗余字段,比如訂單中需要存儲客戶,一般我們只放客戶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這種情況。但是舉一反三,通過擴展,他也可以期限其他冗余數據的級聯更新,比如客戶聯系信息等等。
其實解決數據不一致的根本舉措還是避免濫用冗余,使用冗余要有原則。文中的方案只是一種外掛式的備選方案。