Thomas Zhang的杂货铺
19 06, 2008
编写健壮的PL/SQL代码(四)
作者 tomszrp 13:22 | Permalink 静态链接网址 | Comments 最新回复 (0) | Trackback 引用 (0) | 解决方案

现实生活中,循环的影子无处不在,每天太阳的东升西落,每天的工作、学习、娱乐、休息...,我们总是在不停的重复着昨天、今天、明天.
同样是每天重复的上班、下班,同样 是每天做着相似的事情,走着走着就不一样了:有的人正确的找到了入口,也顺利的进入了下一个入口;有的
人经过更多的努力,也找到了出口,但付出了更多的代价:有的人习惯了现在的模式,就愿意这样不停的重复着,做自己份内的事情,直到有人告诉
他该换换任务了,于是他很乐意的接受了下一次的重复;有的人却一直在原地不停的打转,想找到出口,才发现迷失了方向,陷入了死循环,如果没
有人拉他一般,他必将就那样一路稀里糊涂的继续晕下去,永远找不到路;有的人走着走着,就走到了十字路口,不知道该从哪个方向走。。。

有人说:写程序如写人生,程序也是有生命的,没有生命的程序,是最大的垃圾(文字垃圾)

扯远了,继续回到今天的内容,在这一小节,简单的聊聊大家熟悉的PL/SQL代码中常用的循环控制方式,我个人的主要观点是:在写plsql代码
时,一定要控制上循环的条件和出入口.

循环控制好了,可以有效的提高工作效率(大多时候批量处理总比单个作业高效的多),但循环体内的迭代码也是一个很敏感的区域,代码的
重复运行,会导致问题升级,控制不好将导致严重的后果。

先回顾一下各种循环的用法

先回顾一下各种循环的用法

1)无条件循环

loop

循环体

end loop;

2)while循环
while (循环条件)

loop

循环体

end loop;
3)数字for 循环
for <index> in begin..end loop

循环体

end loop;

4)指针for循环
for <record> in <cursor> loop

循环体

end loop;

 

无条件循环总是要进入循环体,while循环必须要在条件成立的时候才进入循环体,如下面的2段码,他们在功能是等价的:

A:无条件循环

    declare
      v_times number:=1;
    begin
      loop
        if v_times>5 then
           exit;
        end if;   
        dbms_output.put_line('Looped '|| v_times ||' times');
        v_times:=v_times+1;
      end loop;
    end;
    /
    B:while循环
    declare
      v_times number:=1;
    begin
      while v_times<6 loop
         dbms_output.put_line('Looped '|| v_times ||' times');
         v_times:=v_times+1;
      end loop;
    end;
    /

如果要按照次数或记录进行代码处理,我们就需要使用for循环.下面的这个情况,就比较适合使用数字for 循环:

客户提出一个需求,要求把北京地区手机号码号段为1380001尾数为0-1000的记录,凡在朝阳区(编码为BJ.CY.01)的都调拨到海淀区(BJ.HD.01),
如果不在朝阳区就不变,那么我们可以这样实现:

    begin
      for i in 0..1000 loop
          begin
            update gsm_resource 
              set store_id='BJ.HD.01'
              where gsmno='1390001'||to_char(i,'fm0000') and store_id='BJ.CY.01';
          exception 
            when no_data_found then
                 null;
          end;
      end loop;
    end;
    /

:当然了,这样写的后果是会增加一些不必要的update(因为有些记录可能已经不在朝阳区了),但是,这是一个上千万的表,gsmno是主键,

这么做可能效率是相对较好的.

如果客户说,我要把目前在朝阳区手机号码为138?00???88,状态为0(未使用)的号码调拨给海淀区,那么用上面的数字for循环就合适了。因为我们

事先不知道这样的号码有多少.这个时候,就需要考虑指针for循环了.

    begin
      for rec in (select rowid from gsm_resource where  gsmno like '138%00%88' and store_id='BJ.CY.01' and status=0) loop    
          update gsm_resource 
              set store_id='BJ.HD.01'
              where rowid=rec.rowid;   
      end loop;
    end;
    /

以上几个简单的DEMO说明了一下这几种循环的适用场景,算是回顾一下了(大家都比较熟悉,不多唠叨了)

 

本小节我主要想给大家再重申一下使用循环的一个至理格言:one way in - one way out(OWI-OWO),这一原则在结构化编程中大家都是深有体会的,

也非常适合pl/sql循环控制。

 

OWI-OWO的思想非常重要,否则你的代码就会非常难于理解、跟踪、调试或维护。这一点我在以前运维时深有体会,也吃了不少苦头。客户说一个程序在

调用时没有任何异常,但结果就是不对,开发人员面对几千行,若干个package彼此互相调来调去实在找不出原因,我分析了代码,从出现问题的地方开始

调式,发现随着处理条件的不同,在每个procedure中总有4-5个出口,也不记录任何异常的日志.不做跟踪,光从代码上确实很难找到到底是在处理什么数

据的时候从哪个出口退了出去。没办法,根据客户的处理意图和开发人员的控制逻辑,我针对几种类型的数据做了调试,最后跟踪到了“异常”返回的出口,

而这个出口,开发人员认为一般情况下不会出现(他把宝压在了数据必须符合他的初衷的底线上,而实际上,"垃圾"数据是经常可能出现的).最后,开发人

员按照我提供的思路,修改了这段代码,问题得到解决.

看看下面这段代码:

    create or replace procedure relocate_data is
    begin
       for rec in (select rowid,store_id,status from gsm_resource where status<>'9') loop
           if rec.status='6' then
              exit;
           elsif rec.store_id is null then
                 return;
              else
                 update gsm_resource set store_id=store_id||'.02' where rowid=rec.rowid;
           end if;
       end loop;
       return;
    end;
    /
 

这段模拟代码是我以前实际处理过的一个case的缩影,实际上他比我这个复杂了很多倍,上面的代码共有3个出口,我原来处理过的那个比这个出口

还多,中间还 掉用了好多其他procedure,包含了几个goto语句,让你看来就象是走迷宫.

当然了,针对这段demo code,修改起来就比较简单了,我们可以直接在数据收集的时候,就把异常的情况过滤掉。

   create or replace procedure relocate_data is
   begin
     for rec in (select rowid,store_id,status 
                   from gsm_resource 
                   where status not in ('9','6') and store_id is not null)
     loop
          update gsm_resource set store_id=store_id||'.02' where rowid=rec.rowid;       
     end loop;
     return;
    end;
    /   
   下面的这个例子(来自大师Steven Feuerstein的作品)
   CREATE OR REPLACE FUNCTION matching_row (title_list_in IN title_collection_t,
                                            title_in IN VARCHAR2) RETURN PLS_INTEGER
   IS
       l_count PLS_INTEGER;
   begin
       l_count := title_list_in.COUNT;
       for indx in 1 .. l_count loop
          if title_in = title_list_in(indx) then
             return indx;
          elsif indx is null then
                exit;
          end if;
       end loop;
       return null;
   EXCEPTION
       when Exit_Function then return null;
   end;
   /

此过程接收一个集合,并搜索该列表以查找与title_in 中数值相区配的值。使用FOR循环查找该集合中的每一行。但是在第9行,如果找到一个匹配值

,会马上自该函数退出返回。在第11行,如果集合的索引值为NULL(空),那么我就终止该循环。我的程序返回NULL,表明没有找到匹配值.

仅存在一条路径进行该函数(标头)和一条路径进行该循环(FOR语句)。但是,共有三条途径可退出循环(读出集合中的所有路径;找到一个匹配值

并返回;当索引为NULL时退出),还有三条退出该函数的途径(在循环内部返回索引;在可执行部分的末尾返回NULL;在异常部分返回NULL).
现在假定该函数返回一个错误值。为了调试此程序,需要检查三个退出点,并确定哪个点返回了这一错误值及其原因.

 

下面的代码是对这段代码的改进版本,它在结构上更为清楚,并易于管理。请注意,现在采用了WHILE循环,因为我可能希望提早停止该循环。事实上,

如果已经用完了集合中的数值,或者已经找到了一个匹配值(设置return_value 为一个非NULL 索引值),那么该循环将结束.

   CREATE OR REPLACE FUNCTION matching_row (title_list_in IN title_collection_t,
                                            title_in IN VARCHAR2) RETURN PLS_INTEGER
   IS
       l_row PLS_INTEGER;
       return_value PLS_INTEGER;
   begin
       l_row := title_list_in.FIRST;
       while (l_row is not null and return_value is null) loop
          if title_in = title_list_in (l_row) then
             return_value := l_row
          else
             l_row := title_list_in.NEXT (l_row);
          end if;
       end loop;
       return return_value;   
   end;
   /

按照大师们的建议,我们在PL/SQL代码中使用循环时,可以考虑如下的规则,从而写出更加健壮的代码。

1)把 for 循环看作是对任何阅读或运行你的代码的人的承诺。"我将允许FOR循环运行到完成针对IN子句中指定的每个整数或记录的操作。我不会提前

或有条件地终止它的运行". 因此,一个for循环决不应当包括EXIT语句或RETURN语句。
2)while循环 应当只根据直接跟在WHILE关键字之后的布尔表达式来终止。WHILE循环也不应当依靠EXIT或RETURN来停止.最后,当你期望使循环主体

甚至一次都不运行时,则利用WHILE循环。
3)简单循环或者(有可能)无限循环应当利用EXIT 或EXIT WHEN 语句来终止循环的执行。你应当在一个简单循环中不使用RETURN语句;你还应当力
求仅使用一个EXIT语句。而且,如果你看到一个简单循环中甚至没有包含一个EXIT,那么可能会出现一个问题(无限循环)。最后,如果希望你的循环主体

总是执行至少一次,那么采用一个简单循环,并将EXIT语句置于循环主体的底部(这是REPEAT UNTIL在PL/SQL的等价形式).


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
搜索
最新发表
文章分类
文章归档
网站链接
新闻聚合