WHILE 和 FOR 循环

WHILE & FOR LOOP

我在用两种不同的循环方法处理记录时遇到问题,即 WHILE 和 FOR 循环,请参考以下代码

 DECLARE
   TYPE rc_emp_type IS RECORD ( empno NUMBER,ename VARCHAR(30),sal   NUMBER, comm  NUMBER);   
  TYPE rc_emp_tab IS TABLE OF rc_emp_type;
  l_emp_rec rc_emp_tab := rc_emp_tab();
  TYPE rc_emp_calc_type IS RECORD ( empno NUMBER,
                                    ename VARCHAR(30),
                                    sal   NUMBER,
                                    comm  NUMBER,
                                    new_sal NUMBER);
     TYPE rc_emp_calc_tab IS TABLE OF rc_emp_calc_type;
  l_emp_calc_rec rc_emp_calc_tab := rc_emp_calc_tab();
  l_emp_fcalc_rec rc_emp_calc_tab := rc_emp_calc_tab();


  l_idx NUMBER;

  l_start_time TIMESTAMP;
  l_end_time TIMESTAMP;
  l_exe_time TIMESTAMP;
BEGIN
   SELECT empno,ename,sal,comm
   BULK COLLECT INTO l_emp_rec
   FROM emp;
   l_idx := l_emp_rec.FIRST;

   WHILE l_idx IS NOT NULL
   LOOP
     l_emp_calc_rec.EXTEND;
     l_emp_calc_rec(l_emp_calc_rec.LAST).empno := l_emp_rec(l_idx).empno;
     l_emp_calc_rec(l_emp_calc_rec.LAST).ename := l_emp_rec(l_idx).ename;
     l_emp_calc_rec(l_emp_calc_rec.LAST).sal := l_emp_rec(l_idx).sal;
     l_emp_calc_rec(l_emp_calc_rec.LAST).comm := l_emp_rec(l_idx).comm;
     l_emp_calc_rec(l_emp_calc_rec.LAST).new_sal := NVL(l_emp_rec(l_idx).sal,0) + NVL(l_emp_rec(l_idx).comm,0);

     l_idx := l_emp_rec.NEXT(l_idx);
   END LOOP;
      FOR l_idx  IN l_emp_rec.FIRST .. l_emp_rec.LAST
    LOOP
     l_emp_fcalc_rec.EXTEND;
     l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).empno := l_emp_rec(l_idx).empno;
     l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).ename := l_emp_rec(l_idx).ename;
     l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).sal := l_emp_rec(l_idx).sal;
     l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).comm := l_emp_rec(l_idx).comm;
     l_emp_fcalc_rec(l_emp_fcalc_rec.LAST).new_sal := NVL(l_emp_rec(l_idx).sal,0) + NVL(l_emp_rec(l_idx).comm,0);
   END LOOP;
  END;

上面这两个程序中哪个是循环的有效方式

Out of these two above procedure which is the efficient way of looping

在处理 collection 时,For Loop 有时会在 collection 为 Sparse 时抛出错误。在这种情况下,使用 WHILE LOOP 是有益的。两种循环机制在性能上是相同的。

稀疏:- 如果在定义的最低和最高索引值之间至少有一个未定义的索引值,则 collection 是稀疏的。例如,稀疏 collection 有一个元素分配给索引值 1,另一个元素分配给索引值 10,但两者之间没有任何元素。稀疏的反义词collection是密集的。

使用数字 FOR loop 您的 collection 被密集填充(定义了最低和最高之间的每个索引值) 您想要扫描整个 collection,而不是在满足某些条件时终止扫描

相反,使用 WHILE 循环 您的 collection 可能稀疏 您可以在遍历 collection

中的所有元素之前终止循环

正如@XING 所指出的,区别不在于它们的效率如何,而在于稀疏集合会发生什么。您的示例不会遇到此问题,因为两者都是使用批量收集构建的,因此索引值没有差距。但情况并非总是如此。以下演示显示了它们之间的区别。

declare
    cursor c_numbers is
        select level+23 num   -- 23 has no particulat significence 
          from dual
         connect by level <= 5; -- nor does 5     
    type base_set is table of c_numbers%rowtype; 

    while_set base_set; 
    for_set   base_set;  

    while_index integer; -- need to define while loop index 

begin
  -- populate both while and for arrays. 
  open  c_numbers;
  fetch c_numbers bulk collect into while_set; 
  close c_numbers; 

  open  c_numbers;
  fetch c_numbers bulk collect into for_set; 
  close c_numbers;

  -- Make sparse 
  while_set.delete(3);
  for_set.delete(3);

  -- loop through with while
  while_index := while_set.first;
  while while_index is not null 
  loop
      begin
         dbms_output.put_line('While_Set(' || 
                              while_index  || 
                              ') = '       || 
                              while_set(while_index).num
                              );
         while_index := while_set.next(while_index);
      exception 
      when others then
         dbms_output.put_line('Error in While_Set(' || 
                              while_index           || 
                              ') Message='          ||
                              sqlerrm
                              );
      end;
  end loop;      

  -- loop through with for
  for for_index in for_set.first .. for_set.last 
  loop
      begin
         dbms_output.put_line('For_Set(' || 
                              for_index  || 
                              ') = '     || 
                              for_set(for_index).num
                              );
      exception 
      when others then
         dbms_output.put_line('Error in For_Set(' || 
                              for_index           || 
                              ') Message='        || 
                              sqlerrm
                              );
      end;
  end loop; 

end;

也尝试使用集合定义的 for 循环:

type state_populations_t is table of number index by varchar2(20);
state_populations state_populations_t;

是的,该行在生产代码中并且已经 运行 多年,

如果您知道您的 collection 将是 densely-filled,就像 BULK COLLECT 填充的 collection 的情况一样,我建议您使用数字 FOR 循环。假设 densely-filled 因此在这种情况下最合适。

当您不能 100% 确定 collection 是 densely-filled 时,您应该使用 WHILE 循环和 FIRST-NEXT 或 LAST-PRIOR 方法来遍历collection.

您可能会争辩说,您还不如一直使用 WHILE 循环。性能会很好,内存消耗没有什么不同....但是:你可能 "hide" 这样会出错。如果 collection 是 应该 是密集的,但事实并非如此,你永远不会知道。

最后,有一种方法可以使 WHILE 循环比 FOR 循环表现更好:如果您的 collection 非常稀疏(例如,仅在索引值 -1M、0、 1M、2M、3M 等),FOR 循环将引发大量 NO_DATA_FOUND 异常。处理并继续所有这些异常将使循环执行非常缓慢。