Thomas Zhang的杂货铺
17 06, 2008
编写健壮的PL/SQL代码(二)
作者 tomszrp 12:27 | Permalink 静态链接网址 | Comments 最新回复 (0) | Trackback 引用 (0) | 解决方案
下面这个例子更有代表性,案例发生的环境与背景还是与上一节所讲的一样,是在一个复杂的综合各种应用的业务系统中。其中某个业务系统中的一张表zrp(结构见后面),存放了公司员工的基本信息,这张表有多个应用模块在访问,其中有一个模块(proc_demo)用来定期将zrp表中的记录的前3个字段抽取出来供另外一个模块使用(这个模块不关zrp结构是否发生变化,永远就提取前3个字段)

备注:我这里为了模拟场景,数据抽取出来仅仅做一个display,用来模拟其他业务。

 

所以,项目一开始的时候,我们的开发人员是这样设计代码的:

第一步:创建一张表zrp,并导入原始的员工信息

create table zrp
( id number,
name varchar2(32),
address varchar2(32)
);
insert into zrp values(1,'Oracle','china beijing');
insert into zrp values(2,'IBM','china beijing');
insert into zrp values(3,'HP','china beijing');
commit;

注:字段ID,NAME,ADDRESS是模块要访问的信息

第二步:定义一个package.

该package包括一个REF CURSOR类型声明和一个函数,函数用来返回zrp表中所有行的所有列.

create or replace package apk_demo IS
TYPE zrp_rc IS REF CURSOR return zrp%ROWTYPE;
function get_info return apk_demo.zrp_rc;
end apk_demo;
/
create or replace package body apk_demo IS
function get_info RETURN apk_demo.zrp_rc IS
v_ret apk_demo.zrp_rc;
begin
open v_ret for select * from zrp;
return v_ret;
end get_info;
end apk_demo;
/

第三步:编写proc_demo模块,实现数据抽取

create or replace procedure proc_demo IS
v_zrp_buf apk_demo.zrp_rc;
v_id number;
v_name varchar2(32);
v_address varchar2(32);
begin
v_zrp_buf := apk_demo.get_info;
loop
fetch v_zrp_buf INTO v_id,v_name,v_address;
exit when v_zrp_buf%NOTFOUND;
dbms_output.put_line(v_id||'#'||v_name||'#'||v_address);
end loop;
close v_zrp_buf;
end;
/

可以看到,在proc_demo模块中对列的信息使用硬编码的形式,这在特定的情况下是要出"问题".(见第一节)

:我这里提到的问题,其实也不能算是特别大的问题,只要你能cover的住就不能叫问题。

 

发布后,模块工作的很好。下面是测试效果

    SQL> create table zrp
      2  ( id number,
      3    name varchar2(32),
      4    address varchar2(32)
      5  );
    
    Table created
    
    SQL> insert into zrp values(1,'Oracle','china beijing');  
    1 row inserted
    
    SQL> insert into zrp values(2,'IBM','china beijing');    
    1 row inserted
    
    SQL> insert into zrp values(3,'HP','china beijing');    
    1 row inserted
    
    SQL> commit;    
    Commit complete
    
    SQL> 
    SQL> create or replace package apk_demo IS
      2       TYPE zrp_rc IS REF CURSOR return zrp%ROWTYPE;
      3       function get_info return apk_demo.zrp_rc;
      4   end apk_demo;
      5  /
    
    Package created
    
    SQL> create or replace package body apk_demo IS
      2      function get_info RETURN apk_demo.zrp_rc IS
      3         v_ret apk_demo.zrp_rc;
      4      begin
      5         open v_ret for select id,name,address from zrp;
      6         return v_ret;
      7      end get_info;
      8   end apk_demo;
      9  /
    
    Package body created
    
    SQL> 
    SQL> create or replace procedure proc_demo IS
      2     v_zrp_buf apk_demo.zrp_rc;
      3     v_id number;
      4     v_name varchar2(32);
      5     v_address varchar2(32);
      6  begin
      7     v_zrp_buf := apk_demo.get_info;
      8     loop
      9       fetch v_zrp_buf INTO v_id,v_name,v_address;
     10         exit when v_zrp_buf%NOTFOUND;
     11         dbms_output.put_line(v_id||'#'||v_name||'#'||v_address);
     12     end loop;
     13     close v_zrp_buf;
     14  end;
     15  /
    
    Procedure created
    
    SQL> set serveroutput on 
    SQL> exec proc_demo;
    
    1#Oracle#china beijing
    2#IBM#china beijing
    3#HP#china beijing
    
    PL/SQL procedure successfully completed
    
    SQL> 

系统运行了半年后,突然有一天,一个客户说,我的另外一个报表模块中,需要提供员工的联系信息,要求在员工信息表(zrp)增加一个字段,存放员工的手机号码。而且,只有我这个一个报表需要这个手机号码字段,不影响其他现有模块。

于是,我们的需求分析人员,把客户的需求反馈到了开发team,而这个开发team是一个不太规范的team,设计人员也没有全局的分析这样一个“微小”的动作会导致什么后果,直接下单子给报表开发小组,修改了报表代码,并通知实施team按照预定的时间实施了该变更。

 

DBA按照change的要求,修改了该表的结构,增加了一列

    SQL> alter table zrp add phone varchar2(11);   
    Table altered
    
    SQL> select * from zrp;
    
            ID NAME          ADDRESS           PHONE
    ---------- ------------- ----------------- --------
             1 Oracle        china beijing     
             2 IBM           china beijing     
             3 HP            china beijing     
    
    SQL> select object_name,object_type,status from user_objects where status='INVALID';
    
    OBJECT_NAME           OBJECT_TYPE         STATUS
    --------------------- ------------------- -------
    APK_DEMO              PACKAGE BODY        INVALID
    APK_DEMO              PACKAGE             INVALID
    PROC_DEMO             PROCEDURE           INVALID
    SQL> alter procedure proc_demo compile;
    
    Warning: Procedure altered with compilation errors
    
    SQL> show error
    Errors for PROCEDURE STUDY.PROC_DEMO:
    
    LINE/COL ERROR
    -------- -----------------------------------------------------------------------
    9/6      PLS-00394: wrong number of values in the INTO list of a FETCH statement
    9/6      PL/SQL: SQL Statement ignored
    
    SQL> 

接着,DBA发现,系统中有一些依赖表zrp的对象invalid了,然后他尝试去compile 他们,但他发现有几个对象始终无法compile成功。很明显,我上面的函数get_info在返回的时候是zrp的一行的所有列,而之前因为特定的业务需求,我指定了列名,同时一个proc_demo在fetch的时候,在修改了结构,必须要包含4个变量,而现在into到了3个变量中。所以他们变成了invalid.

 

这个时候,报表的那个及其他模块均测试OK了。就剩这2个东西编不过去了,没办法,只能叫开发人员过来调整了,回退是不可能的,客户要求今天必须上。于是一通电话后(在一个走流程管理和版本控制相对严格的的公司里,这个协调是个头疼的事儿),开发人员气喘吁吁的赶到了客户现场,分析了问题后修改并重新发部了该脚本。

 

这样的场景,不知道大家遇到的多不多,我想做运维但每权修改代码的DBA还是比较有体会的。

那么,这样的问题,我们在设计pl/sql代码的时候如何避免呢?

其实Oracle对这个问题有一个很简单的解决方案:不要使用单个变量的列表,而采用记录结构

下面是针对这个问题的修改方案:

    create or replace package body apk_demo IS
        function get_info RETURN apk_demo.zrp_rc IS 
           v_ret apk_demo.zrp_rc; 
        begin 
           open v_ret for select * from zrp;
           return v_ret;
        end get_info; 
     end apk_demo;
    /
    
    create or replace procedure proc_demo IS 
       v_zrp_buf apk_demo.zrp_rc; 
       v_zrp_row zrp%rowtype;   
    begin
      v_zrp_buf := apk_demo.get_info;
      loop
        fetch v_zrp_buf INTO v_zrp_row;
        exit when v_zrp_buf%NOTFOUND;
        dbms_output.put_line(v_zrp_row.id||'#'||v_zrp_row.name||'#'||v_zrp_row.address);  --模拟数据处理
      end loop;
      close v_zrp_buf;
    end;
    /
    下面看看这样修改后的效果:
     SQL> create or replace package body apk_demo IS
      2      function get_info RETURN apk_demo.zrp_rc IS
      3         v_ret apk_demo.zrp_rc;
      4      begin
      5         open v_ret for select * from zrp;
      6         return v_ret;
      7      end get_info;
      8   end apk_demo;
      9  /
    
    Package body created
    
    SQL> create or replace procedure proc_demo IS
      2     v_zrp_buf apk_demo.zrp_rc;
      3     v_zrp_row zrp%rowtype;
      4  begin
      5    v_zrp_buf := apk_demo.get_info;
      6    loop
      7      fetch v_zrp_buf INTO v_zrp_row;
      8      exit when v_zrp_buf%NOTFOUND;
      9      dbms_output.put_line(v_zrp_row.id||'#'||v_zrp_row.name||'#'||v_zrp_row.address);
     10    end loop;
     11    close v_zrp_buf;
     12  end;
     13  /
    
    Procedure created
    
    SQL> set serveroutput on
    SQL> exec proc_demo
    
    1#Oracle#china beijing
    2#IBM#china beijing
    3#HP#china beijing
    
    PL/SQL procedure successfully completed
    下面的代码用来再次模拟增加的字段支撑其他功能,看看对proc_demo模块是否有影响:
    SQL> alter table zrp add zipcode varchar2(6);
    
    Table altered
    
    SQL> alter package apk_demo compile body;
    
    Package body altered
    
    SQL> alter procedure proc_demo compile;
    
    Procedure altered
    
    SQL> exec proc_demo
    
    1#Oracle#china beijing
    2#IBM#china beijing
    3#HP#china beijing
    
    PL/SQL procedure successfully completed
    
    SQL>   
    OK,到现在,proc_demo是不是显得健壮一些了呢?

Comments
博客日历
« 八月 2008 »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
搜索
最新发表
文章分类
文章归档
网站链接
新闻聚合