突然發現自己以前常用的parent_id ,node_id這種簡單直觀的樹形結構設計效率很低,數據量一大,就需要不停迭代尋找節點,於是這幾天學習了新的數據結構(modified preorder tree traversal),在此做下筆記。
此數據結構的好處是查詢非常快,僅僅需要判斷left> ? right <?這樣即可,缺點是修改節點表數據量大時很慢,而且操作複雜一點。
左右值數據結構網上教程很多,不再贅述,總結一下就是:要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小 ,
直接上代碼,上面有註釋 ,說明了如何找子節點,如何找父節點、刪除節點,同級平移,兄弟節點前插入等(基於postgreSQL數據庫)。
表字段
CREATE TABLE "public"."t_test" (
"id" int4 NOT NULL DEFAULT nextval('"T_TEST_ID_seq"'::regclass),
"name" varchar(255) COLLATE "pg_catalog"."default",
"l" int4,
"r" int4,
"level" int4,
"other" varchar(255) COLLATE "pg_catalog"."default",
"status" int2 DEFAULT 0,
PRIMARY KEY ("id")
)
;
單獨摘出-查詢節點SQL
--查詢節點
--查詢節點下全部子孫菜單不包括自身菜單 (查詢“根菜單下的所有子節點”),子查詢中id=1表示查詢該父節點的left值和right值
SELECT * FROM t_test WHERE l > (SELECT l from t_test where id = 1) AND r < (SELECT r from t_test where id = 1);
--查詢子孫總數=(右值-左值-1)/2
--查詢某一節點的全部父祖節點
SELECT * FROM t_test WHERE l <(SELECT l from t_test where id = 4) AND r > (SELECT r from t_test where id = 4) order by l;
--判斷某一節點是否是指定節點的字節點,判斷條件是 子節點的Left > 父節點left , 子節點right < 父節點Right
--8 > 3 AND 9 <4 = false
--判斷是否是指定節點的父節點同理,子節點的Left < 父節點left , 子節點right > 父節點Right
測試的數據庫function
CREATE OR REPLACE FUNCTION "public"."addNode"()
RETURNS "pg_catalog"."void" AS $BODY$
-- Routine body goes here...
DECLARE
--此方法爲測試方法,可刪除
parent_right integer;
parent_left integer;
--移動節點所用變量
count_level integer;
effect_value integer;
effect_left integer;
effect_right integer;
target_position integer;
--有很多用不上的變量
source_value integer;
source_left integer;
source_right integer;
target_left integer;
target_right integer;
position_temp integer;
source_ids integer;
my_cursor REFCURSOR;
--要移動的節點ID
source_id_v integer;
--移動到目標節點的ID
target_id_v integer;
BEGIN
--clear
delete from t_test;
--ROOT
INSERT INTO T_TEST VALUES(1,'根菜單',1,2,0,'');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 1;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
--二級菜單,level = 父結點level+1 = 1,ID = PARENT ID +1 = 2
--重點,LEFT和RIGHT並不是二叉樹的左結點和右結點ID值,僅僅是一個順序標識,left = parent left,right = parent right +1=3
INSERT INTO T_TEST VALUES(2,'計算機中心',parent_right,parent_right+1,1,
'三級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 2;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(3,'設備管理',parent_right,parent_right+1,2,
'三級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 2;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
INSERT INTO T_TEST VALUES(4,'微信管理',parent_right,parent_right+1,2,
'三級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 4;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
INSERT INTO T_TEST VALUES(5,'服務號',parent_right,parent_right+1,3,
'四級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 4;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
INSERT INTO T_TEST VALUES(6,'企業號',parent_right,parent_right+1,3,
'四級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 1;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
INSERT INTO T_TEST VALUES(7,'行政中心',parent_right,parent_right+1,1,
'二級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 7;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(8,'車輛管理',parent_right,parent_right+1,2,
'三級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 1;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(9,'人力資源',parent_right,parent_right+1,1,
'二級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 9;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(10,'組織結構',parent_right,parent_right+1,2,
'二級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 9;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(11,'員工檔案',parent_right,parent_right+1,2,
'二級菜單');
--查出父節點的right值
SELECT r INTO parent_right from t_test where id = 9;
--計算受影響結點的左右值 ,要保持父節點右值比所有子節點的右值大,左節點左值比所有子結點左值小
UPDATE T_TEST SET l=l+2 WHERE l >= parent_right;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_right;
INSERT INTO T_TEST VALUES(12,'合同管理',parent_right,parent_right+1,2,
'二級菜單');
--在設備管理前面添加一個節點作爲其兄弟節點
--查出兄弟節點的right值
SELECT l,r INTO parent_left,parent_right from t_test where id = 3;
--計算受影響結點的左右值
UPDATE T_TEST SET l=l+2 WHERE l >= parent_left;
UPDATE T_TEST SET r=r+2 WHERE r >= parent_left;
INSERT INTO T_TEST VALUES(13,'設備管理前的兄弟節點',parent_left,parent_right,2,'我是新加入的兄弟節點');
--刪除節點
--刪除ID=4節點下的所有子節點,查出父節點的right值
SELECT l,r INTO parent_left,parent_right from t_test where id = 4;
--LEFT比父節點值大 且right比父節點值小,則表示是它的字節點,刪除子節點
--delete from t_test where l >=parent_left and r <=parent_right;
--修改受影響節點的值,left > 父節點left right > 父節點right,也就是修改其所有父節點的左右值
--update t_test set l=l-(parent_right - parent_left + 1) where l > parent_left;
--update t_test set r=r-(parent_right - parent_left + 1) where r > parent_right;
--查詢節點
--查詢節點下全部子孫菜單不包括自身菜單 (查詢“根菜單下的所有子節點”),子查詢中id=1表示查詢該父節點的left值和right值
--SELECT * FROM t_test WHERE l > (SELECT l from t_test where id = 1) AND r < (SELECT r from t_test where id = 1);
--查詢子孫總數=(右值-左值-1)/2
--查詢某一節點的全部父祖節點
--SELECT * FROM t_test WHERE l <(SELECT l from t_test where id = 4) AND r > (SELECT r from t_test where id = 4) order by l;
--判斷某一節點是否是指定節點的字節點,判斷條件是 子節點的Left > 父節點left , 子節點right < 父節點Right
--8 > 3 AND 9 <4 = false
--判斷是否是指定節點的父節點同理,子節點的Left < 父節點left , 子節點right > 父節點Right
--移動節點,此移動方式操作多餘
SELECT l, r into target_left,target_right FROM t_test WHERE id = 10;
--要移動的節點
SELECT l,r into source_left,source_right from t_test where id = 12;
UPDATE T_TEST SET l=l+2,r=r+2 WHERE l >= target_left and r <= source_right;
--UPDATE T_TEST SET r=r+2 WHERE ;
--移動節點 ,將合同管理移動到組織結構前面
UPDATE T_TEST SET l=target_left,r=target_right WHERE id=12;
--移動節點思路一,同節點平移可以直接交換左右值即可(應使用這個)
--節點同等級後移,id=11是移動到其節點後的目標節點
SELECT l, r into target_left,target_right FROM t_test WHERE id = 12;
--要移動的節點
SELECT l,r into source_left,source_right from t_test where id = 10;
--移動節點
UPDATE T_TEST SET l=target_left,r=target_right WHERE id=10;
UPDATE T_TEST SET l=source_left,r=source_right WHERE id=12;
RETURN;
END$BODY$
LANGUAGE plpgsql VOLATILE
COST 100