如何使用2级父子行填充TABLE OF RECORDS

时间:2017-02-08 13:21:06

标签: oracle recursion plsql record

我有一个描述树的行的表,列是: (child_num列是唯一的,用作 主键

TABLE items_tree (
      child_num        number, 
      parent_ref       varchar2(10), 
      child_ref        varchar2(10)
);

TYPE item_rec_type IS RECORD (
      item_id          NUMBER,
      spaces_number    NUMBER,
      parent_ref       VARCHAR2(10),
      child_ref        VARCHAR2(10)
);

TYPE tree_table_type IS TABLE OF item_rec_type%ROWTYPE
    INDEX BY BINARY_INTEGER;

table_tree      tree_table_type;   -- table of all items

items_tree的示例数据(child_num的值不相关):

parent_ref   child_ref
---------------------------
null         Abraham
Abraham      Isaac
Abraham      Ishmael
Isaac        Jakob
Jakob        Yehuda
Jakob        Josef
Jakob        Benjamin
Yehuda       David
Josef        Efraim
Josef        Menashe
David        Solomon
Solomon      Isaiah
Isaiah       Jeremiah

我需要填充table_tree表中的items_tree条记录。为此,我使用的是一个包,其中定义了item_rec_typetree_table_typetable_tree以及两个过程:print_tree,用于检索ROOT个项目在树中,启动该过程并从table_tree打印树。第二个过程get_items_by_parent_recursively是递归过程,它检索所有项目或父项目,例如调用get_items_by_parent_recursively('Abraham')会将 Isaac Ishmael 添加到table_tree

光标在包体中声明:

CURSOR get_children_cur(c_parent in varchar2(10)) 
IS
  SELECT      parent_ref, child_ref
     FROM     items_tree
     WHERE    parent_ref = c_parent
     ORDER BY 1, 2;

get_items_by_parent_recursively中用于检索父项的项目的代码:

procedure get_items_by_parent_recursively(p_parent in VARCHAR2(10), p_spaces_number in NUMBER ) 
AS
  l_spaces_number  NUMBER := 0;
  l_child          VHARCHAR2(10);
  l_parent         VHARCHAR2(10);
BEGIN
  l_spaces_number := p_spaces_number + 3;

  OPEN get_children_cur(p_parent);
  LOOP
     FETCH get_children_cur INTO l_parent, l_child;   
     EXIT WHEN get_children_cur%NOTFOUND;

     IF (l_child is not null) THEN
        v_row_number := v_row_number + 1;
        tree_table(v_row_number).row_num       := v_row_number; 
        tree_table(v_row_number).spaces_number := l_spaces_number; 
        tree_table(v_row_number).parent_ref    := l_parent; 
        tree_table(v_row_number).child_ref     := l_child; 

        -- Calling procedure recursively
        get_items_by_parent_recursively( l_child, l_spaces_number );
     END IF;

  END LOOP;
  CLOSE get_children_cur;

EXCEPTION
  WHEN CURSOR_ALREADY_OPEN THEN  
     DBMS_OUTPUT.put_line('   Exception -- CURSOR_ALREADY_OPEN');
  WHEN INVALID_CURSOR THEN  
     DBMS_OUTPUT.put_line('   Exception -- INVALID_CURSOR');
  WHEN INVALID_NUMBER THEN  
     DBMS_OUTPUT.put_line('   Exception -- INVALID_NUMBER');
  WHEN NO_DATA_FOUND THEN  
     DBMS_OUTPUT.put_line('   Exception -- NO_DATA_FOUND');
  WHEN PROGRAM_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- PROGRAM_ERROR');
  WHEN ROWTYPE_MISMATCH THEN
     DBMS_OUTPUT.put_line('   Exception -- ROWTYPE_MISMATCH');
  WHEN STORAGE_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- STORAGE_ERROR');
  WHEN TOO_MANY_ROWS THEN  
     DBMS_OUTPUT.put_line('   Exception -- TOO_MANY_ROWS');
  WHEN VALUE_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- VALUE_ERROR');

END get_items_by_parent_recursively;

运行此程序我得到例外:CURSOR_ALREADY_OPEN

我已经搜索了一个回复,但没有一个接近我需要的。我会很感激任何想法。

我将尝试使光标get_children_cur成为递归过程的一部分。

1 个答案:

答案 0 :(得分:1)

正如@vmachan所说,您需要将游标定义移动到过程中。虽然您在程序包规范或正文中有它,但在程序之外,它有一个对会话是全局的实例。对程序的每次调用都会尝试打开相同的光标;来自print_tree的初始通话成功,你的桌子上装满了'亚伯拉罕';但随后递归调用尝试重新打开它并获取CURSOR_ALREADY_OPEN异常,然后停止。

将光标移动到过程中意味着每个调用/迭代都有自己的独立副本。清理命名和其他各种问题,然后就可以了:

procedure get_items_by_parent(p_parent in VARCHAR2, p_spaces_number in NUMBER) 
AS
  l_spaces_number  NUMBER := 0;
  l_child          VARCHAR2(10);
  l_parent         VARCHAR2(10);

  CURSOR get_children_cur(p_parent in varchar2) IS
  SELECT parent_item, child_item
  from items_tree
  where parent_item = p_parent
  or (p_parent is null and parent_item is null);

BEGIN
  l_spaces_number := p_spaces_number + 3;

  OPEN get_children_cur(p_parent);
  LOOP
     FETCH get_children_cur INTO l_parent, l_child;   
     EXIT WHEN get_children_cur%NOTFOUND;

     IF (l_child is not null) THEN
        v_row_number := v_row_number + 1;
        table_tree(v_row_number).item_id         := v_row_number; 
        table_tree(v_row_number).spaces_number   := l_spaces_number; 
        table_tree(v_row_number).parent_item_ref := l_parent; 
        table_tree(v_row_number).item_ref        := l_child; 

        -- Calling procedure recursively
        get_items_by_parent( l_child, l_spaces_number );
     END IF;

  END LOOP;
  CLOSE get_children_cur;

EXCEPTION
  WHEN CURSOR_ALREADY_OPEN THEN  
     DBMS_OUTPUT.put_line('   Exception -- CURSOR_ALREADY_OPEN');
  WHEN INVALID_CURSOR THEN  
     DBMS_OUTPUT.put_line('   Exception -- INVALID_CURSOR');
  WHEN INVALID_NUMBER THEN  
     DBMS_OUTPUT.put_line('   Exception -- INVALID_NUMBER');
  WHEN NO_DATA_FOUND THEN  
     DBMS_OUTPUT.put_line('   Exception -- NO_DATA_FOUND');
  WHEN PROGRAM_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- PROGRAM_ERROR');
  WHEN ROWTYPE_MISMATCH THEN
     DBMS_OUTPUT.put_line('   Exception -- ROWTYPE_MISMATCH');
  WHEN STORAGE_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- STORAGE_ERROR');
  WHEN TOO_MANY_ROWS THEN  
     DBMS_OUTPUT.put_line('   Exception -- TOO_MANY_ROWS');
  WHEN VALUE_ERROR THEN  
     DBMS_OUTPUT.put_line('   Exception -- VALUE_ERROR');

END get_items_by_parent;

根据您描述的内容发明print_tree

procedure print_tree is
begin
  get_items_by_parent(null, 0);

  for i in 1..table_tree.count loop
    dbms_output.put_line(to_char(table_tree(i).item_id, '99999') || ' '
      || lpad(' ', table_tree(i).spaces_number, ' ')
      || table_tree(i).item_ref);
  end loop;
end print_tree;

...调用现在可以工作,并产生13个缩进记录:

 1    Abraham
 2       Isaac
 3          Jakob
 4             Yehuda
 5                David
 6                   Solomon
 7                      Isaiah
 8                         Jeremiah
 9             Josef
10                Efraim
11                Menashe
12             Benjamin
13       Ishmael

正如@XING所说,你可以通过不同的游标循环更简单地得到相同的结果:

procedure get_items_by_parent(p_parent in VARCHAR2, p_spaces_number in NUMBER) 
AS
  l_spaces_number  NUMBER := 0;

  CURSOR get_children_cur(p_parent in varchar2) IS
  SELECT parent_item, child_item
  from items_tree
  where child_item is not null
  and (parent_item = p_parent
    or (p_parent is null and parent_item is null));

BEGIN
  l_spaces_number := p_spaces_number + 3;

  FOR r IN get_children_cur(p_parent)
  LOOP
    v_row_number := v_row_number + 1;
    table_tree(v_row_number).item_id         := v_row_number; 
    table_tree(v_row_number).spaces_number   := l_spaces_number; 
    table_tree(v_row_number).parent_item_ref := r.parent_item; 
    table_tree(v_row_number).item_ref        := r.child_item; 

    -- Calling procedure recursively
    get_items_by_parent( r.child_item, l_spaces_number );
  END LOOP;
END get_items_by_parent;

甚至:

procedure get_items_by_parent(p_parent in VARCHAR2, p_spaces_number in NUMBER) 
AS
BEGIN
  FOR r IN (
    SELECT parent_item, child_item
    from items_tree
    where child_item is not null
    and (parent_item = p_parent
      or (p_parent is null and parent_item is null)))
  LOOP
    v_row_number := v_row_number + 1;
    table_tree(v_row_number).item_id         := v_row_number; 
    table_tree(v_row_number).spaces_number   := p_spaces_number + 3; 
    table_tree(v_row_number).parent_item_ref := r.parent_item; 
    table_tree(v_row_number).item_ref        := r.child_item; 

    -- Calling procedure recursively
    get_items_by_parent( r.child_item, p_spaces_number + 3 );
  END LOOP;
END get_items_by_parent;

当然,您根本不需要使用PL / SQL或表,您可以使用分层查询:

select rownum, lpad(' ', level * 3, ' ') || child_item as item
from items_tree
start with parent_item is null
connect by parent_item = prior child_item
order siblings by child_num;

    ROWNUM ITEM                                              
---------- --------------------------------------------------
         1    Abraham                                        
         2       Isaac                                       
         3          Jakob                                    
         4             Yehuda                                
         5                David                              
         6                   Solomon                         
         7                      Isaiah                       
         8                         Jeremiah                  
         9             Josef                                 
        10                Efraim                             
        11                Menashe                            
        12             Benjamin                              
        13       Ishmael                                     

但可能这是一个PL / SQL练习。如果您不需要使用递归过程,您仍然可以使用bulk collect从类似查询中填充表格。