我有一个描述树的行的表,列是:
(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_type
,tree_table_type
,table_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
成为递归过程的一部分。
答案 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
从类似查询中填充表格。