實現樹形菜單或分類的方法之一,使用左右值樹形數據結構(modified preorder tree traversal)實現樹形菜單

突然發現自己以前常用的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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章