2017-02-08 39 views
0

У меня есть таблица с строк описания дерева, столбцы: (колонка child_num уникальна и используется в качестве первичного ключа)Как заполнить таблицу записей с рядами 2 уровня родитель-ребенок

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') добавит Исаак и 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

Я думаю, что курсор должен быть объявлен внутри тела процедуры, я не вижу объявления. Вы пробовали это? – vmachan

+0

Так же, как и не указывая курсор, ваш код недействителен различными способами (опечатка 'vharchar', включая размер в формальном определении параметра, несогласованные имена и тип данных - даже название процедуры слишком велико). [Полный и рабочий пример] (http://stackoverflow.com/help/mcve) был бы намного более полезен, как и ожидаемый результат. Вам действительно нужен PL/SQL для этого - кажется, что-то вы можете сделать с иерархическим запросом или рекурсивным CTE, но зависит от того, какой результат вы хотите ... –

+0

Указанный вами курсор также недействителен. Имена столбцов не соответствуют определению вашей таблицы, вы снова ограничили размер параметра и откуда «p_child»? И где 'p_parent', исходящий из вызова open-cursor в процедуре, - это должно быть' l; _parent' или, более вероятно, 'l_child'? Я могу догадаться, что вы пытаетесь сделать и написать что-то для этого; но это возвращается к тому, почему вы используете PL/SQL вообще ... –

ответ

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.