SAP® HANA Analysis Tool Hierarchy is a special form of hierarchy implementation that uses mathematical sets to provide results fast and with fewer queries, comparing to standard hierarchy recursive implementation.
There is no recursive calling necessary when searching in the hierarchy table. However, there is a need for recalculation when adding values.
It is the implementation of Nested set model
Advantages
- Get the important queries in one select only. No recursive calls are necessary.
Disadvantage
- Some node IDs have to be recalculated when adding or deleting a node, therefore they are not the same over the whole life of the hierarchy.
However, a node name or a new column with the whole life unique identification can be used/added.
If you need to only use the procedures and don’t want to read the whole concept, just jump to the list of all the procedures and copy/paste the needed code.
Part of the solution is also visualization, which will show the hierarchy in the text format:
ROOT +- Field name +- Field name (maybe changed) | +- a formula for the field (for example) +- Field name +- TABLE_FIELD ...
Concept
The principal of the solution is to change a hierarchy into a mathematical set, where all the nodes have borders numbered from left to right increasingly.
For example the following hierarchy:
Can be presented as a mathematical set in the following way:
Where the mathematical set can be then transformed into the table:
Node Name | Left | Right | Level | No. of children example: ((Right – Left) – 1) / 2 |
---|---|---|---|---|
ROOT | 1 | 18 | 1 | ((18 – 1) – 1) / 2 = 8 |
A | 2 | 17 | 2 | ((17 – 2) – 1) / 2 = 7 |
B | 3 | 8 | 3 | 2 |
C | 4 | 7 | 4 | 1 |
D | 5 | 6 | 5 | 0 |
E | 9 | 10 | 3 | 0 |
F | 11 | 16 | 3 | 2 |
G | 12 | 13 | 4 | 0 |
H | 14 | 15 | 4 | 0 |
The big advantage is, that most of the standard search queries can be done in only one single command.
For example:
- Get all children of the node X (also children of the children)
SELECT * FROM hierarchy WHERE Left > "node's X Left" AND Right < "node's X Right";
- Get parent of the node X
SELECT * FROM hierarchy WHERE Left < "node's X Left" AND Right > "node's X Right" AND Level = ("node's X Level" - 1);
- Get next node of the node X
SELECT * FROM hierarchy WHERE Left = "node's X Right" AND Level = "node's X Level";
- No. of children of the node X
SELECT (((Right – Left) – 1) / 2) AS "No. of children" FROM hierarchy WHERE Left = "node's X Left";
Implementation in SAP® HANA Procedures (v.200508)
The implementation has been done by using HANA procedures in HANA 1.0 SPS 12 version, which means it will work also in the HANA 2.x and most likely also in any future versions.
The ARRAY functionality has been used to significantly increase performance. Also, all the operations are done in memory by sharing data between the procedures unless is the hierarchy saved.
The main implementation object is Hierarchy table, consist of the following columns:
Order | Column name | Data type | Dimension | Key | Not null |
---|---|---|---|---|---|
1 | HIERARCHY_ID | VARCHAR | 5000 | X(1) | X |
2 | LEFT | INTEGER | X(2) | X | |
3 | RIGHT | INTEGER | X | ||
4 | LEVEL | INTEGER | X | ||
5 | NODE_NAME | VARCHAR | 255 | ||
6 | NODE_CONTENT | NCLOB | |||
7 | NODE_DETAIL | INTEGER | X |
drop table "PMYNARIK".HAT_HIERARCHY; create column table "PMYNARIK".HAT_HIERARCHY( "HIERARCHY_ID" VARCHAR(5000) not null, "LEFT" INTEGER not null, "RIGHT" INTEGER not null, "LEVEL" INTEGER not null, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER not null, PRIMARY KEY ("HIERARCHY_ID", "LEFT") );
As a default, the Hierarchy table should consist of the top node: ROOT, with the following default values (default output after the hierarchy initialization):
HIERARCHY_ID | LEFT | RIGHT | LEVEL | NODE_NAME | NODE_DETAIL |
---|---|---|---|---|---|
<hierarchy ID> | 1 | 2 | 1 | ROOT | 0 |
The HANA Procedures
The logic is implemented in the following procedures, where kind of the OOP approach has been followed.
How-To implement the procedures into the system
Simply create procedures in the proper order as presented below in the following subchapters.
How-To use the procedures
To start using the solution, run the initialization of a hierarchy first, please:
CREATE PROCEDURE... declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); call "PMYNARIK".HAT_AR_HIERARCHY_INIT(lt_hierarchy); ... END;
To add a new node(s), please use the following example:
CREATE PROCEDURE... declare lv_node_id integer; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lt_visualization table ( "VISUALIZATION" varchar(1000), "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Initialization call "PMYNARIK".HAT_AR_HIERARCHY_INIT(lt_hierarchy); -- Add node. Parent node in ROOT, therefore the ID = 1. Node details are not set, therefore NODE_DETAIL = 0 call "PMYNARIK".HAT_AR_HIERARCHY_ADD_NODE(:lt_hierarchy, 1, 'Node A', 0, lt_hierarchy, lv_node_id); -- Add child node to node A. Node details are not set, therefore NODE_DETAIL = 0 call "PMYNARIK".HAT_AR_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'Node B', 0, lt_hierarchy, lv_node_id); -- Visualize the hierarchy call "PMYNARIK".HAT_AR_HIERARCHY_VISUALIZE(:lt_hierarchy, lt_visualization); select * from :lt_visualization where "LEFT" is not null order by "LEFT"; ... END;
More examples, how to use the procedures, can be found in the optional procedure: HAT_AR_HIERARCHY_TEST.
The List of all the procedures
01 HAT_HIERARCHY Tables
In case you would like to save the created hierarchy, you can use the following table. This is not mandatory.
drop table "PMYNARIK".HAT_HIERARCHY; create column table "PMYNARIK".HAT_HIERARCHY( "HIERARCHY_ID" VARCHAR(5000) not null, "LEFT" INTEGER not null, "RIGHT" INTEGER not null, "LEVEL" INTEGER not null, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER not null, PRIMARY KEY ("HIERARCHY_ID", "LEFT") );
02 HAT_HIERARCHY_INIT
Initialize the ROOT node for the hierarchy. This is the mandatory function!
DROP procedure "PMYNARIK".HAT_HIERARCHY_INIT; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_INIT ( out out_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER ) ) language sqlscript sql security invoker as BEGIN declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(255) array; declare la_node_content nclob array; declare la_node_detail integer array; -- Set default la_left[1] = 1; la_right[1] = 2; la_level[1] = 1; la_node_name[1] = 'ROOT'; la_node_content[1] = ''; la_node_detail[1] = 0; -- Output out_hierarchy = UNNEST(:la_left, :la_right, :la_level, :la_node_name, :la_node_content, :la_node_detail) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
03 HAT_HIERARCHY_GET_NODE
Get node based on the node ID (the Left column in the hierarchy table). To get node based on its node name, please use the procedure: Get Nodes. The main difference is, that node ID is unique but the node name is not.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_NODE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_NODE ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_node_id INTEGER, in in_check_existence TINYINT default 1, out out_node table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare lv_left integer default 0; -- Check if node exists if in_check_existence = 1 then select "LEFT" into lv_left from :in_hierarchy where "LEFT" = :in_node_id; end if; -- Get node out_node = select * from :in_hierarchy where "LEFT" = :in_node_id; END;
04 HAT_HIERARCHY_ADD_NODE
Add a new node.
DROP procedure "PMYNARIK".HAT_HIERARCHY_ADD_NODE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_ADD_NODE ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_parent_node_id INTEGER, in in_node_name VARCHAR(5000), in in_node_content nclob, in in_node_detail_id INTEGER default 0, out out_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), out out_node_id integer ) language sqlscript sql security invoker as BEGIN declare lv_parent_node_id integer; declare lv_parent_left integer; declare lv_parent_right integer; declare lv_parent_level integer; declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare la_left_tmp integer array; declare la_right_tmp integer array; declare la_level_tmp integer array; declare la_node_name_tmp VARCHAR(5000) array; declare la_node_content_tmp nclob array; declare la_node_detail_tmp integer array; declare lv_int integer; declare lv_left integer; declare lv_card integer; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lv_parent_node_id = :in_parent_node_id; lt_hierarchy = select * from :in_hierarchy;-- <= Important, do not change! /* The lt_hierarchy table must have all the indexes also for the null values Otherwise, the recalculation has to be implemented! */ -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Check lv_card = CARDINALITY(:la_left); if :lv_parent_node_id > lv_card then out_node_id = -1; return; end if; -- Get parent lv_parent_left = :lv_parent_node_id; lv_parent_right = :la_right[:lv_parent_left]; lv_parent_level = :la_level[:lv_parent_left]; -- Change the current values lv_card = CARDINALITY(:la_left); for lv_int in 1 .. :lv_card do if :la_left[:lv_int] is null then continue; end if; if :lv_int > :lv_parent_right then la_left_tmp[:lv_int + 2] = :lv_int + 2; la_right_tmp[:lv_int + 2] = :la_right[:lv_int]; la_level_tmp[:lv_int + 2] = :la_level[:lv_int]; la_node_name_tmp[:lv_int + 2] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int + 2] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int + 2] = :la_node_detail[:lv_int]; if :la_right[:lv_int] >= :lv_parent_right then la_right_tmp[:lv_int + 2] = :la_right[:lv_int] + 2; end if; else la_left_tmp[:lv_int] = :la_left[:lv_int]; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; if :la_right[:lv_int] >= :lv_parent_right then la_right_tmp[:lv_int] = :la_right[:lv_int] + 2; end if; end if; end for; -- Add the new node la_left_tmp[:lv_parent_right] = :lv_parent_right; la_right_tmp[:lv_parent_right] = :lv_parent_right + 1; la_level_tmp[:lv_parent_right] = :lv_parent_level + 1; la_node_name_tmp[:lv_parent_right] = :in_node_name; la_node_content_tmp[:lv_parent_right] = :in_node_content; la_node_detail_tmp[:lv_parent_right] = :in_node_detail_id; -- Output out_hierarchy = UNNEST(:la_left_tmp, :la_right_tmp, :la_level_tmp, :la_node_name_tmp, :la_node_content_tmp, :la_node_detail_tmp) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); out_node_id = :lv_parent_right; END;
05 HAT_HIERARCHY_GET_CHILDREN
Get direct children of the node (on the below level). Or all the children, which means also children of the children in all below levels of the selected node.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_CHILDREN; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_CHILDREN ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_parent_node_id INTEGER, in in_incl_children_of_children TINYINT default 0, out out_children table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare lv_parent_left integer; declare lv_parent_right integer; declare lv_parent_level integer; declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare la_left_tmp integer array; declare la_right_tmp integer array; declare la_level_tmp integer array; declare la_node_name_tmp VARCHAR(5000) array; declare la_node_content_tmp nclob array; declare la_node_detail_tmp integer array; declare lv_parent_node_id integer; declare lv_int integer; --declare lv_left integer; declare lv_card integer; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lt_hierarchy = select * from :in_hierarchy; lv_parent_node_id = :in_parent_node_id; -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Get parent node lv_parent_left = :lv_parent_node_id; lv_parent_right = :la_right[:lv_parent_left]; lv_parent_level = :la_level[:lv_parent_left]; -- Get Children lv_card = CARDINALITY(:la_left); if in_incl_children_of_children = 0 Then for lv_int in :lv_parent_left .. :lv_card do if :la_left[:lv_int] is null then continue; end if; if :lv_int > :lv_parent_left and :la_right[:lv_int] < :lv_parent_right and :la_level[:lv_int] = (:lv_parent_level + 1) then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; end if; end for; else for lv_int in :lv_parent_left .. :lv_card do if :la_left[:lv_int] is null then continue; end if; if :lv_int > :lv_parent_left and :la_right[:lv_int] < :lv_parent_right then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; end if; end for; end if; -- Output out_children = UNNEST(:la_left_tmp, :la_right_tmp, :la_level_tmp, :la_node_name_tmp, :la_node_content, :la_node_detail_tmp) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
06 HAT_HIERARCHY_GET_NODES
Get node(s) based on the node name. Because the node name is not unique, more than one node can be returned.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_NODES; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_NODES ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_node_name VARCHAR(5000), in in_check_existence TINYINT default 1, out out_nodes table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare lv_node_name VARCHAR(255); -- Check if node exists if in_check_existence = 1 then select "NODE_NAME" into lv_node_name from :in_hierarchy where "NODE_NAME" = :in_node_name; end if; -- Get node out_nodes = select * from :in_hierarchy where "NODE_NAME" = :in_node_name; END;
07 HAT_HIERARCHY_GET_PARENTS
Get the direct parent of the node. Or all the parents, which means also parents of the parents in all the above levels of the selected node.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_PARENTS; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_PARENTS ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_child_node_id INTEGER, in in_incl_parents_of_parents TINYINT default 0, -- return also parents of the parent out out_parents table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare lv_child_left integer; declare lv_child_right integer; declare lv_child_level integer; declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare la_left_tmp integer array; declare la_right_tmp integer array; declare la_level_tmp integer array; declare la_node_name_tmp VARCHAR(5000) array; declare la_node_content_tmp nclob array; declare la_node_detail_tmp integer array; declare lv_child_node_id integer; declare lv_int integer; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lt_hierarchy = select * from :in_hierarchy; lv_child_node_id = :in_child_node_id; -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Get child node lv_child_left = :lv_child_node_id; lv_child_right = :la_right[:lv_child_left]; lv_child_level = :la_level[:lv_child_left]; -- Get Parents if :in_incl_parents_of_parents = 0 Then for lv_int in 1 .. (:lv_child_left - 1) do if :la_left[:lv_int] is null then continue; end if; if :lv_int < :lv_child_left and :la_right[:lv_int] > :lv_child_right and :la_level[:lv_int] = (:lv_child_level - 1) then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; end if; end for; else for lv_int in 1 .. (:lv_child_left - 1) do if :la_left[:lv_int] is null then continue; end if; if :lv_int < :lv_child_left and :la_right[:lv_int] > :lv_child_right then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; end if; end for; end if; -- Output out_parents = UNNEST(:la_left_tmp, :la_right_tmp, :la_level_tmp, :la_node_name_tmp, :la_node_content_tmp, :la_node_detail_tmp) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
08 HAT_HIERARCHY_DELETE_NODE
Delete selected node and all its children including children of the children.
After the deletion the recalculation is necessary. In principle, the recalculation is an easy mathematical formula:
- All borders of the nodes on the right side of the deleted node(s) are decreasing by: number of deleted nodes * 2
DROP procedure "PMYNARIK".HAT_HIERARCHY_DELETE_NODE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_DELETE_NODE ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_node_id INTEGER, out out_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN /* Will delete node and all its children! */ declare lv_node_left integer; declare lv_node_right integer; declare lv_delta integer; declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare la_left_tmp integer array; declare la_right_tmp integer array; declare la_level_tmp integer array; declare la_node_name_tmp VARCHAR(5000) array; declare la_node_content_tmp nclob array; declare la_node_detail_tmp integer array; declare lv_int integer; declare lv_left integer; declare lv_card integer; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lt_hierarchy = select * from :in_hierarchy;-- <= Important, do not change! -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Get node lv_node_left = :in_node_id; lv_node_right = :la_right[:lv_node_left]; -- Get all nodes to be deleted => delta lv_delta = :lv_node_right - :lv_node_left + 1; -- Delete the nodes lv_card = CARDINALITY(:la_left); for lv_int in 1 .. :lv_card do if :la_left[:lv_int] is null then continue; end if; -- Left > node_right => Left = Left - delta, Right = Right - delta if :lv_int > :lv_node_right then la_left_tmp[:lv_int - :lv_delta] = :lv_int - :lv_delta; la_right_tmp[:lv_int - :lv_delta] = :la_right[:lv_int] - :lv_delta; la_level_tmp[:lv_int - :lv_delta] = :la_level[:lv_int]; la_node_name_tmp[:lv_int - :lv_delta] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int - :lv_delta] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int - :lv_delta] = :la_node_detail[:lv_int]; -- Else Right > node_right => Right = Right - delta elseif :la_right[:lv_int] > :lv_node_right then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int] - :lv_delta; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; -- Else Right < node_left => copy elseif :la_right[:lv_int] < :lv_node_left then la_left_tmp[:lv_int] = :lv_int; la_right_tmp[:lv_int] = :la_right[:lv_int]; la_level_tmp[:lv_int] = :la_level[:lv_int]; la_node_name_tmp[:lv_int] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_int] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_int] = :la_node_detail[:lv_int]; end if; end for; -- Output out_hierarchy = UNNEST(:la_left_tmp, :la_right_tmp, :la_level_tmp, :la_node_name_tmp, :la_node_content_tmp, :la_node_detail_tmp) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
09 HAT_HIERARCHY_GET_COUNT
Get the size of the hierarchy = maximal LEFT value.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_COUNT; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_COUNT ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), out out_count INTEGER ) language sqlscript sql security invoker as BEGIN declare la_left integer array; la_left = ARRAY_AGG(:in_hierarchy."LEFT"); out_count = CARDINALITY(:la_left); END;
10 HAT_HIERARCHY_UPDATE_NODE
Update node:
- name
- content
- detail
based on node ID.
DROP procedure "PMYNARIK".HAT_HIERARCHY_UPDATE_NODE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_UPDATE_NODE ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_node_id INTEGER, in in_node_name VARCHAR(5000), in in_node_content nclob, in in_node_detail_id INTEGER default 0, out out_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lt_hierarchy = select * from :in_hierarchy;-- <= Important, do not change! -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Update values if :la_left[:in_node_id] is not null then la_node_name[:in_node_id] = :in_node_name; la_node_content[:in_node_id] = :in_node_content; la_node_detail[:in_node_id] = :in_node_detail_id; end if; -- Output out_hierarchy = UNNEST(:la_left, :la_right, :la_level, :la_node_name, :la_node_content, :la_node_detail) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
11 HAT_HIERARCHY_GET_NODES_BY_NODE_DETAIL
Get all nodes based on node detail.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GET_NODES_BY_NODE_DETAIL; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GET_NODES_BY_NODE_DETAIL ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), in in_node_detail INTEGER, in in_check_existence TINYINT default 1, out out_nodes table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare lv_node_detail INTEGER; -- Check if node exists if in_check_existence = 1 then select "NODE_DETAIL" into lv_node_detail from :in_hierarchy where "NODE_DETAIL" = :in_node_detail; end if; -- Get node out_nodes = select * from :in_hierarchy where "NODE_DETAIL" = :in_node_detail; END;
90 HAT_HIERARCHY_SAVE
DROP procedure "PMYNARIK".HAT_HIERARCHY_SAVE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_SAVE ( in in_hierarchy_id VARCHAR(5000), in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), out out_result tinyint ) language sqlscript sql security invoker as BEGIN declare lv_count integer; -- Delete the old version select count(*) into lv_count from "PMYNARIK".HAT_HIERARCHY where "HIERARCHY_ID" = :in_hierarchy_id; if lv_count > 0 then delete from "PMYNARIK".HAT_HIERARCHY where "HIERARCHY_ID" = :in_hierarchy_id; end if; -- Insert values insert into "PMYNARIK".HAT_HIERARCHY select :in_hierarchy_id as "HIERARCHY_ID", "LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL" from :in_hierarchy; -- Get node out_result = 0; END;
91 HAT_HIERARCHY_VISUALIZE
Show the visualization of the hierarchy in the text form.
DROP procedure "PMYNARIK".HAT_HIERARCHY_VISUALIZE; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_VISUALIZE ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), out out_visualization table ( "VISUALIZATION" nclob, "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN declare la_visual nclob array; declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare lv_int integer; declare lv_card integer; declare lv_len integer; declare lv_int_1 integer; declare lv_int_child integer; declare lv_start_child integer; declare lv_end_child integer; declare lv_most_right_child_left integer; declare lv_separator varchar(255) default ' '; declare lv_parent_separator varchar(255) default ''; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Default lt_hierarchy = select * from :in_hierarchy;-- <= Important, do not change! /* The lt_hierarchy table must have all the indexes also for the null values Otherwise, the recalculation has to be implemented! */ -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- array index = LEFT value! -- Process lv_card = CARDINALITY(:la_left); for lv_int in 1 .. :lv_card do if :la_left[:lv_int] is null then continue; end if; -- Init text if :la_visual[:lv_int] is null then la_visual[:lv_int] = ''; end if; -- Calculate spaces for parent's node name lv_parent_separator = ''; lv_len = length(:la_node_name[:lv_int]); for lv_int_1 in 1 .. lv_len do lv_parent_separator = :lv_parent_separator || ' '; end for; -- Update the Current node if :lv_int = 1 then la_visual[:lv_int] = :la_visual[:lv_int] || :la_node_name[:lv_int]; else la_visual[:lv_int] = :la_visual[:lv_int] || '-' || :la_node_name[:lv_int]; end if; -- Start / End child borders lv_start_child = :lv_int + 1; lv_end_child = :la_right[:lv_int] - 1; -- Find the most right children lv_most_right_child_left = 0; if :lv_start_child < :lv_end_child then for lv_int_child in :lv_start_child .. :lv_end_child do if :la_left[:lv_int_child] is null then continue; end if; if :la_right[:lv_int_child] = :lv_end_child then lv_most_right_child_left = :lv_int_child; break; end if; end for; end if; -- Update the children if :lv_start_child < :lv_end_child then -- Go through all the children for lv_int_child in :lv_start_child .. :lv_end_child do if :la_left[:lv_int_child] is null then continue; end if; -- Check if null if :la_visual[:lv_int_child] is null then la_visual[:lv_int_child] = ''; end if; if :la_level[:lv_int_child] = (:la_level[:lv_int] + 1) then -- Update only direct child la_visual[:lv_int_child] = :la_visual[:lv_int_child] || :lv_parent_separator || '+'; elseif :lv_int_child <= :lv_most_right_child_left then -- Update la_visual[:lv_int_child] = :la_visual[:lv_int_child] || :lv_parent_separator || '|' || :lv_separator; else -- Update all the children la_visual[:lv_int_child] = :la_visual[:lv_int_child] || :lv_parent_separator || :lv_separator; end if; end for; end if; end for; -- Show temp table out_visualization = UNNEST(:la_visual, :la_left, :la_right, :la_level, :la_node_name, :la_node_content, :la_node_detail) AS ("VISUALIZATION", "LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;
92 HAT_HIERARCHY_TEST
Create a test hierarchy to present the coding principles.
DROP procedure "PMYNARIK".HAT_HIERARCHY_TEST; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_TEST ( ) language sqlscript sql security invoker as BEGIN /* The code is unnecessary complex to check all functionalities The hiearchy is following: ROOT | +-A | +-B | | | +-C | | | +-D | +-E | +-F | +-G | +-H */ declare lv_node_id integer; declare ls_node table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lt_hierarchy_out table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lt_visualization table ("VISUALIZATION" varchar(1000), "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lt_children table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); -- Initialization call "PMYNARIK".HAT_HIERARCHY_INIT(lt_hierarchy); -- Add node A call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, 1, 'A', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node A based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'A', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node B call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'B', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node B based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'B', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node C call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'C', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node C based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'C', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node D call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'D', '', 0, lt_hierarchy, lv_node_id); --lt_hierarchy_in = select * from :lt_hierarchy_out; -- Get ID of node A based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'A', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node E call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'E', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node A based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'A', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node F call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'F', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node F based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'F', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node G call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'G', '', 0, lt_hierarchy, lv_node_id); -- Get ID of node F based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'F', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Add node H call "PMYNARIK".HAT_HIERARCHY_ADD_NODE(:lt_hierarchy, :lv_node_id, 'H', '', 0, lt_hierarchy, lv_node_id); -- Visualize call "PMYNARIK".HAT_HIERARCHY_VISUALIZE(:lt_hierarchy, lt_visualization); select * from :lt_visualization where "LEFT" is not null order by "LEFT"; -- Get ID of node F based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'F', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Delete node F call "PMYNARIK".HAT_HIERARCHY_DELETE_NODE(:lt_hierarchy, :lv_node_id, lt_hierarchy); --select * from :lt_hierarchy; -- Get ID of node D based on name call "PMYNARIK".HAT_HIERARCHY_GET_NODES(:lt_hierarchy, 'D', 1, ls_node); select "LEFT" into lv_node_id from :ls_node; -- Delete node D call "PMYNARIK".HAT_HIERARCHY_DELETE_NODE(:lt_hierarchy, :lv_node_id, lt_hierarchy); -- Visualize call "PMYNARIK".HAT_HIERARCHY_VISUALIZE(:lt_hierarchy, lt_visualization); select * from :lt_visualization where "LEFT" is not null order by "LEFT"; END;
93 HAT_HIERARCHY_GENERATE_FROM_DATA
In case you have data in the standard recursive hierarchy format with a Node and its Parent, this procedure will create the nested set hierarchy implementation. It is very useful from the performance point of view.
DROP procedure "PMYNARIK".HAT_HIERARCHY_GENERATE_FROM_DATA; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_GENERATE_FROM_DATA ( in in_hierarchy_data table ( "NODE_ID" INTEGER, "PARENT_ID" INTEGER ), out out_hierarchy table ( "NODE_ID" INTEGER, "PARENT_ID" INTEGER, "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER), out out_return tinyint ) language sqlscript sql security invoker as BEGIN /* Input hierarchy data table must: - contain only one main ROOT node with node ID = 1 - two parents cannot have the same child */ declare la_data_node_id integer array; declare la_data_parent_id integer array; declare la_data_left integer array; declare la_data_right integer array; declare la_data_level integer array; declare la_data_children integer array; declare lt_hierarchy_data table ( "NODE_ID" INTEGER, "PARENT_ID" INTEGER ); declare lv_data_int integer; declare lv_data_card integer; declare lv_int integer; declare lv_row integer; declare lv_left integer; declare lv_right integer; declare lv_level integer; declare lv_children integer; declare lv_current_node integer; declare lv_stop tinyint; declare lv_data_parent_left integer; declare lv_data_parent_right integer; declare lv_data_parent_level integer; -- Default out_return = 0; lt_hierarchy_data = select * from :in_hierarchy_data order by "NODE_ID", "PARENT_ID"; -- Set arrays la_data_node_id = ARRAY_AGG(:lt_hierarchy_data."NODE_ID"); la_data_parent_id = ARRAY_AGG(:lt_hierarchy_data."PARENT_ID"); -- Proceed lv_data_card = CARDINALITY(:la_data_node_id); -- Set ROOT node lv_children = :lv_data_card; la_data_left[1] = 1; la_data_level[1] = 1; la_data_right[1] = (:lv_children * 2) + 1; for lv_data_int in 2 .. :lv_data_card do -- Default lv_data_parent_left = 0; lv_data_parent_right = 0; lv_data_parent_level = 0; lv_left = 0; lv_right = 0; lv_level = 0; -- Get data parent node for lv_int in 1 .. :lv_data_card do if :la_data_node_id[:lv_int] = :la_data_parent_id[:lv_data_int] then lv_data_parent_left = :la_data_left[:lv_int]; lv_data_parent_right = :la_data_right[:lv_int]; lv_data_parent_level = :la_data_level[:lv_int]; break; end if; end for; if :lv_data_parent_left = 0 then out_return = 1; -- data error, parent not found. Should not happen! return; end if; -- Set level lv_level = :lv_data_parent_level + 1; -- Get all children for the current node la_data_children = ARRAY(); la_data_children[1] = :la_data_node_id[:lv_data_int]; lv_row = 1; lv_stop = 0; while(:lv_stop = 0) do lv_current_node = :la_data_children[:lv_row]; for lv_int in 1 .. :lv_data_card do if :la_data_parent_id[:lv_int] = :lv_current_node then la_data_children[(CARDINALITY(:la_data_children) + 1)] = :la_data_node_id[:lv_int]; end if; end for; lv_row = :lv_row + 1; if :lv_row > (CARDINALITY(:la_data_children)) then lv_stop = 1; end if; end while; -- Set children lv_children = CARDINALITY(:la_data_children) - 1; -- Is there Left sibling in the given level? for lv_int in 1 .. :lv_data_card do if :la_data_left[:lv_int] = 0 then -- we go one by one from bottom to top -- therefore when Left = 0 we can stop search break; end if; if :la_data_level[:lv_int] = (:lv_data_parent_level + 1) and :la_data_left[:lv_int] > :lv_data_parent_left and :la_data_right[:lv_int] < :lv_data_parent_right and :la_data_right[:lv_int] > :lv_left then lv_left = :la_data_right[:lv_int] + 1; lv_right = :lv_left + (:lv_children * 2) + 1; end if; end for; -- Set the new node if :lv_left = 0 then -- Based on parent la_data_left[:lv_data_int] = :lv_data_parent_left + 1; la_data_right[:lv_data_int] = (:lv_data_parent_left + 1) + (:lv_children * 2) + 1; la_data_level[:lv_data_int] = :lv_level; else -- Based on sibling la_data_left[:lv_data_int] = :lv_left; la_data_right[:lv_data_int] = :lv_right; la_data_level[:lv_data_int] = :lv_level; end if; end for; -- Output out_hierarchy = UNNEST(:la_data_node_id, :la_data_parent_id, :la_data_left, :la_data_right, :la_data_level) AS ("NODE_ID", "PARENT_ID", "LEFT", "RIGHT", "LEVEL"); END;
94 HAT_HIERARCHY_REARRANGE_LEFT
The whole solution is using arrays with indexes with the same value as the Left value. This will create Null values for indexes, which are not used. If the Null values are lost in the table, this procedure will recreate them.
DROP procedure "PMYNARIK".HAT_HIERARCHY_REARRANGE_LEFT; CREATE PROCEDURE "PMYNARIK".HAT_HIERARCHY_REARRANGE_LEFT ( in in_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER), out out_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(5000), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER) ) language sqlscript sql security invoker as BEGIN /* This will recalculate LEFT numbers in case NULL lines are not included It means the IDs will correspond with LEFT */ declare la_left integer array; declare la_right integer array; declare la_level integer array; declare la_node_name VARCHAR(5000) array; declare la_node_content nclob array; declare la_node_detail integer array; declare la_left_tmp integer array; declare la_right_tmp integer array; declare la_level_tmp integer array; declare la_node_name_tmp VARCHAR(5000) array; declare la_node_content_tmp nclob array; declare la_node_detail_tmp integer array; declare lt_hierarchy table ( "LEFT" INTEGER, "RIGHT" INTEGER, "LEVEL" INTEGER, "NODE_NAME" VARCHAR(255), "NODE_CONTENT" nclob, "NODE_DETAIL" INTEGER); declare lv_int integer; declare lv_max integer; declare lv_left integer; -- Default lt_hierarchy = select * from :in_hierarchy order by "LEFT"; -- Set arrays la_left = ARRAY_AGG(:lt_hierarchy."LEFT"); la_right = ARRAY_AGG(:lt_hierarchy."RIGHT"); la_level = ARRAY_AGG(:lt_hierarchy."LEVEL"); la_node_name = ARRAY_AGG(:lt_hierarchy."NODE_NAME"); la_node_content = ARRAY_AGG(:lt_hierarchy."NODE_CONTENT"); la_node_detail = ARRAY_AGG(:lt_hierarchy."NODE_DETAIL"); -- Rearrange select max("LEFT") into lv_max from :lt_hierarchy; for lv_int in 1 .. :lv_max do lv_left = :la_left[:lv_int]; if :lv_left is not null then la_left_tmp[:lv_left] = :lv_left; la_right_tmp[:lv_left] = :la_right[:lv_int]; la_level_tmp[:lv_left] = :la_level[:lv_int]; la_node_name_tmp[:lv_left] = :la_node_name[:lv_int]; la_node_content_tmp[:lv_left] = :la_node_content[:lv_int]; la_node_detail_tmp[:lv_left] = :la_node_detail[:lv_int]; end if; end for; -- Output out_hierarchy = UNNEST(:la_left_tmp, :la_right_tmp, :la_level_tmp, :la_node_name_tmp, :la_node_content_tmp, :la_node_detail_tmp) AS ("LEFT", "RIGHT", "LEVEL", "NODE_NAME", "NODE_CONTENT", "NODE_DETAIL"); END;